Merge changes from topic "query-callback" into androidx-master-dev

* changes:
  Follow-up CL resolving b/174478034. Adding functionality for handling bind arguments provided for queries.
  Implementing functionality for a general callback function for SQLite queries. If possible, bind arguments are provided to the callback in addition to the SQLite query statement. This callback may be used for logging executed queries, in which case it is recommended to use an immediate executor.
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index 7fe96b8..4175155 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-beta01") {
+    api(projectOrArtifact(":lifecycle:lifecycle-runtime-ktx")) {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-beta01")
-    api("androidx.savedstate:savedstate-ktx:1.1.0-beta01") {
+    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-ktx"))
+    api(projectOrArtifact(":savedstate:savedstate-ktx")) {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
     api(KOTLIN_STDLIB)
 
-    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.3.0-beta01")
+    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing"))
     androidTestImplementation(JUNIT)
     androidTestImplementation(TRUTH)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index b0d8662..e683bb6 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -23,12 +23,12 @@
     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-beta01")
-    api("androidx.lifecycle:lifecycle-viewmodel:2.3.0-beta01")
-    api("androidx.savedstate:savedstate:1.1.0-beta01")
-    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-beta01")
+    api(projectOrArtifact(":lifecycle:lifecycle-runtime"))
+    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel"))
+    api(projectOrArtifact(":savedstate:savedstate"))
+    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-savedstate"))
 
-    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.3.0-beta01")
+    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing"))
     androidTestImplementation(KOTLIN_STDLIB)
     androidTestImplementation(LEAKCANARY)
     androidTestImplementation(LEAKCANARY_INSTRUMENTATION)
diff --git a/annotation/annotation-experimental-lint/build.gradle b/annotation/annotation-experimental-lint/build.gradle
index 6c42ad7..bc810c7 100644
--- a/annotation/annotation-experimental-lint/build.gradle
+++ b/annotation/annotation-experimental-lint/build.gradle
@@ -51,9 +51,3 @@
     description = "Lint checks for the Experimental annotation library. Also enforces the " +
             "semantics of Kotlin @Experimental APIs from within Android Java source code."
 }
-
-tasks.withType(KotlinCompile).configureEach {
-    kotlinOptions {
-        freeCompilerArgs += ["-XXLanguage:-NewInference"]
-    }
-}
diff --git a/appcompat/appcompat/api/current.txt b/appcompat/appcompat/api/current.txt
index 5bbbed7..e117f42 100644
--- a/appcompat/appcompat/api/current.txt
+++ b/appcompat/appcompat/api/current.txt
@@ -517,11 +517,11 @@
     ctor public AppCompatEditText(android.content.Context);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.core.widget.RichContentReceiverCompat<android.widget.TextView!>? getRichContentReceiverCompat();
+    method public androidx.core.view.OnReceiveContentListener<android.widget.TextView!>? getOnReceiveContentListenerCompat();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable?);
-    method public void setRichContentReceiverCompat(androidx.core.widget.RichContentReceiverCompat<android.widget.TextView!>?);
+    method public void setOnReceiveContentListenerCompat(androidx.core.view.OnReceiveContentListener<android.widget.TextView!>?);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
     method public void setTextAppearance(android.content.Context!, int);
diff --git a/appcompat/appcompat/api/public_plus_experimental_current.txt b/appcompat/appcompat/api/public_plus_experimental_current.txt
index cb1efa6..7c9aad3 100644
--- a/appcompat/appcompat/api/public_plus_experimental_current.txt
+++ b/appcompat/appcompat/api/public_plus_experimental_current.txt
@@ -517,11 +517,11 @@
     ctor public AppCompatEditText(android.content.Context);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.core.widget.RichContentReceiverCompat<android.widget.TextView!>? getRichContentReceiverCompat();
+    method public androidx.core.view.OnReceiveContentListener<android.widget.TextView!>? getOnReceiveContentListenerCompat();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable?);
-    method public void setRichContentReceiverCompat(androidx.core.widget.RichContentReceiverCompat<android.widget.TextView!>?);
+    method public void setOnReceiveContentListenerCompat(androidx.core.view.OnReceiveContentListener<android.widget.TextView!>?);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
     method public void setTextAppearance(android.content.Context!, int);
diff --git a/appcompat/appcompat/api/restricted_current.txt b/appcompat/appcompat/api/restricted_current.txt
index 14d5f94..192c516 100644
--- a/appcompat/appcompat/api/restricted_current.txt
+++ b/appcompat/appcompat/api/restricted_current.txt
@@ -1398,11 +1398,11 @@
     ctor public AppCompatEditText(android.content.Context);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet?, int);
-    method public androidx.core.widget.RichContentReceiverCompat<android.widget.TextView!>? getRichContentReceiverCompat();
+    method public androidx.core.view.OnReceiveContentListener<android.widget.TextView!>? getOnReceiveContentListenerCompat();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable?);
-    method public void setRichContentReceiverCompat(androidx.core.widget.RichContentReceiverCompat<android.widget.TextView!>?);
+    method public void setOnReceiveContentListenerCompat(androidx.core.view.OnReceiveContentListener<android.widget.TextView!>?);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
     method public void setTextAppearance(android.content.Context!, int);
diff --git a/appcompat/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
index 9af9433..c216f56 100644
--- a/appcompat/appcompat/src/androidTest/AndroidManifest.xml
+++ b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
@@ -100,8 +100,8 @@
             android:theme="@style/Theme.TextColors"/>
 
         <activity
-            android:name="androidx.appcompat.widget.AppCompatEditTextRichContentReceiverActivity"
-            android:label="@string/app_compat_edit_text_rich_content_receiver_activity"
+            android:name="androidx.appcompat.widget.AppCompatEditTextReceiveContentActivity"
+            android:label="@string/app_compat_edit_text_receive_content_activity"
             android:theme="@style/Theme.AppCompat.Light"/>
 
         <activity
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextRichContentReceiverActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextReceiveContentActivity.java
similarity index 83%
rename from appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextRichContentReceiverActivity.java
rename to appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextReceiveContentActivity.java
index 9601ac1..4dc2c31 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextRichContentReceiverActivity.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextReceiveContentActivity.java
@@ -18,9 +18,9 @@
 import androidx.appcompat.test.R;
 import androidx.appcompat.testutils.BaseTestActivity;
 
-public class AppCompatEditTextRichContentReceiverActivity extends BaseTestActivity {
+public class AppCompatEditTextReceiveContentActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return R.layout.appcompat_edittext_richcontentreceiver_activity;
+        return R.layout.appcompat_edittext_receive_content_activity;
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextRichContentReceiverTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextReceiveContentTest.java
similarity index 90%
rename from appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextRichContentReceiverTest.java
rename to appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextReceiveContentTest.java
index 5f68405..627bf68 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextRichContentReceiverTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextReceiveContentTest.java
@@ -16,9 +16,9 @@
 
 package androidx.appcompat.widget;
 
-import static androidx.core.widget.RichContentReceiverCompat.FLAG_CONVERT_TO_PLAIN_TEXT;
-import static androidx.core.widget.RichContentReceiverCompat.SOURCE_CLIPBOARD;
-import static androidx.core.widget.RichContentReceiverCompat.SOURCE_INPUT_METHOD;
+import static androidx.core.view.OnReceiveContentListener.FLAG_CONVERT_TO_PLAIN_TEXT;
+import static androidx.core.view.OnReceiveContentListener.SOURCE_CLIPBOARD;
+import static androidx.core.view.OnReceiveContentListener.SOURCE_INPUT_METHOD;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -44,11 +44,10 @@
 
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.test.R;
+import androidx.core.view.OnReceiveContentListener;
 import androidx.core.view.inputmethod.EditorInfoCompat;
 import androidx.core.view.inputmethod.InputConnectionCompat;
 import androidx.core.view.inputmethod.InputContentInfoCompat;
-import androidx.core.widget.RichContentReceiverCompat;
-import androidx.core.widget.TextViewRichContentReceiverCompat;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
@@ -69,17 +68,17 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class AppCompatEditTextRichContentReceiverTest {
+public class AppCompatEditTextReceiveContentTest {
     private static final Set<String> ALL_TEXT_AND_IMAGE_MIME_TYPES = ImmutableSet.of(
             "text/*", "image/*");
 
     @Rule
-    public final ActivityTestRule<AppCompatEditTextRichContentReceiverActivity> mActivityTestRule =
-            new ActivityTestRule<>(AppCompatEditTextRichContentReceiverActivity.class);
+    public final ActivityTestRule<AppCompatEditTextReceiveContentActivity> mActivityTestRule =
+            new ActivityTestRule<>(AppCompatEditTextReceiveContentActivity.class);
 
     private Context mContext;
     private AppCompatEditText mEditText;
-    private RichContentReceiverCompat<TextView> mMockReceiver;
+    private OnReceiveContentListener<TextView> mMockReceiver;
     private ClipboardManager mClipboardManager;
 
     @UiThreadTest
@@ -87,9 +86,9 @@
     public void before() {
         AppCompatActivity activity = mActivityTestRule.getActivity();
         mContext = activity;
-        mEditText = activity.findViewById(R.id.edit_text_default_values);
+        mEditText = activity.findViewById(R.id.edit_text);
 
-        mMockReceiver = Mockito.mock(RichContentReceiverCompat.class);
+        mMockReceiver = Mockito.mock(OnReceiveContentListener.class);
 
         mClipboardManager = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
 
@@ -109,16 +108,16 @@
     @Test
     public void testGetAndSetRichContentReceiverCompat() throws Exception {
         // Verify that by default the getter returns null.
-        assertThat(mEditText.getRichContentReceiverCompat()).isNull();
+        assertThat(mEditText.getOnReceiveContentListenerCompat()).isNull();
 
         // Verify that after setting a custom receiver, the getter returns it.
-        TextViewRichContentReceiverCompat receiver = new TextViewRichContentReceiverCompat() {};
-        mEditText.setRichContentReceiverCompat(receiver);
-        assertThat(mEditText.getRichContentReceiverCompat()).isSameInstanceAs(receiver);
+        OnReceiveContentListener<TextView> receiver = mMockReceiver;
+        mEditText.setOnReceiveContentListenerCompat(receiver);
+        assertThat(mEditText.getOnReceiveContentListenerCompat()).isSameInstanceAs(receiver);
 
         // Verify that the receiver can be reset by passing null.
-        mEditText.setRichContentReceiverCompat(null);
-        assertThat(mEditText.getRichContentReceiverCompat()).isNull();
+        mEditText.setOnReceiveContentListenerCompat(null);
+        assertThat(mEditText.getOnReceiveContentListenerCompat()).isNull();
     }
 
     @UiThreadTest
@@ -152,7 +151,7 @@
         // Setup: Configure the receiver to a custom impl.
         Set<String> receiverMimeTypes = ImmutableSet.of("text/plain", "image/png", "video/mp4");
         when(mMockReceiver.getSupportedMimeTypes()).thenReturn(receiverMimeTypes);
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Call onCreateInputConnection() and assert that contentMimeTypes is set from the receiver.
         EditorInfo editorInfo = new EditorInfo();
@@ -205,7 +204,7 @@
         clip = copyToClipboard(clip);
 
         // Setup: Configure to use the mock receiver.
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the "Paste" action and assert that the custom receiver was executed.
         triggerContextMenuAction(android.R.id.paste);
@@ -224,7 +223,7 @@
         clip = copyToClipboard(clip);
 
         // Setup: Configure to use the mock receiver.
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the "Paste" action and assert that the boolean result is true regardless of
         // the receiver's return value.
@@ -250,7 +249,7 @@
         clip = copyToClipboard(clip);
 
         // Setup: Configure to use the mock receiver.
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the "Paste" action and assert that the custom receiver was executed. This
         // confirms that the receiver is invoked (give a chance to handle the content via some
@@ -290,7 +289,7 @@
         clip = copyToClipboard(clip);
 
         // Setup: Configure to use the mock receiver.
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the "Paste as plain text" action and assert that the custom receiver was
         // executed.
@@ -312,7 +311,7 @@
         clip = copyToClipboard(clip);
 
         // Setup: Configure to use the mock receiver.
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the "Paste as plain text" action and assert that the custom receiver was
         // executed. This confirms that the receiver is invoked (given a chance to handle the
@@ -343,7 +342,7 @@
 
         // Setup: Configure the receiver to a custom impl that supports all text and images.
         when(mMockReceiver.getSupportedMimeTypes()).thenReturn(ALL_TEXT_AND_IMAGE_MIME_TYPES);
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the IME's commitContent() call and assert that the custom receiver was executed.
         triggerImeCommitContentViaCompat("image/png");
@@ -360,7 +359,7 @@
 
         // Setup: Configure the receiver to a custom impl that supports all text and images.
         when(mMockReceiver.getSupportedMimeTypes()).thenReturn(ALL_TEXT_AND_IMAGE_MIME_TYPES);
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the IME's commitContent() call, once when the mock receiver is configured to
         // return true and once when the mock receiver is configured to return false.
@@ -396,7 +395,7 @@
 
         // Setup: Configure the receiver to a custom impl that supports all text and images.
         when(mMockReceiver.getSupportedMimeTypes()).thenReturn(ALL_TEXT_AND_IMAGE_MIME_TYPES);
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the IME's commitContent() call and assert that the custom receiver was not
         // executed. This is because InputConnectionCompat.commitContent() checks the supported MIME
@@ -414,7 +413,7 @@
 
         // Setup: Configure the receiver to a custom impl that supports all text and images.
         when(mMockReceiver.getSupportedMimeTypes()).thenReturn(ALL_TEXT_AND_IMAGE_MIME_TYPES);
-        mEditText.setRichContentReceiverCompat(mMockReceiver);
+        mEditText.setOnReceiveContentListenerCompat(mMockReceiver);
 
         // Trigger the IME's commitContent() call and assert that the custom receiver was executed.
         triggerImeCommitContentDirect("video/mp4");
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatTest.java
index 63d04a8..92aba86 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatTest.java
@@ -24,8 +24,10 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import android.graphics.Typeface;
+import android.os.Build;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -87,21 +89,37 @@
         AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
         switchButton.onInitializeAccessibilityNodeInfo(info);
         assertEquals("android.widget.Switch", info.getClassName());
-        assertEquals(mActivity.getResources().getString(R.string.sample_text1), info.getText());
-        assertEquals(
-                mActivity.getResources().getString(androidx.appcompat.R.string.abc_capital_off),
-                ViewCompat.getStateDescription(switchButton)
-        );
+        final String capitalOff =
+                mActivity.getResources().getString(androidx.appcompat.R.string.abc_capital_off);
+        final String text = mActivity.getResources().getString(R.string.sample_text1);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            assertEquals(text + " " + capitalOff, info.getText());
+            assertNull(ViewCompat.getStateDescription(switchButton));
+        } else {
+            assertEquals(text, info.getText());
+            assertEquals(capitalOff, ViewCompat.getStateDescription(switchButton));
+        }
+        info.recycle();
     }
 
     @Test
     public void testAccessibility_textOnOff() {
         final SwitchCompat switchButton = mContainer.findViewById(R.id.switch_textOnOff);
+        final CharSequence textOn = "testStateOn";
+        final CharSequence textOff = "testStateOff";
         AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
         switchButton.onInitializeAccessibilityNodeInfo(info);
         assertEquals("android.widget.Switch", info.getClassName());
-        assertEquals(mActivity.getResources().getString(R.string.sample_text1), info.getText());
-        assertEquals("testStateOff", ViewCompat.getStateDescription(switchButton));
+        final String text = mActivity.getResources().getString(R.string.sample_text1);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            assertEquals(text + " " + textOff, info.getText());
+            assertNull(ViewCompat.getStateDescription(switchButton));
+        } else {
+            assertEquals(text, info.getText());
+            assertEquals(textOff, ViewCompat.getStateDescription(switchButton));
+        }
+        info.recycle();
+
         final CharSequence newTextOff = "new text off";
         final CharSequence newTextOn = "new text on";
         mActivity.runOnUiThread(
@@ -109,12 +127,41 @@
                     @Override
                     public void run() {
                         switchButton.toggle();
-                        assertEquals("testStateOn", ViewCompat.getStateDescription(switchButton));
-                        switchButton.setTextOff(newTextOff);
+                        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+                        switchButton.onInitializeAccessibilityNodeInfo(info);
+                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                            assertEquals(text + " " + textOn, info.getText());
+                            assertNull(ViewCompat.getStateDescription(switchButton));
+                        } else {
+                            assertEquals(text, info.getText());
+                            assertEquals(textOn, ViewCompat.getStateDescription(switchButton));
+                        }
+                        info.recycle();
+
                         switchButton.setTextOn(newTextOn);
-                        assertEquals(newTextOn, ViewCompat.getStateDescription(switchButton));
+                        switchButton.setTextOff(newTextOff);
+                        info = AccessibilityNodeInfo.obtain();
+                        switchButton.onInitializeAccessibilityNodeInfo(info);
+                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                            assertEquals(text + " " + newTextOn, info.getText());
+                            assertNull(ViewCompat.getStateDescription(switchButton));
+                        } else {
+                            assertEquals(text, info.getText());
+                            assertEquals(newTextOn, ViewCompat.getStateDescription(switchButton));
+                        }
+                        info.recycle();
+
                         switchButton.toggle();
-                        assertEquals(newTextOff, ViewCompat.getStateDescription(switchButton));
+                        info = AccessibilityNodeInfo.obtain();
+                        switchButton.onInitializeAccessibilityNodeInfo(info);
+                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+                            assertEquals(text + " " + newTextOff, info.getText());
+                            assertNull(ViewCompat.getStateDescription(switchButton));
+                        } else {
+                            assertEquals(text, info.getText());
+                            assertEquals(newTextOff, ViewCompat.getStateDescription(switchButton));
+                        }
+                        info.recycle();
                     }
                 }
         );
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_richcontentreceiver_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_receive_content_activity.xml
similarity index 95%
rename from appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_richcontentreceiver_activity.xml
rename to appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_receive_content_activity.xml
index b1c4421..c1706b6 100644
--- a/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_richcontentreceiver_activity.xml
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_receive_content_activity.xml
@@ -28,7 +28,7 @@
         android:orientation="vertical">
 
         <androidx.appcompat.widget.AppCompatEditText
-            android:id="@+id/edit_text_default_values"
+            android:id="@+id/edit_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"/>
     </LinearLayout>
diff --git a/appcompat/appcompat/src/androidTest/res/values/strings.xml b/appcompat/appcompat/src/androidTest/res/values/strings.xml
index 19145b8..563bc0e 100644
--- a/appcompat/appcompat/src/androidTest/res/values/strings.xml
+++ b/appcompat/appcompat/src/androidTest/res/values/strings.xml
@@ -57,7 +57,9 @@
     <string name="app_compat_text_view_activity">AppCompat text view</string>
     <string name="app_compat_text_view_auto_size_activity">AppCompat text view auto-size</string>
     <string name="app_compat_edit_text_activity">AppCompat edit text</string>
-    <string name="app_compat_edit_text_rich_content_receiver_activity">AppCompat edit text rich content receiver</string>
+    <string name="app_compat_edit_text_receive_content_activity">
+        AppCompat edit text receive content activity
+    </string>
     <string name="app_compat_button_auto_size_activity">AppCompat button auto-size</string>
     <string name="sample_text1">Sample text 1</string>
     <string name="sample_text2">Sample text 2</string>
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
index 30e4e6f..a0ac378 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
@@ -17,8 +17,8 @@
 package androidx.appcompat.widget;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-import static androidx.core.widget.RichContentReceiverCompat.FLAG_CONVERT_TO_PLAIN_TEXT;
-import static androidx.core.widget.RichContentReceiverCompat.SOURCE_CLIPBOARD;
+import static androidx.core.view.OnReceiveContentListener.FLAG_CONVERT_TO_PLAIN_TEXT;
+import static androidx.core.view.OnReceiveContentListener.SOURCE_CLIPBOARD;
 
 import android.content.ClipData;
 import android.content.ClipboardManager;
@@ -42,10 +42,11 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.appcompat.R;
+import androidx.core.view.OnReceiveContentListener;
 import androidx.core.view.TintableBackgroundView;
 import androidx.core.view.inputmethod.InputConnectionCompat;
-import androidx.core.widget.RichContentReceiverCompat;
 import androidx.core.widget.TextViewCompat;
+import androidx.core.widget.TextViewOnReceiveContentListener;
 
 /**
  * A {@link EditText} which supports compatible features on older versions of the platform,
@@ -55,8 +56,8 @@
  *     {@link androidx.core.view.ViewCompat}.</li>
  *     <li>Allows setting of the background tint using {@link R.attr#backgroundTint} and
  *     {@link R.attr#backgroundTintMode}.</li>
- *     <li>Allows setting a custom {@link RichContentReceiverCompat receiver callback} in order to
- *     handle insertion of content (e.g. pasting text or an image from the clipboard). This callback
+ *     <li>Allows setting a custom {@link OnReceiveContentListener listener} to handle
+ *     insertion of content (e.g. pasting text or an image from the clipboard). This listener
  *     provides the opportunity to implement app-specific handling such as creating an attachment
  *     when an image is pasted.</li>
  * </ul>
@@ -72,7 +73,7 @@
     private final AppCompatTextHelper mTextHelper;
     private final AppCompatTextClassifierHelper mTextClassifierHelper;
     @Nullable
-    private RichContentReceiverCompat<TextView> mRichContentReceiverCompat;
+    private OnReceiveContentListener<TextView> mOnReceiveContentListener;
 
     public AppCompatEditText(@NonNull Context context) {
         this(context, null);
@@ -204,7 +205,7 @@
     }
 
     /**
-     * If a {@link #setRichContentReceiverCompat receiver callback} is set, the returned
+     * If a {@link #setOnReceiveContentListenerCompat receiver callback} is set, the returned
      * {@link InputConnection} will use it to handle calls to {@link InputConnection#commitContent}.
      *
      * {@inheritDoc}
@@ -214,10 +215,10 @@
         InputConnection ic = super.onCreateInputConnection(outAttrs);
         mTextHelper.populateSurroundingTextIfNeeded(this, ic, outAttrs);
         ic = AppCompatHintHelper.onCreateInputConnection(ic, outAttrs, this);
-        if (ic != null && mRichContentReceiverCompat != null) {
-            mRichContentReceiverCompat.populateEditorInfoContentMimeTypes(ic, outAttrs);
+        if (ic != null && mOnReceiveContentListener != null) {
+            mOnReceiveContentListener.populateEditorInfoContentMimeTypes(ic, outAttrs);
             InputConnectionCompat.OnCommitContentListener callback =
-                    mRichContentReceiverCompat.buildOnCommitContentListener(this);
+                    mOnReceiveContentListener.buildOnCommitContentListener(this);
             ic = InputConnectionCompat.createWrapper(ic, outAttrs, callback);
         }
         return ic;
@@ -264,14 +265,14 @@
     }
 
     /**
-     * If a {@link #setRichContentReceiverCompat receiver callback} is set, uses it to execute the
+     * If a {@link #setOnReceiveContentListenerCompat listener} is set, uses it to execute the
      * "Paste" and "Paste as plain text" menu actions.
      *
      * {@inheritDoc}
      */
     @Override
     public boolean onTextContextMenuItem(int id) {
-        if (mRichContentReceiverCompat == null) {
+        if (mOnReceiveContentListener == null) {
             return super.onTextContextMenuItem(id);
         }
         if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {
@@ -280,7 +281,7 @@
             ClipData clip = cm == null ? null : cm.getPrimaryClip();
             if (clip != null) {
                 int flags = (id == android.R.id.paste) ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT;
-                mRichContentReceiverCompat.onReceive(this, clip, SOURCE_CLIPBOARD, flags);
+                mOnReceiveContentListener.onReceive(this, clip, SOURCE_CLIPBOARD, flags);
             }
             return true;
         }
@@ -289,15 +290,15 @@
 
     /**
      * Returns the callback that handles insertion of content into this view (e.g. pasting from
-     * the clipboard). See {@link #setRichContentReceiverCompat} for more info.
+     * the clipboard). See {@link #setOnReceiveContentListenerCompat} for more info.
      *
      * @return The callback that this view is using to handle insertion of content. Returns
      * {@code null} if no callback is configured, in which case the platform behavior of the
      * {@link EditText} component will be used for content insertion.
      */
     @Nullable
-    public RichContentReceiverCompat<TextView> getRichContentReceiverCompat() {
-        return mRichContentReceiverCompat;
+    public OnReceiveContentListener<TextView> getOnReceiveContentListenerCompat() {
+        return mOnReceiveContentListener;
     }
 
     /**
@@ -305,7 +306,7 @@
      *
      * <p>"Content" and "rich content" here refers to both text and non-text: plain text, styled
      * text, HTML, images, videos, audio files, etc. The callback configured here should typically
-     * extend from {@link androidx.core.widget.TextViewRichContentReceiverCompat} to provide
+     * extend from {@link TextViewOnReceiveContentListener} to provide
      * consistent behavior for text content.
      *
      * <p>This callback will be invoked for the following scenarios:
@@ -319,8 +320,8 @@
      *                 callback (the platform behavior of the {@link EditText} component will then
      *                 be used).
      */
-    public void setRichContentReceiverCompat(
-            @Nullable RichContentReceiverCompat<TextView> receiver) {
-        mRichContentReceiverCompat = receiver;
+    public void setOnReceiveContentListenerCompat(
+            @Nullable OnReceiveContentListener<TextView> receiver) {
+        mOnReceiveContentListener = receiver;
     }
 }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
index 5e25a81..f4d72a52 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
@@ -775,9 +775,11 @@
     public void setTextOn(CharSequence textOn) {
         mTextOn = textOn;
         requestLayout();
-        // Default state is derived from on/off-text, so state has to be updated when on/off-text
-        // are updated.
-        setOnStateDescription();
+        if (isChecked()) {
+            // Default state is derived from on/off-text, so state has to be updated when
+            // on/off-text are updated.
+            setOnStateDescriptionOnRAndAbove();
+        }
     }
 
     /**
@@ -797,9 +799,11 @@
     public void setTextOff(CharSequence textOff) {
         mTextOff = textOff;
         requestLayout();
-        // Default state is derived from on/off-text, so state has to be updated when on/off-text
-        // are updated.
-        setOffStateDescription();
+        if (!isChecked()) {
+            // Default state is derived from on/off-text, so state has to be updated when
+            // on/off-text are updated.
+            setOffStateDescriptionOnRAndAbove();
+        }
     }
 
     /**
@@ -1095,9 +1099,9 @@
         checked = isChecked();
 
         if (checked) {
-            setOnStateDescription();
+            setOnStateDescriptionOnRAndAbove();
         } else {
-            setOffStateDescription();
+            setOffStateDescriptionOnRAndAbove();
         }
 
         if (getWindowToken() != null && ViewCompat.isLaidOut(this)) {
@@ -1433,6 +1437,19 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(ACCESSIBILITY_EVENT_CLASS_NAME);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+            if (!TextUtils.isEmpty(switchText)) {
+                CharSequence oldText = info.getText();
+                if (TextUtils.isEmpty(oldText)) {
+                    info.setText(switchText);
+                } else {
+                    StringBuilder newText = new StringBuilder();
+                    newText.append(oldText).append(' ').append(switchText);
+                    info.setText(newText);
+                }
+            }
+        }
     }
 
     /**
@@ -1452,17 +1469,21 @@
         return amount < low ? low : (amount > high ? high : amount);
     }
 
-    private void setOnStateDescription() {
-        ViewCompat.setStateDescription(
-                this,
-                mTextOn == null ? getResources().getString(R.string.abc_capital_on) : mTextOn
-        );
+    private void setOnStateDescriptionOnRAndAbove() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            ViewCompat.setStateDescription(
+                    this,
+                    mTextOn == null ? getResources().getString(R.string.abc_capital_on) : mTextOn
+            );
+        }
     }
 
-    private void setOffStateDescription() {
-        ViewCompat.setStateDescription(
-                this,
-                mTextOff == null ? getResources().getString(R.string.abc_capital_off) : mTextOff
-        );
+    private void setOffStateDescriptionOnRAndAbove() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            ViewCompat.setStateDescription(
+                    this,
+                    mTextOff == null ? getResources().getString(R.string.abc_capital_off) : mTextOff
+            );
+        }
     }
 }
diff --git a/appsearch/appsearch/build.gradle b/appsearch/appsearch/build.gradle
index 907de5a..085f8ad 100644
--- a/appsearch/appsearch/build.gradle
+++ b/appsearch/appsearch/build.gradle
@@ -56,8 +56,8 @@
     def suffix = name.capitalize()
     project.tasks.create(name: "jar${suffix}", type: Jar) {
         dependsOn variant.javaCompileProvider.get()
-        from variant.javaCompileProvider.get().destinationDir
-        destinationDir new File(project.buildDir, "libJar")
+        from variant.javaCompileProvider.get().destinationDirectory
+        destinationDirectory.set(new File(project.buildDir, "libJar"))
     }
 }
 
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
index 6edc6c1..bb81bfd 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
@@ -27,9 +27,11 @@
 import android.content.Context;
 
 import androidx.appsearch.annotation.AppSearchDocument;
+import androidx.appsearch.app.util.AppSearchTestUtils;
 import androidx.appsearch.localstorage.LocalStorage;
 import androidx.test.core.app.ApplicationProvider;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -43,18 +45,22 @@
     @Before
     public void setUp() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
+        AppSearchTestUtils.cleanup(context);
+
         mSession = checkIsResultSuccess(LocalStorage.createSearchSession(
                 new LocalStorage.SearchContext.Builder(context)
-                        .setDatabaseName("testDb1").build()));
+                        .setDatabaseName(AppSearchTestUtils.DB_1).build()));
+    }
 
-        // Remove all documents from any instances that may have been created in the tests.
-        checkIsResultSuccess(
-                mSession.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()));
+    @After
+    public void tearDown() throws Exception {
+        AppSearchTestUtils.cleanup(ApplicationProvider.getApplicationContext());
     }
 
     @AppSearchDocument
     static class Card {
-        @AppSearchDocument.Uri String mUri;
+        @AppSearchDocument.Uri
+        String mUri;
         @AppSearchDocument.Property
                 (indexingType = INDEXING_TYPE_PREFIXES, tokenizerType = TOKENIZER_TYPE_PLAIN)
         String mString;        // 3a
@@ -75,48 +81,84 @@
 
     @AppSearchDocument
     static class Gift {
-        @AppSearchDocument.Uri String mUri;
+        @AppSearchDocument.Uri
+        String mUri;
 
         // Collections
-        @AppSearchDocument.Property Collection<Long> mCollectLong;         // 1a
-        @AppSearchDocument.Property Collection<Integer> mCollectInteger;   // 1a
-        @AppSearchDocument.Property Collection<Double> mCollectDouble;     // 1a
-        @AppSearchDocument.Property Collection<Float> mCollectFloat;       // 1a
-        @AppSearchDocument.Property Collection<Boolean> mCollectBoolean;   // 1a
-        @AppSearchDocument.Property Collection<byte[]> mCollectByteArr;    // 1a
-        @AppSearchDocument.Property Collection<String> mCollectString;     // 1b
-        @AppSearchDocument.Property Collection<Card> mCollectCard;         // 1c
+        @AppSearchDocument.Property
+        Collection<Long> mCollectLong;         // 1a
+        @AppSearchDocument.Property
+        Collection<Integer> mCollectInteger;   // 1a
+        @AppSearchDocument.Property
+        Collection<Double> mCollectDouble;     // 1a
+        @AppSearchDocument.Property
+        Collection<Float> mCollectFloat;       // 1a
+        @AppSearchDocument.Property
+        Collection<Boolean> mCollectBoolean;   // 1a
+        @AppSearchDocument.Property
+        Collection<byte[]> mCollectByteArr;    // 1a
+        @AppSearchDocument.Property
+        Collection<String> mCollectString;     // 1b
+        @AppSearchDocument.Property
+        Collection<Card> mCollectCard;         // 1c
 
         // Arrays
-        @AppSearchDocument.Property Long[] mArrBoxLong;         // 2a
-        @AppSearchDocument.Property long[] mArrUnboxLong;       // 2b
-        @AppSearchDocument.Property Integer[] mArrBoxInteger;   // 2a
-        @AppSearchDocument.Property int[] mArrUnboxInt;         // 2a
-        @AppSearchDocument.Property Double[] mArrBoxDouble;     // 2a
-        @AppSearchDocument.Property double[] mArrUnboxDouble;   // 2b
-        @AppSearchDocument.Property Float[] mArrBoxFloat;       // 2a
-        @AppSearchDocument.Property float[] mArrUnboxFloat;     // 2a
-        @AppSearchDocument.Property Boolean[] mArrBoxBoolean;   // 2a
-        @AppSearchDocument.Property boolean[] mArrUnboxBoolean; // 2b
-        @AppSearchDocument.Property byte[][] mArrUnboxByteArr;  // 2b
-        @AppSearchDocument.Property Byte[] mBoxByteArr;         // 2a
-        @AppSearchDocument.Property String[] mArrString;        // 2b
-        @AppSearchDocument.Property Card[] mArrCard;            // 2c
+        @AppSearchDocument.Property
+        Long[] mArrBoxLong;         // 2a
+        @AppSearchDocument.Property
+        long[] mArrUnboxLong;       // 2b
+        @AppSearchDocument.Property
+        Integer[] mArrBoxInteger;   // 2a
+        @AppSearchDocument.Property
+        int[] mArrUnboxInt;         // 2a
+        @AppSearchDocument.Property
+        Double[] mArrBoxDouble;     // 2a
+        @AppSearchDocument.Property
+        double[] mArrUnboxDouble;   // 2b
+        @AppSearchDocument.Property
+        Float[] mArrBoxFloat;       // 2a
+        @AppSearchDocument.Property
+        float[] mArrUnboxFloat;     // 2a
+        @AppSearchDocument.Property
+        Boolean[] mArrBoxBoolean;   // 2a
+        @AppSearchDocument.Property
+        boolean[] mArrUnboxBoolean; // 2b
+        @AppSearchDocument.Property
+        byte[][] mArrUnboxByteArr;  // 2b
+        @AppSearchDocument.Property
+        Byte[] mBoxByteArr;         // 2a
+        @AppSearchDocument.Property
+        String[] mArrString;        // 2b
+        @AppSearchDocument.Property
+        Card[] mArrCard;            // 2c
 
         // Single values
-        @AppSearchDocument.Property String mString;        // 3a
-        @AppSearchDocument.Property Long mBoxLong;         // 3a
-        @AppSearchDocument.Property long mUnboxLong;       // 3b
-        @AppSearchDocument.Property Integer mBoxInteger;   // 3a
-        @AppSearchDocument.Property int mUnboxInt;         // 3b
-        @AppSearchDocument.Property Double mBoxDouble;     // 3a
-        @AppSearchDocument.Property double mUnboxDouble;   // 3b
-        @AppSearchDocument.Property Float mBoxFloat;       // 3a
-        @AppSearchDocument.Property float mUnboxFloat;     // 3b
-        @AppSearchDocument.Property Boolean mBoxBoolean;   // 3a
-        @AppSearchDocument.Property boolean mUnboxBoolean; // 3b
-        @AppSearchDocument.Property byte[] mUnboxByteArr;  // 3a
-        @AppSearchDocument.Property Card mCard;            // 3c
+        @AppSearchDocument.Property
+        String mString;        // 3a
+        @AppSearchDocument.Property
+        Long mBoxLong;         // 3a
+        @AppSearchDocument.Property
+        long mUnboxLong;       // 3b
+        @AppSearchDocument.Property
+        Integer mBoxInteger;   // 3a
+        @AppSearchDocument.Property
+        int mUnboxInt;         // 3b
+        @AppSearchDocument.Property
+        Double mBoxDouble;     // 3a
+        @AppSearchDocument.Property
+        double mUnboxDouble;   // 3b
+        @AppSearchDocument.Property
+        Float mBoxFloat;       // 3a
+        @AppSearchDocument.Property
+        float mUnboxFloat;     // 3b
+        @AppSearchDocument.Property
+        Boolean mBoxBoolean;   // 3a
+        @AppSearchDocument.Property
+        boolean mUnboxBoolean; // 3b
+        @AppSearchDocument.Property
+        byte[] mUnboxByteArr;  // 3a
+        @AppSearchDocument.Property
+        Card mCard;            // 3c
 
         @Override
         public boolean equals(Object other) {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java
index 91af24c..925cff2 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java
@@ -40,9 +40,11 @@
 import androidx.appsearch.app.SearchSpec;
 import androidx.appsearch.app.SetSchemaRequest;
 import androidx.appsearch.app.cts.customer.EmailDataClass;
+import androidx.appsearch.app.util.AppSearchTestUtils;
 import androidx.appsearch.localstorage.LocalStorage;
 import androidx.test.core.app.ApplicationProvider;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -53,23 +55,26 @@
 
 public class AppSearchSessionCtsTest {
     private AppSearchSession mDb1;
+    private final String mDbName1 = AppSearchTestUtils.DEFAULT_DATABASE;
     private AppSearchSession mDb2;
+    private final String mDbName2 = AppSearchTestUtils.DB_2;
 
     @Before
     public void setUp() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
+        AppSearchTestUtils.cleanup(context);
+
         mDb1 = checkIsResultSuccess(LocalStorage.createSearchSession(
                 new LocalStorage.SearchContext.Builder(context)
-                        .setDatabaseName("testDb1").build()));
+                        .setDatabaseName(mDbName1).build()));
         mDb2 = checkIsResultSuccess(LocalStorage.createSearchSession(
                 new LocalStorage.SearchContext.Builder(context)
-                        .setDatabaseName("testDb2").build()));
+                        .setDatabaseName(mDbName2).build()));
+    }
 
-        // Remove all documents from any instances that may have been created in the tests.
-        checkIsResultSuccess(
-                mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()));
-        checkIsResultSuccess(
-                mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()));
+    @After
+    public void tearDown() throws Exception {
+        AppSearchTestUtils.cleanup(ApplicationProvider.getApplicationContext());
     }
 
     @Test
@@ -284,7 +289,7 @@
         assertThat(failResult1.isSuccess()).isFalse();
         assertThat(failResult1.getErrorMessage()).contains("Schema is incompatible");
         assertThat(failResult1.getErrorMessage())
-                .contains("Deleted types: [testDb1/builtin:Email]");
+                .contains("Deleted types: [" + mDbName1 + "/builtin:Email]");
 
         // Try to remove the email schema again, which should now work as we set forceOverride to
         // be true.
@@ -307,10 +312,10 @@
                 .setSubject("testPut example")
                 .build();
         AppSearchBatchResult<String, Void> failResult2 = mDb1.putDocuments(
-                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()).get();
+                new PutDocumentsRequest.Builder().addGenericDocument(email2).build()).get();
         assertThat(failResult2.isSuccess()).isFalse();
         assertThat(failResult2.getFailures().get("email2").getErrorMessage())
-                .isEqualTo("Schema type config 'testDb1/builtin:Email' not found");
+                .isEqualTo("Schema type config '" + mDbName1 + "/builtin:Email' not found");
     }
 
     @Test
@@ -363,7 +368,7 @@
         assertThat(failResult1.isSuccess()).isFalse();
         assertThat(failResult1.getErrorMessage()).contains("Schema is incompatible");
         assertThat(failResult1.getErrorMessage())
-                .contains("Deleted types: [testDb1/builtin:Email]");
+                .contains("Deleted types: [" + mDbName1 + "/builtin:Email]");
 
         // Try to remove the email schema again, which should now work as we set forceOverride to
         // be true.
@@ -387,7 +392,7 @@
                 new PutDocumentsRequest.Builder().addGenericDocument(email3).build()).get();
         assertThat(failResult2.isSuccess()).isFalse();
         assertThat(failResult2.getFailures().get("email3").getErrorMessage())
-                .isEqualTo("Schema type config 'testDb1/builtin:Email' not found");
+                .isEqualTo("Schema type config '" + mDbName1 + "/builtin:Email' not found");
 
         // Make sure email in database 2 still present.
         outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
@@ -698,7 +703,7 @@
                 new GenericDocument.Builder<>("uri", "Generic")
                         .setNamespace("document")
                         .setPropertyString("subject", "A commonly used fake word is foo. "
-                                        + "Another nonsense word that’s used a lot is bar")
+                                + "Another nonsense word that’s used a lot is bar")
                         .build();
         checkIsBatchResultSuccess(mDb1.putDocuments(
                 new PutDocumentsRequest.Builder().addGenericDocument(document).build()));
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GlobalSearchSessionCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GlobalSearchSessionCtsTest.java
index cc1a8bf..ad77545 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GlobalSearchSessionCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GlobalSearchSessionCtsTest.java
@@ -35,9 +35,11 @@
 import androidx.appsearch.app.SearchResults;
 import androidx.appsearch.app.SearchSpec;
 import androidx.appsearch.app.SetSchemaRequest;
+import androidx.appsearch.app.util.AppSearchTestUtils;
 import androidx.appsearch.localstorage.LocalStorage;
 import androidx.test.core.app.ApplicationProvider;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -55,21 +57,23 @@
     @Before
     public void setUp() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
+        AppSearchTestUtils.cleanup(context);
+
         mDb1 = checkIsResultSuccess(LocalStorage.createSearchSession(
                 new LocalStorage.SearchContext.Builder(context)
-                        .setDatabaseName("testDb1").build()));
+                        .setDatabaseName(AppSearchTestUtils.DEFAULT_DATABASE).build()));
         mDb2 = checkIsResultSuccess(LocalStorage.createSearchSession(
                 new LocalStorage.SearchContext.Builder(context)
-                        .setDatabaseName("testDb2").build()));
+                        .setDatabaseName(AppSearchTestUtils.DB_2).build()));
 
         mGlobalAppSearchManager = checkIsResultSuccess(LocalStorage.createGlobalSearchSession(
                 new LocalStorage.GlobalSearchContext.Builder(context).build()));
 
-        // Remove all documents from any instances that may have been created in the tests.
-        checkIsResultSuccess(
-                mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()));
-        checkIsResultSuccess(
-                mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        AppSearchTestUtils.cleanup(ApplicationProvider.getApplicationContext());
     }
 
     @Test
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java
index fe176c8..0c2a856 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Context;
+
 import androidx.appsearch.app.AppSearchBatchResult;
 import androidx.appsearch.app.AppSearchResult;
 import androidx.appsearch.app.AppSearchSession;
@@ -25,6 +27,10 @@
 import androidx.appsearch.app.GetByUriRequest;
 import androidx.appsearch.app.SearchResult;
 import androidx.appsearch.app.SearchResults;
+import androidx.appsearch.app.SetSchemaRequest;
+import androidx.appsearch.localstorage.LocalStorage;
+
+import com.google.common.collect.ImmutableList;
 
 import junit.framework.AssertionFailedError;
 
@@ -34,6 +40,24 @@
 
 public class AppSearchTestUtils {
 
+    // List of databases that may be used in tests. Keeping them in a centralized location helps
+    // #cleanup know which databases to clear.
+    public static final String DEFAULT_DATABASE = LocalStorage.DEFAULT_DATABASE_NAME;
+    public static final String DB_1 = "testDb1";
+    public static final String DB_2 = "testDb2";
+
+    public static void cleanup(Context context) throws Exception {
+        List<String> databases = ImmutableList.of(DEFAULT_DATABASE, DB_1, DB_2);
+        for (String database : databases) {
+            AppSearchSession session =
+                    checkIsResultSuccess(
+                            LocalStorage.createSearchSession(new LocalStorage.SearchContext.Builder(
+                                    context).setDatabaseName(database).build()));
+            checkIsResultSuccess(session.setSchema(
+                    new SetSchemaRequest.Builder().setForceOverride(true).build()));
+        }
+    }
+
     public static <V> V checkIsResultSuccess(Future<AppSearchResult<V>> future) throws Exception {
         AppSearchResult<V> result = future.get();
         if (!result.isSuccess()) {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/util/BundleUtilTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/util/BundleUtilTest.java
index 3830b9d..389c3ee 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/util/BundleUtilTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/util/BundleUtilTest.java
@@ -39,7 +39,6 @@
     public void testDeepEquals_self() {
         Bundle one = new Bundle();
         one.putString("a", "a");
-        assertThat(one).isEqualTo(one);
         assertThat(BundleUtil.deepEquals(one, one)).isTrue();
     }
 
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
index 311b515..ffc73249 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
@@ -33,7 +33,7 @@
 public interface AppSearchSession {
 
     /**
-     * Sets the schema being used by documents provided to the {@link #putDocuments} method.
+     * Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
      *
      * <p>The schema provided here is compared to the stored copy of the schema previously supplied
      * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/RemoveByUriRequest.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/RemoveByUriRequest.java
index 4093c8b..ed7cad9 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/RemoveByUriRequest.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/RemoveByUriRequest.java
@@ -45,7 +45,7 @@
         return mNamespace;
     }
 
-    /** Returns the URIs to remove from the namespace. */
+    /** Returns the URIs of documents to remove from the namespace. */
     @NonNull
     public Set<String> getUris() {
         return Collections.unmodifiableSet(mUris);
diff --git a/appsearch/exportToFramework.py b/appsearch/exportToFramework.py
index 33f9429..1a646e2 100755
--- a/appsearch/exportToFramework.py
+++ b/appsearch/exportToFramework.py
@@ -32,7 +32,7 @@
 JETPACK_IMPL_TEST_ROOT = 'local-storage/src/androidTest/java/androidx/appsearch'
 
 # Framework paths relative to frameworks/base/apex/appsearch
-FRAMEWORK_API_ROOT = 'framework/java/android/app/appsearch'
+FRAMEWORK_API_ROOT = 'framework/java/external/android/app/appsearch'
 FRAMEWORK_API_TEST_ROOT = (
         '../../core/tests/coretests/src/'
         'android/app/appsearch/external')
@@ -52,18 +52,10 @@
         self._jetpack_appsearch_root = jetpack_appsearch_root
         self._framework_appsearch_root = framework_appsearch_root
 
-    def _PruneDir(self, dir_to_prune, allow_list=None):
-        all_files = []
+    def _PruneDir(self, dir_to_prune):
         for walk_path, walk_folders, walk_files in os.walk(dir_to_prune):
             for walk_filename in walk_files:
                 abs_path = os.path.join(walk_path, walk_filename)
-                all_files.append(abs_path)
-
-        for abs_path in all_files:
-            rel_path = os.path.relpath(abs_path, dir_to_prune)
-            if allow_list and rel_path in allow_list:
-                print('Prune: skip "%s"' % abs_path)
-            else:
                 print('Prune: remove "%s"' % abs_path)
                 os.remove(abs_path)
 
@@ -137,14 +129,7 @@
         api_test_dest_dir = os.path.join(self._framework_appsearch_root, FRAMEWORK_API_TEST_ROOT)
 
         # Prune existing files
-        self._PruneDir(api_dest_dir, allow_list=[
-            'AppSearchBatchResult.java',
-            'AppSearchManager.java',
-            'AppSearchManagerFrameworkInitializer.java',
-            'AppSearchResult.java',
-            'IAppSearchManager.aidl',
-            'SearchResults.java',
-        ])
+        self._PruneDir(api_dest_dir)
         self._PruneDir(api_test_dest_dir)
 
         # Copy api classes. We can't use _TransformAndCopyFolder here because we
diff --git a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
index cad539b..101356e 100644
--- a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
+++ b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
@@ -36,6 +36,7 @@
 import com.google.android.icing.proto.SearchSpecProto;
 import com.google.android.icing.proto.StringIndexingConfig;
 import com.google.android.icing.proto.TermMatchType;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 import org.junit.Before;
@@ -45,9 +46,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 public class AppSearchImplTest {
     @Rule
@@ -279,16 +278,16 @@
     @Test
     public void testOptimize() throws Exception {
         // Insert schema
-        Set<AppSearchSchema> schemas =
-                Collections.singleton(new AppSearchSchema.Builder("type").build());
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
         mAppSearchImpl.setSchema("database", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
 
         // Insert enough documents.
         for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
                 + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
             GenericDocument document =
-                    new GenericDocument.Builder("uri" + i, "type").setNamespace(
+                    new GenericDocument.Builder<>("uri" + i, "type").setNamespace(
                             "namespace").build();
             mAppSearchImpl.putDocument("database", document);
         }
@@ -327,20 +326,19 @@
                 SearchSpecProto.newBuilder().setQuery("");
 
         // Insert schema
-        Set<AppSearchSchema> schemas =
-                Collections.singleton(new AppSearchSchema.Builder("type").build());
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
         mAppSearchImpl.setSchema("database", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
 
         // Insert document
-        GenericDocument document = new GenericDocument.Builder("uri", "type").setNamespace(
+        GenericDocument document = new GenericDocument.Builder<>("uri", "type").setNamespace(
                 "namespace").build();
         mAppSearchImpl.putDocument("database", document);
 
         // Rewrite SearchSpec
-        mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(searchSpecProto,
-                Collections.singleton(
-                        "database"));
+        mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(
+                searchSpecProto, Collections.singleton("database"));
         assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type");
         assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace");
     }
@@ -351,20 +349,20 @@
                 SearchSpecProto.newBuilder().setQuery("");
 
         // Insert schema
-        Set<AppSearchSchema> schemas = Set.of(
+        List<AppSearchSchema> schemas = ImmutableList.of(
                 new AppSearchSchema.Builder("typeA").build(),
                 new AppSearchSchema.Builder("typeB").build());
         mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
         mAppSearchImpl.setSchema("database2", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
 
         // Insert documents
-        GenericDocument document1 = new GenericDocument.Builder("uri", "typeA").setNamespace(
+        GenericDocument document1 = new GenericDocument.Builder<>("uri", "typeA").setNamespace(
                 "namespace").build();
         mAppSearchImpl.putDocument("database1", document1);
 
-        GenericDocument document2 = new GenericDocument.Builder("uri", "typeB").setNamespace(
+        GenericDocument document2 = new GenericDocument.Builder<>("uri", "typeB").setNamespace(
                 "namespace").build();
         mAppSearchImpl.putDocument("database2", document2);
 
@@ -417,11 +415,11 @@
 
     @Test
     public void testSetSchema() throws Exception {
-        Set<AppSearchSchema> schemas =
-                Collections.singleton(new AppSearchSchema.Builder("Email").build());
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("Email").build());
         // Set schema Email to AppSearch database1
         mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto = SchemaProto.newBuilder()
@@ -437,19 +435,22 @@
 
     @Test
     public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
                         "schema1").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.singleton("schema1"), /*forceOverride=*/ false);
+                Collections.singletonList("schema1"), /*forceOverride=*/ false);
 
         // "schema1" is platform hidden now
         assertThat(mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
                 "database")).containsExactly("database/schema1");
 
         // Add a new schema, and include the already-existing "schema1"
-        mAppSearchImpl.setSchema("database", Set.of(new AppSearchSchema.Builder(
-                        "schema1").build(), new AppSearchSchema.Builder(
-                        "schema2").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.singleton("schema1"), /*forceOverride=*/ false);
+        mAppSearchImpl.setSchema(
+                "database",
+                ImmutableList.of(
+                        new AppSearchSchema.Builder("schema1").build(),
+                        new AppSearchSchema.Builder("schema2").build()),
+                /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("schema1"),
+                /*forceOverride=*/ false);
 
         // Check that "schema1" is still platform hidden, but "schema2" is the default platform
         // visible.
@@ -459,12 +460,12 @@
 
     @Test
     public void testRemoveSchema() throws Exception {
-        Set<AppSearchSchema> schemas = new HashSet<>();
-        schemas.add(new AppSearchSchema.Builder("Email").build());
-        schemas.add(new AppSearchSchema.Builder("Document").build());
+        List<AppSearchSchema> schemas = ImmutableList.of(
+                new AppSearchSchema.Builder("Email").build(),
+                new AppSearchSchema.Builder("Document").build());
         // Set schema Email and Document to AppSearch database1
         mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto = SchemaProto.newBuilder()
@@ -479,19 +480,20 @@
         assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
                 .containsExactlyElementsIn(expectedTypes);
 
-        final Set<AppSearchSchema> finalSchemas = Collections.singleton(new AppSearchSchema.Builder(
-                "Email").build());
+        final List<AppSearchSchema> finalSchemas = Collections.singletonList(
+                new AppSearchSchema.Builder(
+                        "Email").build());
         // Check the incompatible error has been thrown.
         AppSearchException e = assertThrows(AppSearchException.class, () ->
                 mAppSearchImpl.setSchema("database1",
                         finalSchemas, /*schemasNotPlatformSurfaceable=*/
-                        Collections.emptySet(), /*forceOverride=*/ false));
+                        Collections.emptyList(), /*forceOverride=*/ false));
         assertThat(e).hasMessageThat().contains("Schema is incompatible");
         assertThat(e).hasMessageThat().contains("Deleted types: [database1/Document]");
 
         // ForceOverride to delete.
         mAppSearchImpl.setSchema("database1", finalSchemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ true);
+                Collections.emptyList(), /*forceOverride=*/ true);
 
         // Check Document schema is removed.
         expectedProto = SchemaProto.newBuilder()
@@ -508,15 +510,15 @@
     @Test
     public void testRemoveSchema_differentDataBase() throws Exception {
         // Create schemas
-        Set<AppSearchSchema> schemas = new HashSet<>();
-        schemas.add(new AppSearchSchema.Builder("Email").build());
-        schemas.add(new AppSearchSchema.Builder("Document").build());
+        List<AppSearchSchema> schemas = ImmutableList.of(
+                new AppSearchSchema.Builder("Email").build(),
+                new AppSearchSchema.Builder("Document").build());
 
         // Set schema Email and Document to AppSearch database1 and 2
         mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
         mAppSearchImpl.setSchema("database2", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto = SchemaProto.newBuilder()
@@ -534,9 +536,9 @@
                 .containsExactlyElementsIn(expectedTypes);
 
         // Save only Email to database1 this time.
-        schemas = Collections.singleton(new AppSearchSchema.Builder("Email").build());
+        schemas = Collections.singletonList(new AppSearchSchema.Builder("Email").build());
         mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ true);
+                Collections.emptyList(), /*forceOverride=*/ true);
 
         // Create expected schemaType list, database 1 should only contain Email but database 2
         // remains in same.
@@ -557,9 +559,9 @@
 
     @Test
     public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
                         "schema1").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.singleton("schema1"), /*forceOverride=*/ false);
+                Collections.singletonList("schema1"), /*forceOverride=*/ false);
 
         // "schema1" is platform hidden now
         assertThat(mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
@@ -567,8 +569,8 @@
 
         // Remove "schema1" by force overriding
         mAppSearchImpl.setSchema("database",
-                Collections.emptySet(), /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ true);
+                Collections.emptyList(), /*schemasNotPlatformSurfaceable=*/
+                Collections.emptyList(), /*forceOverride=*/ true);
 
         // Check that "schema1" is no longer considered platform hidden
         assertThat(
@@ -577,9 +579,9 @@
 
         // Add "schema1" back, it gets default visibility settings which means it's not platform
         // hidden.
-        mAppSearchImpl.setSchema("database", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
                         "schema1").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
         assertThat(
                 mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
                         "database")).isEmpty();
@@ -587,9 +589,9 @@
 
     @Test
     public void testSetSchema_defaultPlatformVisible() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
                         "Schema").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
         assertThat(
                 mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
                         "database")).isEmpty();
@@ -597,9 +599,9 @@
 
     @Test
     public void testSetSchema_platformHidden() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
                         "Schema").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.singleton("Schema"), /*forceOverride=*/ false);
+                Collections.singletonList("Schema"), /*forceOverride=*/ false);
         assertThat(mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
                 "database")).containsExactly("database/Schema");
     }
@@ -609,9 +611,9 @@
         // Nothing exists yet
         assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isFalse();
 
-        mAppSearchImpl.setSchema("database", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
                         "Schema").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
         assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isTrue();
 
         assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "UnknownSchema")).isFalse();
@@ -624,16 +626,16 @@
                 VisibilityStore.DATABASE_NAME);
 
         // Has database1
-        mAppSearchImpl.setSchema("database1", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database1", Collections.singletonList(new AppSearchSchema.Builder(
                         "schema").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
         assertThat(mAppSearchImpl.getDatabasesLocked()).containsExactly(
                 VisibilityStore.DATABASE_NAME, "database1");
 
         // Has both databases
-        mAppSearchImpl.setSchema("database2", Collections.singleton(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("database2", Collections.singletonList(new AppSearchSchema.Builder(
                         "schema").build()), /*schemasNotPlatformSurfaceable=*/
-                Collections.emptySet(), /*forceOverride=*/ false);
+                Collections.emptyList(), /*forceOverride=*/ false);
         assertThat(mAppSearchImpl.getDatabasesLocked()).containsExactly(
                 VisibilityStore.DATABASE_NAME, "database1", "database2");
     }
diff --git a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java
index d4a30f4..f3732a1 100644
--- a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java
+++ b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java
@@ -18,13 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.common.collect.ImmutableSet;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
 import java.util.Collections;
-import java.util.Set;
 
 public class VisibilityStoreTest {
 
@@ -41,20 +42,28 @@
 
     @Test
     public void testSetVisibility() throws Exception {
-        mVisibilityStore.setVisibility(
-                "database", /*schemasNotPlatformSurfaceable=*/ Set.of("schema1", "schema2"));
+        mVisibilityStore.setVisibility("database",
+                /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"));
         assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database"))
-                .containsExactly("schema1", "schema2");
+                .containsExactlyElementsIn(ImmutableSet.of("schema1", "schema2"));
 
         // New .setVisibility() call completely overrides previous visibility settings. So
-        // "schema1" isn't preserved.
-        mVisibilityStore.setVisibility(
-                "database", /*schemasNotPlatformSurfaceable=*/ Set.of("schema1", "schema3"));
+        // "schema2" isn't preserved.
+        mVisibilityStore.setVisibility("database",
+                /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema3"));
         assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database"))
-                .containsExactly("schema1", "schema3");
+                .containsExactlyElementsIn(ImmutableSet.of("schema1", "schema3"));
 
         mVisibilityStore.setVisibility(
                 "database", /*schemasNotPlatformSurfaceable=*/ Collections.emptySet());
         assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database")).isEmpty();
     }
+
+    @Test
+    public void testEmptyDatabase() throws Exception {
+        mVisibilityStore.setVisibility(LocalStorage.DEFAULT_DATABASE_NAME,
+                /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"));
+        assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable(""))
+                .containsExactlyElementsIn(ImmutableSet.of("schema1", "schema2"));
+    }
 }
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index 2ec2d82..9c25f4b 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -61,6 +61,7 @@
 import com.google.android.icing.proto.StatusProto;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -232,17 +233,19 @@
      *                                      which do not comply with the new schema will be deleted.
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    public void setSchema(@NonNull String databaseName, @NonNull Set<AppSearchSchema> schemas,
-            @NonNull Set<String> schemasNotPlatformSurfaceable,
+    public void setSchema(
+            @NonNull String databaseName,
+            @NonNull List<AppSearchSchema> schemas,
+            @NonNull List<String> schemasNotPlatformSurfaceable,
             boolean forceOverride) throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
             SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
 
             SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
-            for (AppSearchSchema schema : schemas) {
+            for (int i = 0; i < schemas.size(); i++) {
                 SchemaTypeConfigProto schemaTypeProto =
-                        SchemaToProtoConverter.toSchemaTypeConfigProto(schema);
+                        SchemaToProtoConverter.toSchemaTypeConfigProto(schemas.get(i));
                 newSchemaBuilder.addTypes(schemaTypeProto);
             }
 
@@ -281,8 +284,9 @@
             String databasePrefix = getDatabasePrefix(databaseName);
             Set<String> qualifiedSchemasNotPlatformSurfaceable =
                     new ArraySet<>(schemasNotPlatformSurfaceable.size());
-            for (String schema : schemasNotPlatformSurfaceable) {
-                qualifiedSchemasNotPlatformSurfaceable.add(databasePrefix + schema);
+            for (int i = 0; i < schemasNotPlatformSurfaceable.size(); i++) {
+                qualifiedSchemasNotPlatformSurfaceable.add(
+                        databasePrefix + schemasNotPlatformSurfaceable.get(i));
             }
             mVisibilityStoreLocked.setVisibility(databaseName,
                     qualifiedSchemasNotPlatformSurfaceable);
@@ -306,11 +310,11 @@
      *
      * <p>This method belongs to query group.
      *
-     * @param databaseName  The name of the database where this schema lives.
+     * @param databaseName The name of the database where this schema lives.
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
-    public Set<AppSearchSchema> getSchema(@NonNull String databaseName) throws AppSearchException {
+    public List<AppSearchSchema> getSchema(@NonNull String databaseName) throws AppSearchException {
         SchemaProto fullSchema;
         mReadWriteLock.readLock().lock();
         try {
@@ -319,7 +323,7 @@
             mReadWriteLock.readLock().unlock();
         }
 
-        Set<AppSearchSchema> result = new ArraySet<>();
+        List<AppSearchSchema> result = new ArrayList<>();
         for (int i = 0; i < fullSchema.getTypesCount(); i++) {
             String typeDatabase = getDatabaseName(fullSchema.getTypes(i).getSchemaType());
             if (!databaseName.equals(typeDatabase)) {
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index 42223be..3426cfe 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -23,6 +23,7 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.appsearch.app.AppSearchResult;
 import androidx.appsearch.app.AppSearchSession;
@@ -41,12 +42,22 @@
  * An AppSearch storage system which stores data locally in the app's storage space using a bundled
  * version of the search native library.
  *
+ * <p>The search native library is an on-device searching library that allows apps to define
+ * {@link androidx.appsearch.app.AppSearchSchema}s, save and query a variety of
+ * {@link androidx.appsearch.annotation.AppSearchDocument}s. The library needs to be initialized
+ * before using, which will create a folder to save data in the app's storage space.
+ *
  * <p>Queries are executed multi-threaded, but a single thread is used for mutate requests (put,
  * delete, etc..).
  */
 public class LocalStorage {
-    /** The default empty database name.*/
-    private static final String DEFAULT_DATABASE_NAME = "";
+    /**
+     * The default empty database name.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @VisibleForTesting
+    public static final String DEFAULT_DATABASE_NAME = "";
 
     private static volatile ListenableFuture<AppSearchResult<LocalStorage>> sInstance;
 
@@ -82,7 +93,9 @@
             }
 
             /**
-             * Sets the name of the database to create or open.
+             * Sets the name of the database associated with {@link AppSearchSession}.
+             *
+             * <p>{@link AppSearchSession} will create or open a database under the given name.
              *
              * <p>Databases with different names are fully separate with distinct types, namespaces,
              * and data.
@@ -160,8 +173,11 @@
     /**
      * Opens a new {@link AppSearchSession} on this storage.
      *
-     * <p>If the system is not initialized, it will be initialized using the provided
-     * {@code context}.
+     * <p>This process requires a native search library. If it's not created, the initialization
+     * process will create one.
+     *
+     * @param context The {@link SearchContext} contains all information to create a new
+     *                {@link AppSearchSession}
      */
     @NonNull
     public static ListenableFuture<AppSearchResult<AppSearchSession>> createSearchSession(
@@ -182,8 +198,11 @@
     /**
      * Opens a new {@link GlobalSearchSession} on this storage.
      *
-     * <p>If the system is not initialized, it will be initialized using the provided
-     * {@code context}.
+     * <p>This process requires a native search library. If it's not created, the initialization
+     * process will create one.
+     *
+     * @param context The {@link GlobalSearchContext} contains all information to create a new
+     *                {@link GlobalSearchSession}
      * @hide
      */
     @NonNull
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
index 12b25fa..f9440ec 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
@@ -31,10 +31,13 @@
 import androidx.appsearch.app.SearchSpec;
 import androidx.appsearch.app.SetSchemaRequest;
 import androidx.appsearch.localstorage.util.FutureUtil;
+import androidx.collection.ArraySet;
 import androidx.core.util.Preconditions;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
@@ -67,8 +70,10 @@
         return execute(() -> {
             try {
                 mAppSearchImpl.setSchema(
-                        mDatabaseName, request.getSchemas(),
-                        request.getSchemasNotPlatformSurfaceable(), request.isForceOverride());
+                        mDatabaseName,
+                        new ArrayList<>(request.getSchemas()),
+                        new ArrayList<>(request.getSchemasNotPlatformSurfaceable()),
+                        request.isForceOverride());
                 return AppSearchResult.newSuccessfulResult(/*value=*/ null);
             } catch (Throwable t) {
                 return throwableToFailedResult(t);
@@ -81,7 +86,8 @@
     public ListenableFuture<AppSearchResult<Set<AppSearchSchema>>> getSchema() {
         return execute(() -> {
             try {
-                return AppSearchResult.newSuccessfulResult(mAppSearchImpl.getSchema(mDatabaseName));
+                List<AppSearchSchema> schemas = mAppSearchImpl.getSchema(mDatabaseName);
+                return AppSearchResult.newSuccessfulResult(new ArraySet<>(schemas));
             } catch (Throwable t) {
                 return throwableToFailedResult(t);
             }
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java
index a95231f..09c1cc4d 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java
@@ -65,7 +65,11 @@
     static final String DATABASE_NAME = "$$__AppSearch__Database";
 
     // Namespace of documents that contain visibility settings
-    private static final String NAMESPACE = "namespace";
+    private static final String NAMESPACE = GenericDocument.DEFAULT_NAMESPACE;
+
+    // Prefix to add to all visibility document uri's. IcingSearchEngine doesn't allow empty uri's.
+    private static final String URI_PREFIX = "uri:";
+
     private final AppSearchImpl mAppSearchImpl;
 
     // The map contains schemas that are platform-hidden for each database. All schemas in the map
@@ -97,7 +101,7 @@
         if (!mAppSearchImpl.hasSchemaTypeLocked(DATABASE_NAME, SCHEMA_TYPE)) {
             // Schema type doesn't exist yet. Add it.
             mAppSearchImpl.setSchema(DATABASE_NAME,
-                    Collections.singleton(new AppSearchSchema.Builder(SCHEMA_TYPE)
+                    Collections.singletonList(new AppSearchSchema.Builder(SCHEMA_TYPE)
                             .addProperty(new AppSearchSchema.PropertyConfig.Builder(
                                     NOT_PLATFORM_SURFACEABLE_PROPERTY)
                                     .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
@@ -105,7 +109,7 @@
                                             AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
                                     .build())
                             .build()),
-                    /*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
+                    /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                     /*forceOverride=*/ false);
         }
 
@@ -119,7 +123,7 @@
             try {
                 // Note: We use the other clients' database names as uris
                 GenericDocument document = mAppSearchImpl.getDocument(
-                        DATABASE_NAME, NAMESPACE, /*uri=*/ database);
+                        DATABASE_NAME, NAMESPACE, /*uri=*/ addUriPrefix(database));
 
                 String[] schemas = document.getPropertyStringArray(
                         NOT_PLATFORM_SURFACEABLE_PROPERTY);
@@ -155,7 +159,7 @@
 
         // Persist the document
         GenericDocument.Builder visibilityDocument = new GenericDocument.Builder(
-                /*uri=*/ databaseName, SCHEMA_TYPE)
+                /*uri=*/ addUriPrefix(databaseName), SCHEMA_TYPE)
                 .setNamespace(NAMESPACE);
         if (!schemasNotPlatformSurfaceable.isEmpty()) {
             visibilityDocument.setPropertyString(NOT_PLATFORM_SURFACEABLE_PROPERTY,
@@ -195,4 +199,14 @@
         mNotPlatformSurfaceableMap.clear();
         initialize();
     }
+
+    /**
+     * Adds a uri prefix to create a visibility store document's uri.
+     *
+     * @param uri Non-prefixed uri
+     * @return Prefixed uri
+     */
+    private static String addUriPrefix(String uri) {
+        return URI_PREFIX + uri;
+    }
 }
diff --git a/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh b/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
index df929f4..b33734b 100755
--- a/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
+++ b/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
@@ -94,6 +94,19 @@
     fi
 }
 
+# Disable CPU hotpluging by killing mpdecision service via ctl.stop system property.
+# This helper checks the state and existence of the mpdecision service via init.svc.
+# Possible values from init.svc: "stopped", "stopping", "running", "restarting"
+function_stop_mpdecision() {
+    MPDECISION_STATUS=`getprop init.svc.mpdecision`
+    while [ "$MPDECISION_STATUS" == "running" ] || [ "$MPDECISION_STATUS" == "restarting" ]; do
+        setprop ctl.stop mpdecision
+        # Give initrc some time to kill the mpdecision service.
+        sleep 0.1
+        MPDECISION_STATUS=`getprop init.svc.mpdecision`
+    done
+}
+
 # Find the min or max (little vs big) of CPU max frequency, and lock cores of the selected type to
 # an available frequency that's >= $CPU_TARGET_FREQ_PERCENT% of max. Disable other cores.
 function_lock_cpu() {
@@ -111,10 +124,14 @@
     disableIndices=''
     cpu=0
 
+    # Stop mpdecision (CPU hotplug service) if it exists. Not available on all devices.
+    function_stop_mpdecision
+
     # Loop through all available cores; We have to check by the parent folder
     # "cpu#" instead of cpu#/online or cpu#/cpufreq directly, since they may
     # not be accessible yet.
     while [ -d ${CPU_BASE}/cpu${cpu} ]; do
+
         # Try to enable core, so we can find its frequencies.
         # Note: In cases where the online file is inaccessible, it represents a
         # core which cannot be turned off, so we simply assume it is enabled if
diff --git a/benchmark/integration-tests/macrobenchmark-target/build.gradle b/benchmark/integration-tests/macrobenchmark-target/build.gradle
index 1e72999..4de1d5d 100644
--- a/benchmark/integration-tests/macrobenchmark-target/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark-target/build.gradle
@@ -22,8 +22,18 @@
     id("kotlin-android")
 }
 
+android {
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
+        }
+    }
+}
+
 dependencies {
-    api(KOTLIN_STDLIB)
+    implementation(KOTLIN_STDLIB)
     implementation(CONSTRAINT_LAYOUT, { transitive = true })
     implementation("androidx.arch.core:core-runtime:2.1.0")
     implementation("androidx.appcompat:appcompat:1.2.0")
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index f103e683..58d1f37 100644
--- a/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -13,16 +13,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="androidx.benchmark.integration.macrobenchmark.target">
 
     <application
+        android:label="Jetpack Benchmark Macrobenchmark Target"
         android:allowBackup="false"
         android:supportsRtl="true"
-        android:theme="@style/Theme.AppCompat">
+        android:theme="@style/Theme.AppCompat"
+        tools:ignore="MissingApplicationIcon">
 
         <!--
-        The activity needs to be exported so the Macro Benchmark Sample can discover activities
+        Activities need to be exported so the macrobenchmark can discover them
         under the new package visibility changes for Android 11.
          -->
         <activity
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/RecyclerViewActivity.kt b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/RecyclerViewActivity.kt
index bb07d9f..f5d6a2a 100644
--- a/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/RecyclerViewActivity.kt
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/RecyclerViewActivity.kt
@@ -33,10 +33,8 @@
         recycler.adapter = adapter
     }
 
-    private fun entries(size: Int): List<Entry> {
-        return IntRange(0, size).map {
-            Entry("Item $it")
-        }
+    private fun entries(size: Int) = List(size) {
+        Entry("Item $it")
     }
 
     companion object {
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/TrivialStartupActivity.kt b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/TrivialStartupActivity.kt
index 5cf2081..52792d1 100644
--- a/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/TrivialStartupActivity.kt
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/benchmark/integration/macrobenchmark/target/TrivialStartupActivity.kt
@@ -16,11 +16,8 @@
 
 package androidx.benchmark.integration.macrobenchmark.target
 
-import android.app.Activity
-import android.os.Build
 import android.os.Bundle
 import android.widget.TextView
-import androidx.annotation.RequiresApi
 import androidx.appcompat.app.AppCompatActivity
 
 class TrivialStartupActivity : AppCompatActivity() {
@@ -30,18 +27,5 @@
 
         val notice = findViewById<TextView>(R.id.txtNotice)
         notice.setText(R.string.app_notice)
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-            KitkatActivityCompat.reportFullyDrawn(this)
-        }
-    }
-}
-
-/**
- * Wrapper avoids UnsafeApiCall lint
- */
-@RequiresApi(19)
-object KitkatActivityCompat {
-    fun reportFullyDrawn(activity: Activity) {
-        activity.reportFullyDrawn()
     }
 }
diff --git a/benchmark/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_row.xml b/benchmark/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_row.xml
index fa8493f..7e77ae3 100644
--- a/benchmark/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_row.xml
+++ b/benchmark/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_row.xml
@@ -13,46 +13,32 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<androidx.cardview.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/card"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="64dp"
-    android:padding="8dp">
-
-    <androidx.cardview.widget.CardView
-        android:id="@+id/card"
+    android:layout_margin="8dp">
+    <androidx.appcompat.widget.LinearLayoutCompat
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:padding="8dp">
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
 
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/content"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            tools:text="Sample text" />
+        <Space
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
 
-            <androidx.appcompat.widget.AppCompatCheckBox
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="end"
-                android:layout_marginEnd="16dp"
-                android:layout_marginRight="16dp"
-                android:layout_marginTop="8dp"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-            <androidx.appcompat.widget.AppCompatTextView
-                android:id="@+id/content"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="16dp"
-                android:layout_marginStart="16dp"
-                android:layout_marginTop="8dp"
-                android:padding="8dp"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent"
-                tools:text="Sample text" />
-        </androidx.constraintlayout.widget.ConstraintLayout>
-    </androidx.cardview.widget.CardView>
-</LinearLayout>
\ No newline at end of file
+        <androidx.appcompat.widget.AppCompatCheckBox
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp" />
+    </androidx.appcompat.widget.LinearLayoutCompat>
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/JankMetricValidation.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/JankMetricValidation.kt
index 89e3b20..8d2e9a2 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/JankMetricValidation.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/JankMetricValidation.kt
@@ -70,8 +70,10 @@
             val recycler = device.findObject(By.res(PACKAGE_NAME, RESOURCE_ID))
             // Setting a gesture margin is important otherwise gesture nav is triggered.
             recycler.setGestureMargin(device.displayWidth / 5)
-            recycler.fling(Direction.DOWN)
-            device.waitForIdle()
+            for (i in 1..10) {
+                recycler.scroll(Direction.DOWN, 2f)
+                device.waitForIdle()
+            }
         }
     }
 
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
index d94f6c0..aee2877 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -134,6 +134,10 @@
         "jank_percentile_90" to "frameTime90thPercentileMs",
         "jank_percentile_95" to "frameTime95thPercentileMs",
         "jank_percentile_99" to "frameTime99thPercentileMs",
+        "gpu_jank_percentile_50" to "gpuFrameTime50thPercentileMs",
+        "gpu_jank_percentile_90" to "gpuFrameTime90thPercentileMs",
+        "gpu_jank_percentile_95" to "gpuFrameTime95thPercentileMs",
+        "gpu_jank_percentile_99" to "gpuFrameTime99thPercentileMs",
         "missed_vsync" to "vsyncMissedFrameCount",
         "deadline_missed" to "deadlineMissedFrameCount",
         "janky_frames_count" to "jankyFrameCount",
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/XmlTestConfigVerificationTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/XmlTestConfigVerificationTest.kt
deleted file mode 100644
index 43a6fe0..0000000
--- a/buildSrc-tests/src/test/kotlin/androidx/build/XmlTestConfigVerificationTest.kt
+++ /dev/null
@@ -1,73 +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.build
-
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.xml.sax.InputSource
-import org.xml.sax.helpers.DefaultHandler
-import java.io.StringReader
-import javax.xml.parsers.SAXParserFactory
-
-/**
- * Simple check that the test config templates are able to be parsed as valid xml.
- */
-@RunWith(JUnit4::class)
-class XmlTestConfigVerificationTest {
-
-    @Test
-    fun testValidTestConfigXml_TEMPLATE() {
-        val parser = SAXParserFactory.newInstance().newSAXParser()
-        parser.parse(
-            InputSource(StringReader(TEMPLATE.replace("TEST_BLOCK", FULL_TEST))),
-            DefaultHandler()
-        )
-    }
-
-    @Test
-    fun testValidTestConfigXml_SELF_INSTRUMENTING_TEMPLATE() {
-        val parser = SAXParserFactory.newInstance().newSAXParser()
-        parser.parse(
-            InputSource(
-                StringReader(
-                    SELF_INSTRUMENTING_TEMPLATE.replace(
-                        "TEST_BLOCK",
-                        DEPENDENT_TESTS
-                    )
-                )
-            ),
-            DefaultHandler()
-        )
-    }
-
-    @Test
-    fun testValidTestConfigXml_MEDIA_TEMPLATE() {
-        val parser = SAXParserFactory.newInstance().newSAXParser()
-        parser.parse(
-            InputSource(
-                StringReader(
-                    MEDIA_TEMPLATE.replace(
-                        "INSTRUMENTATION_ARGS",
-                        CLIENT_PREVIOUS + SERVICE_PREVIOUS
-                    )
-                )
-            ),
-            DefaultHandler()
-        )
-    }
-}
\ No newline at end of file
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
index c187d48..cef9eac 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
@@ -48,7 +48,6 @@
     val tmpFolder2 = TemporaryFolder()
 
     private lateinit var root: Project
-    private lateinit var root2: Project
     private lateinit var p1: Project
     private lateinit var p2: Project
     private lateinit var p3: Project
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/XmlTestConfigVerificationTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/XmlTestConfigVerificationTest.kt
new file mode 100644
index 0000000..d35f5e8
--- /dev/null
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/XmlTestConfigVerificationTest.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.build.testConfiguration
+
+import org.hamcrest.CoreMatchers
+import org.hamcrest.MatcherAssert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.xml.sax.InputSource
+import org.xml.sax.helpers.DefaultHandler
+import java.io.StringReader
+import javax.xml.parsers.SAXParserFactory
+
+/**
+ * Simple check that the test config templates are able to be parsed as valid xml.
+ */
+@RunWith(JUnit4::class)
+class XmlTestConfigVerificationTest {
+
+    private lateinit var builder: ConfigBuilder
+    private lateinit var mediaBuilder: MediaConfigBuilder
+
+    @Before
+    fun init() {
+        builder = ConfigBuilder()
+        builder.isBenchmark(false)
+            .applicationId("com.androidx.placeholder.Placeholder")
+            .isPostsubmit(true)
+            .minSdk("15")
+            .tag("placeholder_tag")
+            .testApkName("placeholder.apk")
+            .testRunner("com.example.Runner")
+        mediaBuilder = MediaConfigBuilder()
+        mediaBuilder.clientApplicationId("com.androidx.client.Placeholder")
+            .clientApkName("clientPlaceholder.apk")
+            .serviceApplicationId("com.androidx.service.Placeholder")
+            .serviceApkName("servicePlaceholder.apk")
+            .minSdk("15")
+            .tag("placeholder_tag")
+            .testRunner("com.example.Runner")
+            .isClientPrevious(true)
+            .isServicePrevious(false)
+    }
+
+    @Test
+    fun testAgainstGoldenDefault() {
+        MatcherAssert.assertThat(
+            builder.build(),
+            CoreMatchers.`is`(goldenDefaultConfig)
+        )
+    }
+
+    @Test
+    fun testAgainstMediaGoldenDefault() {
+        MatcherAssert.assertThat(
+            mediaBuilder.build(),
+            CoreMatchers.`is`(goldenMediaDefaultConfig)
+        )
+    }
+
+    @Test
+    fun testValidTestConfigXml_default() {
+        validate(builder.build())
+    }
+
+    @Test
+    fun testValidTestConfigXml_benchmarkTrue() {
+        builder.isBenchmark(true)
+        validate(builder.build())
+    }
+
+    @Test
+    fun testValidTestConfigXml_withAppApk() {
+        builder.appApkName("Placeholder.apk")
+        validate(builder.build())
+    }
+
+    @Test
+    fun testValidTestConfigXml_presubmitWithAppApk() {
+        builder.isPostsubmit(false)
+            .appApkName("Placeholder.apk")
+        validate(builder.build())
+    }
+
+    @Test
+    fun testValidTestConfigXml_presubmit() {
+        builder.isPostsubmit(false)
+        validate(builder.build())
+    }
+
+    @Test
+    fun testValidTestConfigXml_presubmitBenchmark() {
+        builder.isPostsubmit(false)
+            .isBenchmark(true)
+        validate(builder.build())
+    }
+
+    @Test
+    fun testValidMediaConfigXml_default() {
+        validate(mediaBuilder.build())
+    }
+
+    @Test
+    fun testValidMediaConfigXml_presubmit() {
+        mediaBuilder.isPostsubmit(false)
+        validate(mediaBuilder.build())
+    }
+
+    private fun validate(xml: String) {
+        val parser = SAXParserFactory.newInstance().newSAXParser()
+        return parser.parse(
+            InputSource(
+                StringReader(
+                    xml
+                )
+            ),
+            DefaultHandler()
+        )
+    }
+}
+
+private val goldenDefaultConfig = """
+    <?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.-->
+    <configuration description="Runs tests for the module">
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
+    <option name="min-api-level" value="15" />
+    </object>
+    <option name="test-suite-tag" value="placeholder_tag" />
+    <option name="config-descriptor:metadata" key="applicationId" value="com.androidx.placeholder.Placeholder" />
+    <option name="wifi:disable" value="true" />
+    <include name="google/unbundled/common/setup" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="placeholder.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="runner" value="com.example.Runner"/>
+    <option name="package" value="com.androidx.placeholder.Placeholder" />
+    </test>
+    </configuration>
+""".trimIndent()
+
+private val goldenMediaDefaultConfig = """
+    <?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.-->
+    <configuration description="Runs tests for the module">
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
+    <option name="min-api-level" value="15" />
+    </object>
+    <option name="test-suite-tag" value="placeholder_tag" />
+    <option name="test-suite-tag" value="media_compat" />
+    <option name="config-descriptor:metadata" key="applicationId" value="com.androidx.client.Placeholder;com.androidx.service.Placeholder" />
+    <option name="wifi:disable" value="true" />
+    <include name="google/unbundled/common/setup" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="clientPlaceholder.apk" />
+    <option name="test-file-name" value="servicePlaceholder.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="runner" value="com.example.Runner"/>
+    <option name="package" value="com.androidx.client.Placeholder" />
+    <option name="instrumentation-arg" key="client_version" value="previous" />
+    <option name="instrumentation-arg" key="service_version" value="tot" />
+    </test>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="runner" value="com.example.Runner"/>
+    <option name="package" value="com.androidx.service.Placeholder" />
+    <option name="instrumentation-arg" key="client_version" value="previous" />
+    <option name="instrumentation-arg" key="service_version" value="tot" />
+    </test>
+    </configuration>
+""".trimIndent()
\ No newline at end of file
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index bad8aa5..b2122db5 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -60,9 +60,7 @@
     cacheableImplementation {
         extendsFrom(project.configurations.cacheableApi)
     }
-    cacheableRuntime {
-        extendsFrom(project.configurations.cacheableImplementation)
-    }
+    cacheableRuntimeOnly
 }
 
 dependencies {
@@ -74,12 +72,12 @@
     cacheableApi build_libs.dokka_gradle
     // needed by inspection plugin
     cacheableImplementation "com.google.protobuf:protobuf-gradle-plugin:0.8.13"
-    // TODO(aurimas): remove when b/173417030 is fixed
+    // TODO(aurimas): remove when b/174658825 is fixed
     cacheableImplementation "org.anarres.jarjar:jarjar-gradle:1.0.1"
     cacheableImplementation "com.github.jengelman.gradle.plugins:shadow:5.2.0"
     // dependencies that aren't used by buildSrc directly but that we resolve here so that the
     // root project doesn't need to re-resolve them and their dependencies on every build
-    cacheableRuntime build_libs.hilt_plugin
+    cacheableRuntimeOnly build_libs.hilt_plugin
     // dependencies whose resolutions we don't need to cache
     compileOnly(findGradleKotlinDsl()) // Only one file in this configuration, no need to cache it
     implementation project("jetpad-integration") // Doesn't have a .pom, so not slow to load
@@ -168,7 +166,7 @@
 
 loadConfigurationQuicklyInto(configurations.cacheableApi, configurations.api)
 loadConfigurationQuicklyInto(configurations.cacheableImplementation, configurations.implementation)
-loadConfigurationQuicklyInto(configurations.cacheableRuntime, configurations.runtime)
+loadConfigurationQuicklyInto(configurations.cacheableRuntimeOnly, configurations.runtimeOnly)
 
 project.tasks.withType(Jar) { task ->
     task.reproducibleFileOrder = true
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index 5fa294c..09e1a67 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -33,6 +33,8 @@
 import androidx.build.jacoco.Jacoco
 import androidx.build.license.configureExternalDependencyLicenseCheck
 import androidx.build.studio.StudioTask
+import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
+import androidx.build.testConfiguration.configureTestConfigGeneration
 import com.android.build.api.extension.LibraryAndroidComponentsExtension
 import com.android.build.gradle.AppExtension
 import com.android.build.gradle.AppPlugin
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
index f27e132..600632c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
@@ -21,7 +21,6 @@
 import com.android.build.gradle.LibraryExtension
 import com.android.build.gradle.LibraryPlugin
 import com.android.build.gradle.TestedExtension
-import org.gradle.api.DomainObjectCollection
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.artifacts.type.ArtifactTypeDefinition
@@ -32,7 +31,6 @@
 import org.gradle.kotlin.dsl.invoke
 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
-import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 const val composeSourceOption =
@@ -194,7 +192,10 @@
                         "src/commonMain/kotlin", "src/jvmMain/kotlin",
                         "src/androidMain/kotlin"
                     )
-                    res.srcDirs("src/androidMain/res")
+                    res.srcDirs(
+                        "src/commonMain/resources",
+                        "src/androidMain/res"
+                    )
 
                     // Keep Kotlin files in java source sets so the source set is not empty when
                     // running unit tests which would prevent the tests from running in CI.
@@ -225,11 +226,9 @@
          * resolved.
          */
         private fun Project.configureForMultiplatform() {
-            if (multiplatformExtension == null) {
-                throw IllegalStateException(
-                    "Unable to configureForMultiplatform() when " +
-                        "multiplatformExtension is null (multiplatform plugin not enabled?)"
-                )
+            val multiplatformExtension = checkNotNull(multiplatformExtension) {
+                "Unable to configureForMultiplatform() when " +
+                    "multiplatformExtension is null (multiplatform plugin not enabled?)"
             }
 
             /*
@@ -247,13 +246,20 @@
             TODO: Consider changing unitTest to androidLocalTest and androidAndroidTest to
             androidDeviceTest when https://github.com/JetBrains/kotlin/pull/2829 rolls in.
             */
-            multiplatformExtension!!.sourceSets {
+            multiplatformExtension.sourceSets.all {
                 // Allow all experimental APIs, since MPP projects are themselves experimental
-                (this as DomainObjectCollection<KotlinSourceSet>).all {
-                    it.languageSettings.apply {
-                        useExperimentalAnnotation("kotlin.Experimental")
-                        useExperimentalAnnotation("kotlin.ExperimentalMultiplatform")
-                    }
+                it.languageSettings.apply {
+                    useExperimentalAnnotation("kotlin.Experimental")
+                    useExperimentalAnnotation("kotlin.ExperimentalMultiplatform")
+                }
+            }
+
+            afterEvaluate {
+                if (multiplatformExtension.targets.findByName("jvm") != null) {
+                    tasks.named("jvmTestClasses").also(::addToBuildOnServer)
+                }
+                if (multiplatformExtension.targets.findByName("desktop") != null) {
+                    tasks.named("desktopTestClasses").also(::addToBuildOnServer)
                 }
             }
         }
diff --git a/buildSrc/src/main/kotlin/androidx/build/GenerateMediaTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/GenerateMediaTestConfigurationTask.kt
deleted file mode 100644
index 3bfbd2f..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/GenerateMediaTestConfigurationTask.kt
+++ /dev/null
@@ -1,219 +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.build
-
-import com.android.build.api.variant.BuiltArtifacts
-import com.android.build.api.variant.BuiltArtifactsLoader
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.file.RegularFileProperty
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import java.io.File
-
-const val MEDIA_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
-        <!-- Copyright (C) 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.
-        -->
-        <configuration description="Runs tests for the module">
-        <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
-            <option name="min-api-level" value="MIN_SDK" />
-        </object>
-        <option name="test-suite-tag" value="androidx_unit_tests_suite" />
-        <option name="config-descriptor:metadata" key="applicationId"
-            value="CLIENT_APPLICATION_ID;SERVICE_APPLICATION_ID" />
-        <option name="wifi:disable" value="true" />
-        <include name="google/unbundled/common/setup" />
-        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CLIENT_FILE_NAME" />
-        <option name="test-file-name" value="SERVICE_FILE_NAME" />
-        </target_preparer>
-        <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="runner" value="TEST_RUNNER"/>
-        <option name="package" value="CLIENT_APPLICATION_ID" />
-        INSTRUMENTATION_ARGS
-        </test>
-        <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="runner" value="TEST_RUNNER"/>
-        <option name="package" value="SERVICE_APPLICATION_ID" />
-        INSTRUMENTATION_ARGS
-        </test>
-        </configuration>"""
-
-const val CLIENT_PREVIOUS = """
-    <option name="instrumentation-arg" key="client_version" value="previous" />
-"""
-const val CLIENT_TOT = """
-    <option name="instrumentation-arg" key="client_version" value="tot" />
-"""
-const val SERVICE_PREVIOUS = """
-    <option name="instrumentation-arg" key="service_version" value="previous" />
-"""
-const val SERVICE_TOT = """
-    <option name="instrumentation-arg" key="service_version" value="tot" />
-"""
-
-/**
- * Writes three configuration files to test combinations of media client & service in
- * <a href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
- * format that gets zipped alongside the APKs to be tested. The combinations are of previous and
- * tip-of-tree versions client and service. We want to test every possible pairing that includes
- * tip-of-tree.
- *
- * This config gets ingested by Tradefed.
- */
-abstract class GenerateMediaTestConfigurationTask : DefaultTask() {
-
-    @get:InputFiles
-    abstract val clientToTFolder: DirectoryProperty
-
-    @get:Internal
-    abstract val clientToTLoader: Property<BuiltArtifactsLoader>
-
-    @get:InputFiles
-    abstract val clientPreviousFolder: DirectoryProperty
-
-    @get:Internal
-    abstract val clientPreviousLoader: Property<BuiltArtifactsLoader>
-
-    @get:InputFiles
-    abstract val serviceToTFolder: DirectoryProperty
-
-    @get:Internal
-    abstract val serviceToTLoader: Property<BuiltArtifactsLoader>
-
-    @get:InputFiles
-    abstract val servicePreviousFolder: DirectoryProperty
-
-    @get:Internal
-    abstract val servicePreviousLoader: Property<BuiltArtifactsLoader>
-
-    @get:Input
-    abstract val clientToTPath: Property<String>
-
-    @get:Input
-    abstract val clientPreviousPath: Property<String>
-
-    @get:Input
-    abstract val serviceToTPath: Property<String>
-
-    @get:Input
-    abstract val servicePreviousPath: Property<String>
-
-    @get:Input
-    abstract val minSdk: Property<Int>
-
-    @get:Input
-    abstract val testRunner: Property<String>
-
-    @get:OutputFile
-    abstract val clientPreviousServiceToT: RegularFileProperty
-
-    @get:OutputFile
-    abstract val clientToTServicePrevious: RegularFileProperty
-
-    @get:OutputFile
-    abstract val clientToTServiceToT: RegularFileProperty
-
-    @TaskAction
-    fun generateAndroidTestZip() {
-        val clientToTApk = resolveApk(clientToTFolder, clientToTLoader)
-        val clientPreviousApk = resolveApk(clientPreviousFolder, clientPreviousLoader)
-        val serviceToTApk = resolveApk(serviceToTFolder, serviceToTLoader)
-        val servicePreviousApk = resolveApk(
-            servicePreviousFolder, servicePreviousLoader
-        )
-        writeConfigFileContent(
-            clientToTApk, serviceToTApk, clientToTPath.get(),
-            serviceToTPath.get(), clientToTServiceToT
-        )
-        writeConfigFileContent(
-            clientToTApk, servicePreviousApk, clientToTPath.get(),
-            servicePreviousPath.get(), clientToTServicePrevious
-        )
-        writeConfigFileContent(
-            clientPreviousApk, serviceToTApk, clientPreviousPath.get(),
-            serviceToTPath.get(), clientPreviousServiceToT
-        )
-    }
-
-    private fun resolveApk(
-        apkFolder: DirectoryProperty,
-        apkLoader: Property<BuiltArtifactsLoader>
-    ): BuiltArtifacts {
-        return apkLoader.get().load(apkFolder.get())
-            ?: throw RuntimeException("Cannot load APK for $name")
-    }
-
-    private fun resolveName(apk: BuiltArtifacts, path: String): String {
-        return apk.elements.single().outputFile.substringAfterLast("/")
-            .renameApkForTesting(path, false)
-    }
-
-    private fun writeConfigFileContent(
-        clientApk: BuiltArtifacts,
-        serviceApk: BuiltArtifacts,
-        clientPath: String,
-        servicePath: String,
-        outputFile: RegularFileProperty
-    ) {
-        val instrumentationArgs =
-            if (clientPath.contains("previous")) {
-                if (servicePath.contains("previous")) {
-                    CLIENT_PREVIOUS + SERVICE_PREVIOUS
-                } else {
-                    CLIENT_PREVIOUS + SERVICE_TOT
-                }
-            } else if (servicePath.contains("previous")) {
-                CLIENT_TOT + SERVICE_PREVIOUS
-            } else {
-                CLIENT_TOT + SERVICE_TOT
-            }
-        var configContent: String = MEDIA_TEMPLATE
-        configContent = configContent
-            .replace("CLIENT_FILE_NAME", resolveName(clientApk, clientPath))
-            .replace("SERVICE_FILE_NAME", resolveName(serviceApk, servicePath))
-            .replace("CLIENT_APPLICATION_ID", clientApk.applicationId)
-            .replace("SERVICE_APPLICATION_ID", serviceApk.applicationId)
-            .replace("MIN_SDK", minSdk.get().toString())
-            .replace("TEST_RUNNER", testRunner.get())
-            .replace("INSTRUMENTATION_ARGS", instrumentationArgs)
-        val resolvedOutputFile: File = outputFile.asFile.get()
-        if (!resolvedOutputFile.exists()) {
-            if (!resolvedOutputFile.createNewFile()) {
-                throw RuntimeException(
-                    "Failed to create test configuration file: $outputFile"
-                )
-            }
-        }
-        resolvedOutputFile.writeText(configContent)
-    }
-}
diff --git a/buildSrc/src/main/kotlin/androidx/build/GenerateTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/GenerateTestConfigurationTask.kt
deleted file mode 100644
index e958cd3..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/GenerateTestConfigurationTask.kt
+++ /dev/null
@@ -1,210 +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.build
-
-import androidx.build.dependencyTracker.ProjectSubset
-import com.android.build.api.variant.BuiltArtifactsLoader
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.file.RegularFileProperty
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import java.io.File
-
-const val TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
-        <!-- Copyright (C) 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.
-        -->
-        <configuration description="Runs tests for the module">
-        <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
-            <option name="min-api-level" value="MIN_SDK" />
-        </object>
-        <option name="test-suite-tag" value="TEST_SUITE_TAG" />
-        <option name="config-descriptor:metadata" key="applicationId" value="APPLICATION_ID" />
-        <option name="wifi:disable" value="true" />
-        <include name="google/unbundled/common/setup" />
-        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="TEST_FILE_NAME" />
-        <option name="test-file-name" value="APP_FILE_NAME" />
-        </target_preparer>
-        <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="runner" value="TEST_RUNNER"/>
-        <option name="package" value="APPLICATION_ID" />
-        </test>
-        </configuration>"""
-
-const val SELF_INSTRUMENTING_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
-        <!-- Copyright (C) 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.
-        -->
-        <configuration description="Runs tests for the module">
-        <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
-            <option name="min-api-level" value="MIN_SDK" />
-        </object>
-        <option name="test-suite-tag" value="TEST_SUITE_TAG" />
-        <option name="config-descriptor:metadata" key="applicationId" value="APPLICATION_ID" />
-        <option name="wifi:disable" value="true" />
-        <include name="google/unbundled/common/setup" />
-        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="TEST_FILE_NAME" />
-        </target_preparer>
-        TEST_BLOCK
-        </configuration>"""
-
-const val FULL_TEST = """
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="runner" value="TEST_RUNNER"/>
-        <option name="package" value="APPLICATION_ID" />
-    </test>
-"""
-
-const val DEPENDENT_TESTS = """
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="runner" value="TEST_RUNNER"/>
-        <option name="package" value="APPLICATION_ID" />
-        <option name="size" value="small" />
-        <option name="test-timeout" value="300" />
-    </test>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="runner" value="TEST_RUNNER"/>
-        <option name="package" value="APPLICATION_ID" />
-        <option name="size" value="medium" />
-        <option name="test-timeout" value="1500" />
-    </test>
-"""
-
-/**
- * Writes a configuration file in
- * <a href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
- * format that gets zipped alongside the APKs to be tested.
- * This config gets ingested by Tradefed.
- */
-abstract class GenerateTestConfigurationTask : DefaultTask() {
-
-    @get:InputFiles
-    @get:Optional
-    abstract val appFolder: DirectoryProperty
-
-    @get:Internal
-    abstract val appLoader: Property<BuiltArtifactsLoader>
-
-    @get:InputFiles
-    abstract val testFolder: DirectoryProperty
-
-    @get:Internal
-    abstract val testLoader: Property<BuiltArtifactsLoader>
-
-    @get:Input
-    abstract val minSdk: Property<Int>
-
-    @get:Input
-    abstract val hasBenchmarkPlugin: Property<Boolean>
-
-    @get:Input
-    abstract val testRunner: Property<String>
-
-    @get:Input
-    abstract val projectPath: Property<String>
-
-    @get:Input
-    abstract val affectedModuleDetectorSubset: Property<ProjectSubset>
-
-    @get:OutputFile
-    abstract val outputXml: RegularFileProperty
-
-    @TaskAction
-    fun generateAndroidTestZip() {
-        writeConfigFileContent()
-    }
-
-    private fun writeConfigFileContent() {
-        /*
-        Testing an Android Application project involves 2 APKS: an application to be instrumented,
-        and a test APK. Testing an Android Library project involves only 1 APK, since the library
-        is bundled inside the test APK, meaning it is self instrumenting. We add extra data to
-        configurations testing Android Application projects, so that both APKs get installed.
-         */
-        var configContent: String = if (appLoader.isPresent) {
-            val appApk = appLoader.get().load(appFolder.get())
-                ?: throw RuntimeException("Cannot load application APK for $name")
-            val appName = appApk.elements.single().outputFile.substringAfterLast("/")
-                .renameApkForTesting(projectPath.get(), hasBenchmarkPlugin.get())
-            TEMPLATE.replace("APP_FILE_NAME", appName)
-        } else {
-            SELF_INSTRUMENTING_TEMPLATE
-        }
-        configContent = when (affectedModuleDetectorSubset.get()) {
-            ProjectSubset.CHANGED_PROJECTS, ProjectSubset.ALL_AFFECTED_PROJECTS -> {
-                configContent.replace("TEST_BLOCK", FULL_TEST)
-            }
-            ProjectSubset.DEPENDENT_PROJECTS -> {
-                configContent.replace("TEST_BLOCK", DEPENDENT_TESTS)
-            }
-            else -> {
-                throw IllegalStateException(
-                    "$name should not be running if the AffectedModuleDetector is returning " +
-                        "${affectedModuleDetectorSubset.get()} for this project."
-                )
-            }
-        }
-        val tag = if (hasBenchmarkPlugin.get()) "MetricTests" else "androidx_unit_tests"
-        val testApk = testLoader.get().load(testFolder.get())
-            ?: throw RuntimeException("Cannot load test APK for $name")
-        val testName = testApk.elements.single().outputFile
-            .substringAfterLast("/")
-            .renameApkForTesting(projectPath.get(), hasBenchmarkPlugin.get())
-        configContent = configContent.replace("TEST_FILE_NAME", testName)
-            .replace("APPLICATION_ID", testApk.applicationId)
-            .replace("MIN_SDK", minSdk.get().toString())
-            .replace("TEST_SUITE_TAG", tag)
-            .replace("TEST_RUNNER", testRunner.get())
-        val resolvedOutputFile: File = outputXml.asFile.get()
-        if (!resolvedOutputFile.exists()) {
-            if (!resolvedOutputFile.createNewFile()) {
-                throw RuntimeException(
-                    "Failed to create test configuration file: $outputXml"
-                )
-            }
-        }
-        resolvedOutputFile.writeText(configContent)
-    }
-}
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index cf2d806..51777b8 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -20,7 +20,7 @@
  * The list of versions codes of all the libraries in this project.
  */
 object LibraryVersions {
-    val ACTIVITY = Version("1.2.0-beta02")
+    val ACTIVITY = Version("1.2.0-rc01")
     val ADS_IDENTIFIER = Version("1.0.0-alpha04")
     val ANNOTATION = Version("1.2.0-alpha02")
     val ANNOTATION_EXPERIMENTAL = Version("1.1.0-alpha02")
@@ -32,7 +32,7 @@
     val ASYNCLAYOUTINFLATER = Version("1.1.0-alpha01")
     val AUTOFILL = Version("1.1.0-rc01")
     val BENCHMARK = Version("1.1.0-alpha02")
-    val BIOMETRIC = Version("1.2.0-alpha01")
+    val BIOMETRIC = Version("1.2.0-alpha02")
     val BROWSER = Version("1.3.0-rc01")
     val BUILDSRC_TESTS = Version("1.0.0-alpha01")
     val CAMERA = Version("1.0.0-rc01")
@@ -61,7 +61,7 @@
     val EMOJI = Version("1.2.0-alpha03")
     val ENTERPRISE = Version("1.1.0-rc01")
     val EXIFINTERFACE = Version("1.4.0-alpha01")
-    val FRAGMENT = Version("1.3.0-beta02")
+    val FRAGMENT = Version("1.3.0-rc01")
     val FUTURES = Version("1.2.0-alpha01")
     val GRIDLAYOUT = Version("1.1.0-alpha01")
     val HEIFWRITER = Version("1.1.0-alpha02")
@@ -71,11 +71,12 @@
     val IPC = Version("1.0.0-alpha01")
     val JETIFIER = Version("1.0.0-beta10")
     val LEANBACK = Version("1.1.0-beta01")
-    val LEANBACK_PAGING = Version("1.1.0-alpha06")
+    val LEANBACK_PAGING = Version("1.1.0-alpha07")
     val LEANBACK_PREFERENCE = Version("1.1.0-beta01")
+    val LEANBACK_TAB = Version("1.1.0-beta01")
     val LEGACY = Version("1.1.0-alpha01")
     val LOCALBROADCASTMANAGER = Version("1.1.0-alpha02")
-    val LIFECYCLE = Version("2.3.0-beta01")
+    val LIFECYCLE = Version("2.3.0-rc01")
     val LIFECYCLE_EXTENSIONS = Version("2.2.0")
     val LOADER = Version("1.2.0-alpha01")
     val MEDIA = Version("1.3.0-alpha02")
@@ -83,7 +84,7 @@
     val MEDIAROUTER = Version("1.3.0-alpha01")
     val NAVIGATION = Version("2.4.0-alpha01")
     val NAVIGATION_COMPOSE = Version("1.0.0-alpha04")
-    val PAGING = Version("3.0.0-alpha10")
+    val PAGING = Version("3.0.0-alpha11")
     val PAGING_COMPOSE = Version("1.0.0-alpha04")
     val PALETTE = Version("1.1.0-alpha01")
     val PRINT = Version("1.1.0-beta01")
@@ -94,8 +95,9 @@
     val RECYCLERVIEW_SELECTION = Version("2.0.0-alpha01")
     val REMOTECALLBACK = Version("1.0.0-alpha02")
     val ROOM = Version("2.3.0-alpha04")
-    val SAVEDSTATE = Version("1.1.0-beta01")
+    val SAVEDSTATE = Version("1.1.0-rc01")
     val SECURITY = Version("1.1.0-alpha03")
+    val SECURITY_APP_AUTHENTICATOR = Version("1.0.0-alpha01")
     val SECURITY_BIOMETRIC = Version("1.0.0-alpha01")
     val SECURITY_IDENTITY_CREDENTIAL = Version("1.0.0-alpha01")
     val SERIALIZATION = Version("1.0.0-alpha01")
@@ -121,15 +123,15 @@
     val VERSIONED_PARCELABLE = Version("1.2.0-alpha01")
     val VIEWPAGER = Version("1.1.0-alpha01")
     val VIEWPAGER2 = Version("1.1.0-alpha02")
-    val WEAR = Version("1.2.0-alpha03")
-    val WEAR_COMPLICATIONS = Version("1.0.0-alpha03")
-    val WEAR_INPUT = Version("1.0.0-rc01")
+    val WEAR = Version("1.2.0-alpha04")
+    val WEAR_COMPLICATIONS = Version("1.0.0-alpha04")
+    val WEAR_INPUT = Version("1.1.0-alpha01")
     val WEAR_TILES = Version("1.0.0-alpha01")
     val WEAR_TILES_DATA = WEAR_TILES
-    val WEAR_WATCHFACE = Version("1.0.0-alpha03")
-    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha03")
-    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha03")
-    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha03")
+    val WEAR_WATCHFACE = Version("1.0.0-alpha04")
+    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha04")
+    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha04")
+    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha04")
     val WEBKIT = Version("1.4.0-beta01")
     val WINDOW = Version("1.0.0-alpha02")
     val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
index e764c62..9c6b2c0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -110,10 +110,15 @@
             // concerned with drawables potentially being a little bit blurry
             disable("IconMissingDensityFolder")
 
+            // Disable a check that's only triggered by translation updates which are
+            // outside of library owners' control, b/174655193
+            disable("UnusedQuantity")
+
             // Disable until it works for our projects, b/171986505
             disable("JavaPluginLanguageLevel")
 
-            if (extension.type.compilationTarget != CompilationTarget.HOST) {
+            // Provide stricter enforcement for project types intended to run on a device.
+            if (extension.type.compilationTarget == CompilationTarget.DEVICE) {
                 fatal("Assert")
                 fatal("NewApi")
                 fatal("ObsoleteSdkInt")
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 4d7315f..199694d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -470,12 +470,19 @@
         private val COBUILT_TEST_PATHS = setOf(
             // Install media tests together per b/128577735
             setOf(
+                // Making a change in :media:version-compat-tests makes
+                // mediaGenerateTestConfiguration run (an unfortunate but low priority bug). To
+                // prevent failures from missing apks, we make sure to build the
+                // version-compat-tests projects in that case. Same with media2-session below.
+                ":media:version-compat-tests",
                 ":media:version-compat-tests:client",
                 ":media:version-compat-tests:service",
                 ":media:version-compat-tests:client-previous",
                 ":media:version-compat-tests:service-previous"
             ),
             setOf(
+                ":media2:media2-session",
+                ":media2:media2-session:version-compat-tests",
                 ":media2:media2-session:version-compat-tests:client",
                 ":media2:media2-session:version-compat-tests:service",
                 ":media2:media2-session:version-compat-tests:client-previous",
@@ -486,17 +493,17 @@
                 ":compose:material:material"
             ),
             setOf(
-                ":benchmark:integration-tests:macrobenchmark",
-                ":benchmark:integration-tests:macrobenchmark-target"
-            ),
-            setOf(
                 ":benchmark:benchmark-macro",
                 ":benchmark:integration-tests:macrobenchmark-target"
-            ),
+            ), // link benchmark-macro's correctness test and its target
+            setOf(
+                ":benchmark:integration-tests:macrobenchmark",
+                ":benchmark:integration-tests:macrobenchmark-target"
+            ), // link benchmark's macrobenchmark and its target
             setOf(
                 ":compose:integration-tests:macrobenchmark",
-                ":compose:integration-tests:demos"
-            ),
+                ":compose:integration-tests:macrobenchmark-target"
+            ), // link compose's macrobenchmark and its target
         )
     }
 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/ToStringLogger.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/ToStringLogger.kt
index 359edbb..eb0d0e0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/ToStringLogger.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/ToStringLogger.kt
@@ -39,7 +39,7 @@
     ).also {
         it.level = LogLevel.DEBUG
         it.setOutputEventListener {
-            stringBuilder.appendln(it.toString())
+            stringBuilder.append(it.toString() + "\n")
         }
     },
     Clock {
@@ -65,4 +65,4 @@
             return logger
         }
     }
-}
\ No newline at end of file
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
new file mode 100644
index 0000000..a55e365
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
@@ -0,0 +1,316 @@
+/*
+ * 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.build.testConfiguration
+
+class ConfigBuilder {
+    var appApkName: String? = null
+    lateinit var applicationId: String
+    var isBenchmark: Boolean = false
+    var isPostsubmit: Boolean = true
+    lateinit var minSdk: String
+    var tag: String = "androidx_unit_tests"
+    lateinit var testApkName: String
+    lateinit var testRunner: String
+
+    fun appApkName(appApkName: String) = apply { this.appApkName = appApkName }
+    fun applicationId(applicationId: String) = apply { this.applicationId = applicationId }
+    fun isBenchmark(isBenchmark: Boolean) = apply { this.isBenchmark = isBenchmark }
+    fun isPostsubmit(isPostsubmit: Boolean) = apply { this.isPostsubmit = isPostsubmit }
+    fun minSdk(minSdk: String) = apply { this.minSdk = minSdk }
+    fun tag(tag: String) = apply { this.tag = tag }
+    fun testApkName(testApkName: String) = apply { this.testApkName = testApkName }
+    fun testRunner(testRunner: String) = apply { this.testRunner = testRunner }
+
+    fun build(): String {
+        val sb = StringBuilder()
+        sb.append(XML_HEADER_AND_LICENSE)
+            .append(CONFIGURATION_OPEN)
+            .append(MIN_API_LEVEL_CONTROLLER_OBJECT.replace("MIN_SDK", minSdk))
+            .append(TEST_SUITE_TAG_OPTION.replace("TEST_SUITE_TAG", tag))
+            .append(MODULE_METADATA_TAG_OPTION.replace("APPLICATION_ID", applicationId))
+            .append(WIFI_DISABLE_OPTION)
+        if (isBenchmark) {
+            if (isPostsubmit) {
+                sb.append(BENCHMARK_POSTSUBMIT_OPTIONS)
+            } else {
+                sb.append(BENCHMARK_PRESUBMIT_OPTION)
+            }
+        }
+        sb.append(SETUP_INCLUDE)
+            .append(TARGET_PREPARER_OPEN)
+            .append(APK_INSTALL_OPTION.replace("APK_NAME", testApkName))
+        if (!appApkName.isNullOrEmpty())
+            sb.append(APK_INSTALL_OPTION.replace("APK_NAME", appApkName!!))
+        sb.append(TARGET_PREPARER_CLOSE)
+            .append(TEST_BLOCK_OPEN)
+            .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
+            .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
+        if (isPostsubmit)
+            sb.append(TEST_BLOCK_CLOSE)
+        else {
+            sb.append(SMALL_TEST_OPTIONS)
+                .append(TEST_BLOCK_CLOSE)
+                .append(TEST_BLOCK_OPEN)
+                .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
+                .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
+                .append(MEDIUM_TEST_OPTIONS)
+                .append(TEST_BLOCK_CLOSE)
+        }
+        sb.append(CONFIGURATION_CLOSE)
+        return sb.toString()
+    }
+}
+
+class MediaConfigBuilder {
+    lateinit var clientApkName: String
+    lateinit var clientApplicationId: String
+    var isClientPrevious: Boolean = true
+    var isPostsubmit: Boolean = true
+    var isServicePrevious: Boolean = true
+    lateinit var minSdk: String
+    lateinit var serviceApkName: String
+    lateinit var serviceApplicationId: String
+    var tag: String = "androidx_unit_tests"
+    lateinit var testRunner: String
+
+    fun clientApkName(clientApkName: String) = apply { this.clientApkName = clientApkName }
+    fun clientApplicationId(clientApplicationId: String) =
+        apply { this.clientApplicationId = clientApplicationId }
+    fun isPostsubmit(isPostsubmit: Boolean) = apply { this.isPostsubmit = isPostsubmit }
+    fun isClientPrevious(isClientPrevious: Boolean) = apply {
+        this.isClientPrevious = isClientPrevious
+    }
+    fun isServicePrevious(isServicePrevious: Boolean) = apply {
+        this.isServicePrevious = isServicePrevious
+    }
+    fun minSdk(minSdk: String) = apply { this.minSdk = minSdk }
+    fun serviceApkName(serviceApkName: String) = apply { this.serviceApkName = serviceApkName }
+    fun serviceApplicationId(serviceApplicationId: String) =
+        apply { this.serviceApplicationId = serviceApplicationId }
+    fun tag(tag: String) = apply { this.tag = tag }
+    fun testRunner(testRunner: String) = apply { this.testRunner = testRunner }
+
+    private fun mediaInstrumentationArgs(): String {
+        return if (isClientPrevious) {
+            if (isServicePrevious) {
+                CLIENT_PREVIOUS + SERVICE_PREVIOUS
+            } else {
+                CLIENT_PREVIOUS + SERVICE_TOT
+            }
+        } else {
+            if (isServicePrevious) {
+                CLIENT_TOT + SERVICE_PREVIOUS
+            } else {
+                CLIENT_TOT + SERVICE_TOT
+            }
+        }
+    }
+
+    fun build(): String {
+        val sb = StringBuilder()
+        sb.append(XML_HEADER_AND_LICENSE)
+            .append(CONFIGURATION_OPEN)
+            .append(MIN_API_LEVEL_CONTROLLER_OBJECT.replace("MIN_SDK", minSdk))
+            .append(TEST_SUITE_TAG_OPTION.replace("TEST_SUITE_TAG", tag))
+            .append(TEST_SUITE_TAG_OPTION.replace("TEST_SUITE_TAG", "media_compat"))
+            .append(
+                MODULE_METADATA_TAG_OPTION.replace(
+                    "APPLICATION_ID", "$clientApplicationId;$serviceApplicationId"
+                )
+            )
+            .append(WIFI_DISABLE_OPTION)
+            .append(SETUP_INCLUDE)
+            .append(TARGET_PREPARER_OPEN)
+            .append(APK_INSTALL_OPTION.replace("APK_NAME", clientApkName))
+            .append(APK_INSTALL_OPTION.replace("APK_NAME", serviceApkName))
+        sb.append(TARGET_PREPARER_CLOSE)
+            .append(TEST_BLOCK_OPEN)
+            .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
+            .append(PACKAGE_OPTION.replace("APPLICATION_ID", clientApplicationId))
+            .append(mediaInstrumentationArgs())
+        if (isPostsubmit)
+            sb.append(TEST_BLOCK_CLOSE)
+                .append(TEST_BLOCK_OPEN)
+                .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
+                .append(PACKAGE_OPTION.replace("APPLICATION_ID", serviceApplicationId))
+                .append(mediaInstrumentationArgs())
+                .append(TEST_BLOCK_CLOSE)
+        else {
+            // add the small and medium test runners for both client and service apps
+            sb.append(SMALL_TEST_OPTIONS)
+                .append(TEST_BLOCK_CLOSE)
+                .append(TEST_BLOCK_OPEN)
+                .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
+                .append(PACKAGE_OPTION.replace("APPLICATION_ID", clientApplicationId))
+                .append(mediaInstrumentationArgs())
+                .append(MEDIUM_TEST_OPTIONS)
+                .append(TEST_BLOCK_CLOSE)
+                .append(TEST_BLOCK_OPEN)
+                .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
+                .append(PACKAGE_OPTION.replace("APPLICATION_ID", serviceApplicationId))
+                .append(mediaInstrumentationArgs())
+                .append(SMALL_TEST_OPTIONS)
+                .append(TEST_BLOCK_CLOSE)
+                .append(TEST_BLOCK_OPEN)
+                .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
+                .append(PACKAGE_OPTION.replace("APPLICATION_ID", serviceApplicationId))
+                .append(mediaInstrumentationArgs())
+                .append(MEDIUM_TEST_OPTIONS)
+                .append(TEST_BLOCK_CLOSE)
+        }
+        sb.append(CONFIGURATION_CLOSE)
+        return sb.toString()
+    }
+}
+
+/**
+ * These constants are the building blocks of the xml configs, but
+ * they aren't very readable as separate chunks. Look to
+ * the golden examples at the bottom of
+ * {@link androidx.build.testConfiguration.XmlTestConfigVerificationTest}
+ * for examples of what the full xml will look like.
+ */
+
+private val XML_HEADER_AND_LICENSE = """
+    <?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.-->
+
+""".trimIndent()
+
+private val CONFIGURATION_OPEN = """
+    <configuration description="Runs tests for the module">
+
+""".trimIndent()
+
+private val CONFIGURATION_CLOSE = """
+    </configuration>
+""".trimIndent()
+
+private val MIN_API_LEVEL_CONTROLLER_OBJECT = """
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
+    <option name="min-api-level" value="MIN_SDK" />
+    </object>
+
+""".trimIndent()
+
+private val TEST_SUITE_TAG_OPTION = """
+    <option name="test-suite-tag" value="TEST_SUITE_TAG" />
+
+""".trimIndent()
+
+private val MODULE_METADATA_TAG_OPTION = """
+    <option name="config-descriptor:metadata" key="applicationId" value="APPLICATION_ID" />
+
+""".trimIndent()
+
+private val WIFI_DISABLE_OPTION = """
+    <option name="wifi:disable" value="true" />
+
+""".trimIndent()
+
+private val SETUP_INCLUDE = """
+    <include name="google/unbundled/common/setup" />
+
+""".trimIndent()
+
+private val TARGET_PREPARER_OPEN = """
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+
+""".trimIndent()
+
+private val TARGET_PREPARER_CLOSE = """
+    </target_preparer>
+
+""".trimIndent()
+
+private val APK_INSTALL_OPTION = """
+    <option name="test-file-name" value="APK_NAME" />
+
+""".trimIndent()
+
+private val TEST_BLOCK_OPEN = """
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+
+""".trimIndent()
+
+private val TEST_BLOCK_CLOSE = """
+    </test>
+
+""".trimIndent()
+
+private val RUNNER_OPTION = """
+    <option name="runner" value="TEST_RUNNER"/>
+
+""".trimIndent()
+
+private val PACKAGE_OPTION = """
+    <option name="package" value="APPLICATION_ID" />
+
+""".trimIndent()
+
+private val BENCHMARK_PRESUBMIT_OPTION = """
+    <option name="instrumentation-arg" key="androidx.benchmark.dryRunMode.enable" value="true" />
+
+""".trimIndent()
+
+private val BENCHMARK_POSTSUBMIT_OPTIONS = """
+    <option name="instrumentation-arg" key="androidx.benchmark.output.enable" value="true" />
+    <option name="instrumentation-arg" key="listener" value="androidx.benchmark.junit4.InstrumentationResultsRunListener" />
+
+""".trimIndent()
+
+private val SMALL_TEST_OPTIONS = """
+    <option name="size" value="small" />
+    <option name="test-timeout" value="300" />
+
+""".trimIndent()
+
+private val MEDIUM_TEST_OPTIONS = """
+    <option name="size" value="medium" />
+    <option name="test-timeout" value="1500" />
+
+""".trimIndent()
+
+private val CLIENT_PREVIOUS = """
+    <option name="instrumentation-arg" key="client_version" value="previous" />
+
+""".trimIndent()
+
+private val CLIENT_TOT = """
+    <option name="instrumentation-arg" key="client_version" value="tot" />
+
+""".trimIndent()
+
+private val SERVICE_PREVIOUS = """
+    <option name="instrumentation-arg" key="service_version" value="previous" />
+
+""".trimIndent()
+
+private val SERVICE_TOT = """
+    <option name="instrumentation-arg" key="service_version" value="tot" />
+
+""".trimIndent()
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
new file mode 100644
index 0000000..8ef93bcb
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.build.testConfiguration
+
+import androidx.build.dependencyTracker.ProjectSubset
+import androidx.build.renameApkForTesting
+import com.android.build.api.variant.BuiltArtifacts
+import com.android.build.api.variant.BuiltArtifactsLoader
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+
+/**
+ * Writes three configuration files to test combinations of media client & service in
+ * <a href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
+ * format that gets zipped alongside the APKs to be tested. The combinations are of previous and
+ * tip-of-tree versions client and service. We want to test every possible pairing that includes
+ * tip-of-tree.
+ *
+ * This config gets ingested by Tradefed.
+ */
+abstract class GenerateMediaTestConfigurationTask : DefaultTask() {
+
+    @get:InputFiles
+    abstract val clientToTFolder: DirectoryProperty
+
+    @get:Internal
+    abstract val clientToTLoader: Property<BuiltArtifactsLoader>
+
+    @get:InputFiles
+    abstract val clientPreviousFolder: DirectoryProperty
+
+    @get:Internal
+    abstract val clientPreviousLoader: Property<BuiltArtifactsLoader>
+
+    @get:InputFiles
+    abstract val serviceToTFolder: DirectoryProperty
+
+    @get:Internal
+    abstract val serviceToTLoader: Property<BuiltArtifactsLoader>
+
+    @get:InputFiles
+    abstract val servicePreviousFolder: DirectoryProperty
+
+    @get:Internal
+    abstract val servicePreviousLoader: Property<BuiltArtifactsLoader>
+
+    @get:Input
+    abstract val affectedModuleDetectorSubset: Property<ProjectSubset>
+
+    @get:Input
+    abstract val clientToTPath: Property<String>
+
+    @get:Input
+    abstract val clientPreviousPath: Property<String>
+
+    @get:Input
+    abstract val serviceToTPath: Property<String>
+
+    @get:Input
+    abstract val servicePreviousPath: Property<String>
+
+    @get:Input
+    abstract val minSdk: Property<Int>
+
+    @get:Input
+    abstract val testRunner: Property<String>
+
+    @get:OutputFile
+    abstract val clientPreviousServiceToT: RegularFileProperty
+
+    @get:OutputFile
+    abstract val clientToTServicePrevious: RegularFileProperty
+
+    @get:OutputFile
+    abstract val clientToTServiceToT: RegularFileProperty
+
+    @TaskAction
+    fun generateAndroidTestZip() {
+        val clientToTApk = resolveApk(clientToTFolder, clientToTLoader)
+        val clientPreviousApk = resolveApk(clientPreviousFolder, clientPreviousLoader)
+        val serviceToTApk = resolveApk(serviceToTFolder, serviceToTLoader)
+        val servicePreviousApk = resolveApk(
+            servicePreviousFolder, servicePreviousLoader
+        )
+        writeConfigFileContent(
+            clientToTApk, serviceToTApk, clientToTPath.get(),
+            serviceToTPath.get(), clientToTServiceToT, false, false
+        )
+        writeConfigFileContent(
+            clientToTApk, servicePreviousApk, clientToTPath.get(),
+            servicePreviousPath.get(), clientToTServicePrevious, false, true
+        )
+        writeConfigFileContent(
+            clientPreviousApk, serviceToTApk, clientPreviousPath.get(),
+            serviceToTPath.get(), clientPreviousServiceToT, true, false
+        )
+    }
+
+    private fun resolveApk(
+        apkFolder: DirectoryProperty,
+        apkLoader: Property<BuiltArtifactsLoader>
+    ): BuiltArtifacts {
+        return apkLoader.get().load(apkFolder.get())
+            ?: throw RuntimeException("Cannot load required APK for task: $name")
+    }
+
+    private fun resolveName(apk: BuiltArtifacts, path: String): String {
+        return apk.elements.single().outputFile.substringAfterLast("/")
+            .renameApkForTesting(path, false)
+    }
+
+    private fun writeConfigFileContent(
+        clientApk: BuiltArtifacts,
+        serviceApk: BuiltArtifacts,
+        clientPath: String,
+        servicePath: String,
+        outputFile: RegularFileProperty,
+        isClientPrevious: Boolean,
+        isServicePrevious: Boolean
+    ) {
+        val configBuilder = MediaConfigBuilder()
+        configBuilder.clientApkName(resolveName(clientApk, clientPath))
+            .clientApplicationId(clientApk.applicationId)
+            .serviceApkName(resolveName(serviceApk, servicePath))
+            .serviceApplicationId(serviceApk.applicationId)
+            .minSdk(minSdk.get().toString())
+            .testRunner(testRunner.get())
+            .isClientPrevious(isClientPrevious)
+            .isServicePrevious(isServicePrevious)
+        when (affectedModuleDetectorSubset.get()) {
+            ProjectSubset.CHANGED_PROJECTS, ProjectSubset.ALL_AFFECTED_PROJECTS -> {
+                configBuilder.isPostsubmit(true)
+            }
+            ProjectSubset.DEPENDENT_PROJECTS -> {
+                configBuilder.isPostsubmit(false)
+            }
+            else -> {
+                throw IllegalStateException(
+                    "$name should not be running if the AffectedModuleDetector is returning " +
+                        "${affectedModuleDetectorSubset.get()} for this project."
+                )
+            }
+        }
+
+        val resolvedOutputFile: File = outputFile.asFile.get()
+        if (!resolvedOutputFile.exists()) {
+            if (!resolvedOutputFile.createNewFile()) {
+                throw RuntimeException(
+                    "Failed to create test configuration file: $outputFile"
+                )
+            }
+        }
+        resolvedOutputFile.writeText(configBuilder.build())
+    }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
new file mode 100644
index 0000000..a95e43c
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.build.testConfiguration
+
+import androidx.build.dependencyTracker.ProjectSubset
+import androidx.build.renameApkForTesting
+import com.android.build.api.variant.BuiltArtifactsLoader
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+
+/**
+ * Writes a configuration file in
+ * <a href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
+ * format that gets zipped alongside the APKs to be tested.
+ * This config gets ingested by Tradefed.
+ */
+abstract class GenerateTestConfigurationTask : DefaultTask() {
+
+    @get:InputFiles
+    @get:Optional
+    abstract val appFolder: DirectoryProperty
+
+    @get:Internal
+    abstract val appLoader: Property<BuiltArtifactsLoader>
+
+    @get:InputFiles
+    abstract val testFolder: DirectoryProperty
+
+    @get:Internal
+    abstract val testLoader: Property<BuiltArtifactsLoader>
+
+    @get:Input
+    abstract val minSdk: Property<Int>
+
+    @get:Input
+    abstract val hasBenchmarkPlugin: Property<Boolean>
+
+    @get:Input
+    abstract val testRunner: Property<String>
+
+    @get:Input
+    abstract val projectPath: Property<String>
+
+    @get:Input
+    abstract val affectedModuleDetectorSubset: Property<ProjectSubset>
+
+    @get:OutputFile
+    abstract val outputXml: RegularFileProperty
+
+    @TaskAction
+    fun generateAndroidTestZip() {
+        writeConfigFileContent()
+    }
+
+    private fun writeConfigFileContent() {
+        /*
+        Testing an Android Application project involves 2 APKS: an application to be instrumented,
+        and a test APK. Testing an Android Library project involves only 1 APK, since the library
+        is bundled inside the test APK, meaning it is self instrumenting. We add extra data to
+        configurations testing Android Application projects, so that both APKs get installed.
+         */
+        val configBuilder = ConfigBuilder()
+        if (appLoader.isPresent) {
+            val appApk = appLoader.get().load(appFolder.get())
+                ?: throw RuntimeException("Cannot load required APK for task: $name")
+            val appName = appApk.elements.single().outputFile.substringAfterLast("/")
+                .renameApkForTesting(projectPath.get(), hasBenchmarkPlugin.get())
+            configBuilder.appApkName(appName)
+        }
+        val isPostsubmit: Boolean = when (affectedModuleDetectorSubset.get()) {
+            ProjectSubset.CHANGED_PROJECTS, ProjectSubset.ALL_AFFECTED_PROJECTS -> {
+                true
+            }
+            ProjectSubset.DEPENDENT_PROJECTS -> {
+                false
+            }
+            else -> {
+                throw IllegalStateException(
+                    "$name should not be running if the AffectedModuleDetector is returning " +
+                        "${affectedModuleDetectorSubset.get()} for this project."
+                )
+            }
+        }
+        configBuilder.isPostsubmit(isPostsubmit)
+        if (hasBenchmarkPlugin.get()) {
+            configBuilder.isBenchmark(true)
+            if (isPostsubmit) {
+                configBuilder.tag("microbenchmarks")
+            }
+        } else if (projectPath.get().endsWith("macrobenchmark")) {
+            configBuilder.tag("macrobenchmarks")
+        }
+        val testApk = testLoader.get().load(testFolder.get())
+            ?: throw RuntimeException("Cannot load required APK for task: $name")
+        val testName = testApk.elements.single().outputFile
+            .substringAfterLast("/")
+            .renameApkForTesting(projectPath.get(), hasBenchmarkPlugin.get())
+        configBuilder.testApkName(testName)
+            .applicationId(testApk.applicationId)
+            .minSdk(minSdk.get().toString())
+            .testRunner(testRunner.get())
+
+        val resolvedOutputFile: File = outputXml.asFile.get()
+        if (!resolvedOutputFile.exists()) {
+            if (!resolvedOutputFile.createNewFile()) {
+                throw RuntimeException(
+                    "Failed to create test configuration file: $outputXml"
+                )
+            }
+        }
+        resolvedOutputFile.writeText(configBuilder.build())
+    }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/TestSuiteConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
similarity index 92%
rename from buildSrc/src/main/kotlin/androidx/build/TestSuiteConfiguration.kt
rename to buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index c676f20..764abcc 100644
--- a/buildSrc/src/main/kotlin/androidx/build/TestSuiteConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -16,10 +16,15 @@
 
 @file:Suppress("UnstableApiUsage") // Incubating AGP APIs
 
-package androidx.build
+package androidx.build.testConfiguration
 
+import androidx.build.AndroidXPlugin
+import androidx.build.asFilenamePrefix
 import androidx.build.dependencyTracker.AffectedModuleDetector
+import androidx.build.getTestConfigDirectory
 import androidx.build.gradle.getByType
+import androidx.build.hasAndroidTestSourceCode
+import androidx.build.hasBenchmarkPlugin
 import com.android.build.api.artifact.ArtifactType
 import com.android.build.api.artifact.Artifacts
 import com.android.build.api.extension.AndroidComponentsExtension
@@ -85,8 +90,7 @@
         if (!project.parent!!.tasks.withType(GenerateMediaTestConfigurationTask::class.java)
             .names.contains(
                     "support-$mediaPrefix-test${
-                    AndroidXPlugin
-                        .GENERATE_TEST_CONFIGURATION_TASK
+                    AndroidXPlugin.GENERATE_TEST_CONFIGURATION_TASK
                     }"
                 )
         ) {
@@ -95,6 +99,11 @@
                 GenerateMediaTestConfigurationTask::class.java
             ) { task ->
                 AffectedModuleDetector.configureTaskGuard(task)
+                task.affectedModuleDetectorSubset.set(
+                    project.provider {
+                        AffectedModuleDetector.getProjectSubset(project)
+                    }
+                )
             }
             project.rootProject.tasks.findByName(AndroidXPlugin.ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
                 .dependsOn(task)
@@ -103,8 +112,7 @@
             return project.parent!!.tasks.withType(GenerateMediaTestConfigurationTask::class.java)
                 .named(
                     "support-$mediaPrefix-test${
-                    AndroidXPlugin
-                        .GENERATE_TEST_CONFIGURATION_TASK
+                    AndroidXPlugin.GENERATE_TEST_CONFIGURATION_TASK
                     }"
                 )
         }
@@ -203,4 +211,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/busytown/androidx_max_dep_versions.sh b/busytown/androidx_max_dep_versions.sh
index 5dc8e64..aee0689 100755
--- a/busytown/androidx_max_dep_versions.sh
+++ b/busytown/androidx_max_dep_versions.sh
@@ -5,6 +5,8 @@
 
 cd "$(dirname $0)"
 
-impl/build.sh --no-daemon assembleDebug assembleAndroidTest -PuseMaxDepVersions --offline "$@"
+impl/build.sh --no-daemon --offline assembleDebug assembleAndroidTest \
+    -PuseMaxDepVersions \
+    -Pandroidx.validateNoUnrecognizedMessages "$@"
 
 echo "Completing $0 at $(date)"
diff --git a/busytown/androidx_test_changed_apks.sh b/busytown/androidx_test_changed_apks.sh
index 549692f..40a0bf4 100755
--- a/busytown/androidx_test_changed_apks.sh
+++ b/busytown/androidx_test_changed_apks.sh
@@ -9,6 +9,7 @@
     -Pandroidx.enableAffectedModuleDetection \
     -Pandroidx.changedProjects \
     -Pandroidx.coverageEnabled=true \
+    -Pandroidx.validateNoUnrecognizedMessages \
     -Pandroidx.allWarningsAsErrors --offline "$@"
 
 echo "Completing $0 at $(date)"
diff --git a/busytown/androidx_test_dependent_apks.sh b/busytown/androidx_test_dependent_apks.sh
index a2abc43..57745ba 100755
--- a/busytown/androidx_test_dependent_apks.sh
+++ b/busytown/androidx_test_dependent_apks.sh
@@ -9,6 +9,7 @@
     -Pandroidx.enableAffectedModuleDetection \
     -Pandroidx.dependentProjects \
     -Pandroidx.coverageEnabled=true \
+    -Pandroidx.validateNoUnrecognizedMessages \
     -Pandroidx.allWarningsAsErrors --offline "$@"
 
 echo "Completing $0 at $(date)"
diff --git a/camera/camera-camera2-pipe/dependencies.gradle b/camera/camera-camera2-pipe/dependencies.gradle
index 6cd62b7..db00727 100644
--- a/camera/camera-camera2-pipe/dependencies.gradle
+++ b/camera/camera-camera2-pipe/dependencies.gradle
@@ -22,7 +22,7 @@
                 "androidx.annotation:annotation:1.0.0",
                 KOTLIN_STDLIB,
                 KOTLIN_COROUTINES_ANDROID,
-                "org.jetbrains.kotlinx:atomicfu:0.13.1"
+                "org.jetbrains.kotlinx:atomicfu:0.15.0"
         ],
         IMPLEMENTATION : [DAGGER]
     ]
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index 1dddd47..c797ab9 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -95,7 +95,8 @@
     companion object Constants3A {
         // Constants related to controlling the time or frame budget a 3A operation should get.
         const val DEFAULT_FRAME_LIMIT = 60
-        const val DEFAULT_TIME_LIMIT_MS = 3000
+        const val DEFAULT_TIME_LIMIT_MS = 3_000
+        const val DEFAULT_TIME_LIMIT_NS = 3_000_000_000L
 
         // Constants related to metering regions.
         /** No metering region is specified. */
@@ -170,19 +171,19 @@
          *
          * @param frameLimit the maximum number of frames to wait before we give up waiting for
          * this operation to complete.
-         * @param timeLimitMs the maximum time limit in ms we wait before we give up waiting for
+         * @param timeLimitNs the maximum time limit in ms we wait before we give up waiting for
          * this operation to complete.
          *
          * @return [Result3A], which will contain the latest frame number at which the locks were
          * applied or the frame number at which the method returned early because either frame limit
          * or time limit was reached.
          */
-        fun lock3A(
+        suspend fun lock3A(
             aeLockBehavior: Lock3ABehavior? = null,
             afLockBehavior: Lock3ABehavior? = null,
             awbLockBehavior: Lock3ABehavior? = null,
             frameLimit: Int = DEFAULT_FRAME_LIMIT,
-            timeLimitMs: Int = DEFAULT_TIME_LIMIT_MS
+            timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS
         ): Deferred<Result3A>
 
         /**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Lock3ABehavior.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Lock3ABehavior.kt
index 27406f7..5d594f7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Lock3ABehavior.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Lock3ABehavior.kt
@@ -41,4 +41,36 @@
      * Initiate a new scan, and then lock the values once the scan is done.
      */
     AFTER_NEW_SCAN,
-}
\ No newline at end of file
+}
+
+fun Lock3ABehavior?.shouldUnlockAe() =
+    this == Lock3ABehavior.AFTER_NEW_SCAN
+
+fun Lock3ABehavior?.shouldUnlockAf() =
+    this == Lock3ABehavior.AFTER_NEW_SCAN
+
+fun Lock3ABehavior?.shouldUnlockAwb() =
+    this == Lock3ABehavior.AFTER_NEW_SCAN
+
+// For ae and awb if we set the lock = true in the capture request the camera device
+// locks them immediately. So when we want to wait for ae to converge we have to explicitly
+// wait for it to converge.
+fun Lock3ABehavior?.shouldWaitForAeToConverge() =
+    this != null && this != Lock3ABehavior.IMMEDIATE
+
+fun Lock3ABehavior?.shouldWaitForAwbToConverge() =
+    this != null && this != Lock3ABehavior.IMMEDIATE
+
+// TODO(sushilnath@): add the optimization to not wait for af to converge before sending the
+// trigger for modes other than CONTINUOUS_VIDEO. The paragraph below explains the reasoning.
+//
+// For af, if the mode is MACRO, AUTO or CONTINUOUS_PICTURE and we send a capture request to
+// start an af trigger then camera device starts a new scan(for AUTO mode) or waits for the
+// current scan to finish(for CONTINUOUS_PICTURE) and then locks the auto-focus, so if we want
+// to wait for af to converge before locking it, we don't have to explicitly wait for
+// convergence, we can send the trigger right away, but if the mode is CONTINUOUS_VIDEO then
+// sending a request to start a trigger locks the auto focus immediately, so if we want af to
+// converge first then we have to explicitly wait for it.
+// Ref: https://developer.android.com/reference/android/hardware/camera2/CaptureResult#CONTROL_AF_STATE
+fun Lock3ABehavior?.shouldWaitForAfToConverge() =
+    this != null && this != Lock3ABehavior.IMMEDIATE
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
index 9d5c485..2b7aa32 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
@@ -30,6 +30,7 @@
 import kotlinx.coroutines.Deferred
 
 internal val cameraGraphSessionIds = atomic(0)
+
 class CameraGraphSessionImpl(
     private val token: TokenLock.Token,
     private val graphProcessor: GraphProcessor,
@@ -84,14 +85,20 @@
         TODO("Implement setTorch")
     }
 
-    override fun lock3A(
+    override suspend fun lock3A(
         aeLockBehavior: Lock3ABehavior?,
         afLockBehavior: Lock3ABehavior?,
         awbLockBehavior: Lock3ABehavior?,
         frameLimit: Int,
-        timeLimitMs: Int
+        timeLimitNs: Long
     ): Deferred<Result3A> {
-        TODO("Implement lock3A")
+        // TODO(sushilnath): check if the device or the current mode supports lock for each of
+        // ae, af and awb respectively. If not supported return an exception or return early with
+        // the right status code.
+        return controller3A.lock3A(
+            aeLockBehavior, afLockBehavior, awbLockBehavior, frameLimit,
+            timeLimitNs
+        )
     }
 
     override fun lock3A(
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt
index df3b3c2..4de0475 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt
@@ -16,16 +16,28 @@
 
 package androidx.camera.camera2.pipe.impl
 
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER_START
 import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER
 import android.hardware.camera2.CaptureResult
 import android.hardware.camera2.params.MeteringRectangle
 import androidx.annotation.GuardedBy
 import androidx.camera.camera2.pipe.AeMode
 import androidx.camera.camera2.pipe.AfMode
 import androidx.camera.camera2.pipe.AwbMode
+import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraGraph.Constants3A.FRAME_NUMBER_INVALID
+import androidx.camera.camera2.pipe.Lock3ABehavior
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.Status3A
+import androidx.camera.camera2.pipe.impl.Log.debug
+import androidx.camera.camera2.pipe.shouldUnlockAe
+import androidx.camera.camera2.pipe.shouldUnlockAf
+import androidx.camera.camera2.pipe.shouldUnlockAwb
+import androidx.camera.camera2.pipe.shouldWaitForAeToConverge
+import androidx.camera.camera2.pipe.shouldWaitForAfToConverge
+import androidx.camera.camera2.pipe.shouldWaitForAwbToConverge
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.cancel
@@ -38,6 +50,66 @@
     private val graphState3A: GraphState3A,
     private val graphListener3A: Listener3A
 ) {
+    companion object {
+        private val aeConvergedStateList = listOf(
+            CaptureResult.CONTROL_AE_STATE_CONVERGED,
+            CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED,
+            CaptureResult.CONTROL_AE_STATE_LOCKED
+        )
+
+        private val awbConvergedStateList = listOf(
+            CaptureResult.CONTROL_AWB_STATE_CONVERGED,
+            CaptureResult.CONTROL_AWB_STATE_LOCKED
+        )
+
+        private val afConvergedStateList = listOf(
+            CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+            CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED,
+            CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
+            CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
+        )
+
+        private val aeLockedStateList = listOf(CaptureResult.CONTROL_AE_STATE_LOCKED)
+
+        private val awbLockedStateList = listOf(CaptureResult.CONTROL_AWB_STATE_LOCKED)
+
+        private val afLockedStateList = listOf(
+            CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
+            CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
+        )
+
+        val parameterForAfTriggerStart = mapOf<CaptureRequest.Key<*>, Any>(
+            CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_START
+        )
+
+        val parameterForAfTriggerCancel = mapOf<CaptureRequest.Key<*>, Any>(
+            CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL
+        )
+
+        private val result3ASubmitFailed = Result3A(FRAME_NUMBER_INVALID, Status3A.SUBMIT_FAILED)
+
+        private val aeUnlockedStateList = listOf(
+            CaptureResult.CONTROL_AE_STATE_INACTIVE,
+            CaptureResult.CONTROL_AE_STATE_SEARCHING,
+            CaptureResult.CONTROL_AE_STATE_CONVERGED,
+            CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
+        )
+
+        private val afUnlockedStateList = listOf(
+            CaptureResult.CONTROL_AF_STATE_INACTIVE,
+            CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN,
+            CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+            CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+            CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED
+        )
+
+        private val awbUnlockedStateList = listOf(
+            CaptureResult.CONTROL_AWB_STATE_INACTIVE,
+            CaptureResult.CONTROL_AWB_STATE_SEARCHING,
+            CaptureResult.CONTROL_AWB_STATE_CONVERGED
+        )
+    }
+
     // Keep track of the result associated with latest call to update3A. If update3A is called again
     // and the current result is not complete, we will cancel the current result.
     @GuardedBy("this")
@@ -59,7 +131,7 @@
         // Update the 3A state of the graph. This will make sure then when GraphProcessor builds
         // the next request it will apply the 3A parameters corresponding to the updated 3A state
         // to the request.
-        graphState3A.update(aeMode, afMode, awbMode, aeRegions, afRegions, awbRegions)
+        graphState3A.update(aeMode, afMode, awbMode, aeRegions, afRegions, awbRegions, null, null)
         // Try submitting a new repeating request with the 3A parameters corresponding to the new
         // 3A state and corresponding listeners.
         graphProcessor.invalidate()
@@ -110,13 +182,261 @@
 
         if (!graphProcessor.submit(extra3AParams)) {
             graphListener3A.removeListener(listener)
-            return CompletableDeferred(
-                Result3A(FRAME_NUMBER_INVALID, Status3A.SUBMIT_FAILED)
-            )
+            return CompletableDeferred(result3ASubmitFailed)
         }
         return listener.getDeferredResult()
     }
 
+    /**
+     * Given the desired lock behaviors for ae, af and awb, this method, (a) first unlocks them and
+     * wait for them to converge, and then (b) locks them.
+     *
+     * (a) In this step, as needed, we first send a single request with 'af trigger = cancel' to
+     * unlock af, and then a repeating request to unlock ae and awb. We suspend till we receive a
+     * response from the camera that each of the ae, af awb are converged.
+     * (b) In this step, as needed, we submit a repeating request to lock ae and awb, and then a
+     * single request to lock af by setting 'af trigger = start'. Once these requests are submitted
+     * we don't wait further and immediately return a Deferred<Result3A> which gets completed when
+     * the capture result with correct lock states for ae, af and awb is received.
+     *
+     * If we received an error when submitting any of the above requests or if waiting for the
+     * desired 3A state times out then we return early with the appropriate status code.
+     *
+     * Note: the frameLimit and timeLimitNs applies to each of the above steps (a) and (b) and not
+     * as a whole for the whole lock3A method. Thus, in the worst case this method including the
+     * completion of returned Deferred<Result3A> can take 2 * min(time equivalent of frameLimit,
+     * timeLimit) to complete
+     */
+    suspend fun lock3A(
+        aeLockBehavior: Lock3ABehavior? = null,
+        afLockBehavior: Lock3ABehavior? = null,
+        awbLockBehavior: Lock3ABehavior? = null,
+        frameLimit: Int = CameraGraph.DEFAULT_FRAME_LIMIT,
+        timeLimitMsNs: Long? = CameraGraph.DEFAULT_TIME_LIMIT_NS
+    ): Deferred<Result3A> {
+        // If we explicitly need to unlock af first before proceeding to lock it, we need to send
+        // a single request with TRIGGER = TRIGGER_CANCEL so that af can start a fresh scan.
+        if (afLockBehavior.shouldUnlockAf()) {
+            debug { "lock3A - sending a request to unlock af first." }
+            if (!graphProcessor.submit(parameterForAfTriggerCancel)) {
+                return CompletableDeferred(result3ASubmitFailed)
+            }
+        }
+
+        // As needed unlock ae, awb and wait for ae, af and awb to converge.
+        if (aeLockBehavior.shouldWaitForAeToConverge() ||
+            afLockBehavior.shouldWaitForAfToConverge() ||
+            awbLockBehavior.shouldWaitForAwbToConverge()
+        ) {
+            val converged3AExitConditions = createConverged3AExitConditions(
+                aeLockBehavior.shouldWaitForAeToConverge(),
+                afLockBehavior.shouldWaitForAfToConverge(),
+                awbLockBehavior.shouldWaitForAwbToConverge()
+            )
+            val listener = Result3AStateListenerImpl(
+                converged3AExitConditions,
+                frameLimit,
+                timeLimitMsNs
+            )
+            graphListener3A.addListener(listener)
+
+            // If we have to explicitly unlock ae, awb, then update the 3A state of the camera
+            // graph. This is because ae, awb lock values should stay as part of repeating
+            // request to the camera device. For af we need only one single request to trigger it,
+            // leaving it unset in the subsequent requests to the camera device will not affect the
+            // previously sent af trigger.
+            val aeLockValue = if (aeLockBehavior.shouldUnlockAe()) false else null
+            val awbLockValue = if (awbLockBehavior.shouldUnlockAwb()) false else null
+            if (aeLockValue != null || awbLockValue != null) {
+                debug { "lock3A - setting aeLock=$aeLockValue, awbLock=$awbLockValue" }
+                graphState3A.update(
+                    aeLock = aeLockValue,
+                    awbLock = awbLockValue
+                )
+            }
+            graphProcessor.invalidate()
+
+            debug {
+                "lock3A - waiting for" +
+                    (if (aeLockBehavior.shouldWaitForAeToConverge()) " ae" else "") +
+                    (if (afLockBehavior.shouldWaitForAfToConverge()) " af" else "") +
+                    (if (awbLockBehavior.shouldWaitForAwbToConverge()) " awb" else "") +
+                    " to converge before locking them."
+            }
+            val result = listener.getDeferredResult().await()
+            debug {
+                "lock3A - converged at frame number=${result.frameNumber.value}, status=${result
+                    .status}"
+            }
+            // Return immediately if we encounter an error when unlocking and waiting for
+            // convergence.
+            if (result.status != Status3A.OK) {
+                return CompletableDeferred(result)
+            }
+        }
+
+        return lock3ANow(aeLockBehavior, afLockBehavior, awbLockBehavior, frameLimit, timeLimitMsNs)
+    }
+
+    /**
+     * This method unlocks ae, af and awb, as specified by setting the corresponding parameter to
+     * true.
+     *
+     * There are two requests involved in this operation, (a) a single request with af trigger =
+     * cancel, to unlock af, and then (a) a repeating request to unlock ae, awb.
+     */
+    suspend fun unlock3A(
+        ae: Boolean? = null,
+        af: Boolean? = null,
+        awb: Boolean? = null
+    ): Deferred<Result3A> {
+        check(ae == true || af == true || awb == true) { "No parameter has value as true" }
+        // If we explicitly need to unlock af first before proceeding to lock it, we need to send
+        // a single request with TRIGGER = TRIGGER_CANCEL so that af can start a fresh scan.
+        if (af == true) {
+            debug { "unlock3A - sending a request to unlock af first." }
+            if (!graphProcessor.submit(parameterForAfTriggerCancel)) {
+                debug { "unlock3A - request to unlock af failed, returning early." }
+                return CompletableDeferred(result3ASubmitFailed)
+            }
+        }
+
+        // As needed unlock ae, awb and wait for ae, af and awb to converge.
+        val unlocked3AExitConditions = createUnLocked3AExitConditions(
+            ae == true,
+            af == true,
+            awb == true
+        )
+        val listener = Result3AStateListenerImpl(unlocked3AExitConditions)
+        graphListener3A.addListener(listener)
+
+        // Update the 3A state of the camera graph and invalidate the repeating request with the
+        // new state.
+        val aeLockValue = if (ae == true) false else null
+        val awbLockValue = if (awb == true) false else null
+        if (aeLockValue != null || awbLockValue != null) {
+            debug { "unlock3A - updating graph state, aeLock=$aeLockValue, awbLock=$awbLockValue" }
+            graphState3A.update(
+                aeLock = aeLockValue,
+                awbLock = awbLockValue
+            )
+        }
+        graphProcessor.invalidate()
+        return listener.getDeferredResult()
+    }
+
+    private suspend fun lock3ANow(
+        aeLockBehavior: Lock3ABehavior?,
+        afLockBehavior: Lock3ABehavior?,
+        awbLockBehavior: Lock3ABehavior?,
+        frameLimit: Int?,
+        timeLimitMsNs: Long?
+    ): Deferred<Result3A> {
+        val finalAeLockValue = if (aeLockBehavior == null) null else true
+        val finalAwbLockValue = if (awbLockBehavior == null) null else true
+        val locked3AExitConditions = createLocked3AExitConditions(
+            finalAeLockValue != null,
+            afLockBehavior != null,
+            finalAwbLockValue != null
+        )
+
+        var resultForLocked: Deferred<Result3A>? = null
+        if (locked3AExitConditions.isNotEmpty()) {
+            val listener = Result3AStateListenerImpl(
+                locked3AExitConditions,
+                frameLimit,
+                timeLimitMsNs
+            )
+            graphListener3A.addListener(listener)
+            graphState3A.update(aeLock = finalAeLockValue, awbLock = finalAwbLockValue)
+            debug {
+                "lock3A - submitting request with aeLock=$finalAeLockValue , " +
+                    "awbLock=$finalAwbLockValue"
+            }
+            graphProcessor.invalidate()
+            resultForLocked = listener.getDeferredResult()
+        }
+
+        if (afLockBehavior == null) {
+            return resultForLocked!!
+        }
+
+        debug { "lock3A - submitting a request to lock af." }
+        if (!graphProcessor.submit(parameterForAfTriggerStart)) {
+            // TODO(sushilnath@): Change the error code to a more specific code so it's clear
+            // that one of the request in sequence of requests failed and the caller should
+            // unlock 3A to bring the 3A system to an initial state and then try again if they
+            // want to. The other option is to reset or restore the 3A state here.
+            return CompletableDeferred(result3ASubmitFailed)
+        }
+        return resultForLocked!!
+    }
+
+    private fun createConverged3AExitConditions(
+        waitForAeToConverge: Boolean,
+        waitForAfToConverge: Boolean,
+        waitForAwbToConverge: Boolean
+    ): Map<CaptureResult.Key<*>, List<Any>> {
+        if (
+            !waitForAeToConverge && !waitForAfToConverge && !waitForAwbToConverge
+        ) {
+            return mapOf()
+        }
+        val exitConditionMapForConverged = mutableMapOf<CaptureResult.Key<*>, List<Any>>()
+        if (waitForAeToConverge) {
+            exitConditionMapForConverged[CaptureResult.CONTROL_AE_STATE] = aeConvergedStateList
+        }
+        if (waitForAwbToConverge) {
+            exitConditionMapForConverged[CaptureResult.CONTROL_AWB_STATE] = awbConvergedStateList
+        }
+        if (waitForAfToConverge) {
+            exitConditionMapForConverged[CaptureResult.CONTROL_AF_STATE] = afConvergedStateList
+        }
+        return exitConditionMapForConverged
+    }
+
+    private fun createLocked3AExitConditions(
+        waitForAeToLock: Boolean,
+        waitForAfToLock: Boolean,
+        waitForAwbToLock: Boolean
+    ): Map<CaptureResult.Key<*>, List<Any>> {
+        if (!waitForAeToLock && !waitForAfToLock && !waitForAwbToLock) {
+            return mapOf()
+        }
+        val exitConditionMapForLocked = mutableMapOf<CaptureResult.Key<*>, List<Any>>()
+        if (waitForAeToLock) {
+            exitConditionMapForLocked[CaptureResult.CONTROL_AE_STATE] = aeLockedStateList
+        }
+        if (waitForAfToLock) {
+            exitConditionMapForLocked[CaptureResult.CONTROL_AF_STATE] = afLockedStateList
+        }
+        if (waitForAwbToLock) {
+            exitConditionMapForLocked[CaptureResult.CONTROL_AWB_STATE] = awbLockedStateList
+        }
+        return exitConditionMapForLocked
+    }
+
+    private fun createUnLocked3AExitConditions(
+        ae: Boolean,
+        af: Boolean,
+        awb: Boolean
+    ): Map<CaptureResult.Key<*>, List<Any>> {
+        if (!ae && !af && !awb) {
+            return mapOf()
+        }
+        val exitConditionMapForUnLocked = mutableMapOf<CaptureResult.Key<*>, List<Any>>()
+        if (ae) {
+            exitConditionMapForUnLocked[CaptureResult.CONTROL_AE_STATE] = aeUnlockedStateList
+        }
+        if (af) {
+            exitConditionMapForUnLocked[CaptureResult.CONTROL_AF_STATE] = afUnlockedStateList
+        }
+        if (awb) {
+            exitConditionMapForUnLocked[CaptureResult.CONTROL_AWB_STATE] = awbUnlockedStateList
+        }
+        return exitConditionMapForUnLocked
+    }
+
     // We create a map for the 3A modes and the desired values and leave out the keys
     // corresponding to the metering regions. The reason being the camera framework can chose to
     // crop or modify the metering regions as per its constraints. So when we receive at least
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphState3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphState3A.kt
index b69476c..97664c0 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphState3A.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphState3A.kt
@@ -28,6 +28,12 @@
  *
  * This object is used to maintain the key-value pairs for the most recent 3A state that is used
  * when building the requests that are sent to a CameraCaptureSession.
+ *
+ * The state is comprised of the modes, metering regions for ae, af and awb, and locks for ae and
+ * awb. We don't track the lock for af since af lock is achieved by setting 'af trigger = start' in
+ * in a request and then omitting the af trigger field in the subsequent requests doesn't disturb
+ * the af state. However for ae and awb, the lock type is boolean and should be explicitly set to
+ * 'true' in the subsequent requests once we have locked ae/awb and want them to stay locked.
  */
 @CameraGraphScope
 class GraphState3A @Inject constructor() {
@@ -37,14 +43,18 @@
     private var aeRegions: List<MeteringRectangle>? = null
     private var afRegions: List<MeteringRectangle>? = null
     private var awbRegions: List<MeteringRectangle>? = null
+    private var aeLock: Boolean? = null
+    private var awbLock: Boolean? = null
 
     fun update(
-        aeMode: AeMode?,
-        afMode: AfMode?,
-        awbMode: AwbMode?,
-        aeRegions: List<MeteringRectangle>?,
-        afRegions: List<MeteringRectangle>?,
-        awbRegions: List<MeteringRectangle>?
+        aeMode: AeMode? = null,
+        afMode: AfMode? = null,
+        awbMode: AwbMode? = null,
+        aeRegions: List<MeteringRectangle>? = null,
+        afRegions: List<MeteringRectangle>? = null,
+        awbRegions: List<MeteringRectangle>? = null,
+        aeLock: Boolean? = null,
+        awbLock: Boolean? = null
     ) {
         synchronized(this) {
             aeMode?.let { this.aeMode = it }
@@ -53,6 +63,8 @@
             aeRegions?.let { this.aeRegions = it }
             afRegions?.let { this.afRegions = it }
             awbRegions?.let { this.awbRegions = it }
+            aeLock?.let { this.aeLock = it }
+            awbLock?.let { this.awbLock = it }
         }
     }
 
@@ -65,6 +77,8 @@
             aeRegions?.let { map.put(CaptureRequest.CONTROL_AE_REGIONS, it.toTypedArray()) }
             afRegions?.let { map.put(CaptureRequest.CONTROL_AF_REGIONS, it.toTypedArray()) }
             awbRegions?.let { map.put(CaptureRequest.CONTROL_AWB_REGIONS, it.toTypedArray()) }
+            aeLock?.let { map.put(CaptureRequest.CONTROL_AE_LOCK, it) }
+            awbLock?.let { map.put(CaptureRequest.CONTROL_AWB_LOCK, it) }
             return map
         }
     }
@@ -77,6 +91,8 @@
             aeRegions?.let { builder.set(CaptureRequest.CONTROL_AE_REGIONS, it.toTypedArray()) }
             afRegions?.let { builder.set(CaptureRequest.CONTROL_AF_REGIONS, it.toTypedArray()) }
             awbRegions?.let { builder.set(CaptureRequest.CONTROL_AWB_REGIONS, it.toTypedArray()) }
+            aeLock?.let { builder.set(CaptureRequest.CONTROL_AE_LOCK, it) }
+            awbLock?.let { builder.set(CaptureRequest.CONTROL_AWB_LOCK, it) }
         }
     }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3ALock3ATest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3ALock3ATest.kt
new file mode 100644
index 0000000..8d837e7
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3ALock3ATest.kt
@@ -0,0 +1,641 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.impl
+
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.os.Build
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Lock3ABehavior
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestNumber
+import androidx.camera.camera2.pipe.Status3A
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
+import androidx.camera.camera2.pipe.testing.FakeFrameMetadata
+import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
+import androidx.camera.camera2.pipe.testing.FakeRequestMetadata
+import androidx.camera.camera2.pipe.testing.FakeRequestProcessor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(CameraPipeRobolectricTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class Controller3ALock3ATest {
+    private val graphProcessor = FakeGraphProcessor()
+    private val graphState3A = GraphState3A()
+    private val requestProcessor = FakeRequestProcessor(graphState3A)
+    private val listener3A = Listener3A()
+    private val controller3A = Controller3A(graphProcessor, graphState3A, listener3A)
+
+    @Test
+    fun testAfImmediateAeImmediate(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val result = controller3A.lock3A(
+            afLockBehavior = Lock3ABehavior.IMMEDIATE,
+            aeLockBehavior = Lock3ABehavior.IMMEDIATE
+        )
+        assertThat(result.isCompleted).isFalse()
+
+        // Since requirement of to lock both AE and AF immediately, the requests to lock AE and AF
+        // are sent right away. The result of lock3A call will complete once AE and AF have reached
+        // their desired states. In this response i.e cameraResponse1, AF is still scanning so the
+        // result won't be complete.
+        val cameraResponse = GlobalScope.async {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_PASSIVE_SCAN,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        cameraResponse.await()
+        assertThat(result.isCompleted).isFalse()
+
+        // One we we are notified that the AE and AF are in locked state, the result of lock3A call
+        // will complete.
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_FOCUSED_LOCKED,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+        // We not check if the correct sequence of requests were submitted by lock3A call. The
+        // request should be a repeating request to lock AE.
+        val request1 = requestProcessor.nextEvent().request
+        assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+
+        // The second request should be a single request to lock AF.
+        val request2 = requestProcessor.nextEvent().request
+        assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_START
+        )
+        assertThat(request2.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+    }
+
+    @Test
+    fun testAfImmediateAeAfterCurrentScan(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val lock3AAsyncTask = GlobalScope.async {
+            controller3A.lock3A(
+                afLockBehavior = Lock3ABehavior.IMMEDIATE,
+                aeLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN
+            )
+        }
+        assertThat(lock3AAsyncTask.isCompleted).isFalse()
+        // Launch a task to repeatedly invoke a given capture result.
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+                            CaptureResult.CONTROL_AE_STATE to
+                                CaptureResult.CONTROL_AE_STATE_CONVERGED
+                        )
+                    )
+                )
+                delay(Companion.FRAME_RATE_MS)
+            }
+        }
+
+        val result = lock3AAsyncTask.await()
+        // Result of lock3A call shouldn't be complete yet since the AE and AF are not locked yet.
+        assertThat(result.isCompleted).isFalse()
+
+        // Check the correctness of the requests submitted by lock3A.
+        // One repeating request was sent to monitor the state of AE to get converged.
+        requestProcessor.nextEvent().request
+        // Once AE is converged, another repeatingrequest is sent to lock AE.
+        val request1 = requestProcessor.nextEvent().request
+        assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_FOCUSED_LOCKED,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+        // A single request to lock AF must have been used as well.
+        val request2 = requestProcessor.nextEvent().request
+        assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_START
+        )
+    }
+
+    @Test
+    fun testAfImmediateAeAfterNewScan(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val lock3AAsyncTask = GlobalScope.async {
+            controller3A.lock3A(
+                afLockBehavior = Lock3ABehavior.IMMEDIATE,
+                aeLockBehavior = Lock3ABehavior.AFTER_NEW_SCAN
+            )
+        }
+        assertThat(lock3AAsyncTask.isCompleted).isFalse()
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+                            CaptureResult.CONTROL_AE_STATE to
+                                CaptureResult.CONTROL_AE_STATE_CONVERGED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = lock3AAsyncTask.await()
+        assertThat(result.isCompleted).isFalse()
+
+        // For a new AE scan we first send a request to unlock AE just in case it was
+        // previously or internally locked.
+        val request1 = requestProcessor.nextEvent().request
+        assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            false
+        )
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_FOCUSED_LOCKED,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+        // There should be one more request to lock AE after new scan is done.
+        val request2 = requestProcessor.nextEvent().request
+        assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+
+        // And one request to lock AF.
+        val request3 = requestProcessor.nextEvent().request
+        assertThat(request3!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_START
+        )
+        assertThat(request3.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+    }
+
+    @Test
+    fun testAfAfterCurrentScanAeImmediate(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val lock3AAsyncTask = GlobalScope.async {
+            controller3A.lock3A(
+                afLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
+                aeLockBehavior = Lock3ABehavior.IMMEDIATE
+            )
+        }
+        assertThat(lock3AAsyncTask.isCompleted).isFalse()
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+                            CaptureResult.CONTROL_AE_STATE to
+                                CaptureResult.CONTROL_AE_STATE_CONVERGED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = lock3AAsyncTask.await()
+        assertThat(result.isCompleted).isFalse()
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_FOCUSED_LOCKED,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+        // There should be one request to monitor AF to finish it's scan.
+        requestProcessor.nextEvent()
+        // One request to lock AE
+        val request2 = requestProcessor.nextEvent().request
+        assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+
+        // And one request to lock AF.
+        val request3 = requestProcessor.nextEvent().request
+        assertThat(request3!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_START
+        )
+        assertThat(request3.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+    }
+
+    @Test
+    fun testAfAfterNewScanScanAeImmediate(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val lock3AAsyncTask = GlobalScope.async {
+            controller3A.lock3A(
+                afLockBehavior = Lock3ABehavior.AFTER_NEW_SCAN,
+                aeLockBehavior = Lock3ABehavior.IMMEDIATE
+            )
+        }
+        assertThat(lock3AAsyncTask.isCompleted).isFalse()
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+                            CaptureResult.CONTROL_AE_STATE to
+                                CaptureResult.CONTROL_AE_STATE_CONVERGED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = lock3AAsyncTask.await()
+        assertThat(result.isCompleted).isFalse()
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_FOCUSED_LOCKED,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+        // One request to cancel AF to start a new scan.
+        val request1 = requestProcessor.nextEvent().request
+        assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
+        )
+        // There should be one request to monitor AF to finish it's scan.
+        requestProcessor.nextEvent()
+
+        // There should be one request to monitor lock AE.
+        val request2 = requestProcessor.nextEvent().request
+        assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+
+        // And one request to lock AF.
+        val request3 = requestProcessor.nextEvent().request
+        assertThat(request3!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_START
+        )
+        assertThat(request3.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+    }
+
+    @Test
+    fun testAfAfterCurrentScanAeAfterCurrentScan(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val lock3AAsyncTask = GlobalScope.async {
+            controller3A.lock3A(
+                afLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN,
+                aeLockBehavior = Lock3ABehavior.AFTER_CURRENT_SCAN
+            )
+        }
+        assertThat(lock3AAsyncTask.isCompleted).isFalse()
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+                            CaptureResult.CONTROL_AE_STATE to
+                                CaptureResult.CONTROL_AE_STATE_CONVERGED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = lock3AAsyncTask.await()
+        assertThat(result.isCompleted).isFalse()
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_FOCUSED_LOCKED,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+        // There should be one request to monitor AF to finish it's scan.
+        requestProcessor.nextEvent()
+        // One request to lock AE
+        val request2 = requestProcessor.nextEvent().request
+        assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+
+        // And one request to lock AF.
+        val request3 = requestProcessor.nextEvent().request
+        assertThat(request3!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_START
+        )
+        assertThat(request3.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+    }
+
+    @Test
+    fun testAfAfterNewScanScanAeAfterNewScan(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val lock3AAsyncTask = GlobalScope.async {
+            controller3A.lock3A(
+                afLockBehavior = Lock3ABehavior.AFTER_NEW_SCAN,
+                aeLockBehavior = Lock3ABehavior.AFTER_NEW_SCAN
+            )
+        }
+        assertThat(lock3AAsyncTask.isCompleted).isFalse()
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+                            CaptureResult.CONTROL_AE_STATE to
+                                CaptureResult.CONTROL_AE_STATE_CONVERGED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = lock3AAsyncTask.await()
+        assertThat(result.isCompleted).isFalse()
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult
+                            .CONTROL_AF_STATE_FOCUSED_LOCKED,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+        // One request to cancel AF to start a new scan.
+        val request1 = requestProcessor.nextEvent().request
+        assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
+        )
+        // There should be one request to unlock AE and monitor the current AF scan to finish.
+        val request2 = requestProcessor.nextEvent().request
+        assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            false
+        )
+
+        // There should be one request to monitor lock AE.
+        val request3 = requestProcessor.nextEvent().request
+        assertThat(request3!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+
+        // And one request to lock AF.
+        val request4 = requestProcessor.nextEvent().request
+        assertThat(request4!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+            CaptureRequest.CONTROL_AF_TRIGGER_START
+        )
+        assertThat(request4.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK]).isEqualTo(
+            true
+        )
+    }
+
+    private fun initGraphProcessor() {
+        graphProcessor.attach(requestProcessor)
+        graphProcessor.setRepeating(Request(streams = listOf(StreamId(1))))
+    }
+
+    companion object {
+        // The time duration in milliseconds between two frame results.
+        private const val FRAME_RATE_MS = 33L
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3ASubmit3ATest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3ASubmit3ATest.kt
index bf5ea3f..c87f5c0 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3ASubmit3ATest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3ASubmit3ATest.kt
@@ -45,9 +45,9 @@
 @RunWith(CameraPipeRobolectricTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class Controller3ASubmit3ATest {
-    private val requestProcessor = FakeRequestProcessor()
     private val graphProcessor = FakeGraphProcessor()
     private val graphState3A = GraphState3A()
+    private val requestProcessor = FakeRequestProcessor(graphState3A)
     private val listener3A = Listener3A()
     private val controller3A = Controller3A(graphProcessor, graphState3A, listener3A)
 
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AUnlock3ATest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AUnlock3ATest.kt
new file mode 100644
index 0000000..ad3d24c
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AUnlock3ATest.kt
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.impl
+
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.os.Build
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestNumber
+import androidx.camera.camera2.pipe.Status3A
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
+import androidx.camera.camera2.pipe.testing.FakeFrameMetadata
+import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
+import androidx.camera.camera2.pipe.testing.FakeRequestMetadata
+import androidx.camera.camera2.pipe.testing.FakeRequestProcessor
+import com.google.common.truth.Truth
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(CameraPipeRobolectricTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class Controller3AUnlock3ATest {
+    private val graphProcessor = FakeGraphProcessor()
+    private val graphState3A = GraphState3A()
+    private val requestProcessor = FakeRequestProcessor(graphState3A)
+    private val listener3A = Listener3A()
+    private val controller3A = Controller3A(graphProcessor, graphState3A, listener3A)
+
+    @Test
+    fun testUnlockAe(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val unLock3AAsyncTask = GlobalScope.async {
+            controller3A.unlock3A(ae = true)
+        }
+
+        // Launch a task to repeatedly invoke a given capture result.
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AE_STATE to
+                                CaptureResult.CONTROL_AE_STATE_LOCKED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = unLock3AAsyncTask.await()
+        // Result of unlock3A call shouldn't be complete yet since the AE is locked.
+        Truth.assertThat(result.isCompleted).isFalse()
+
+        // There should be one request to lock AE.
+        val request1 = requestProcessor.nextEvent().request
+        Truth.assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK])
+            .isEqualTo(false)
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_SEARCHING
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        Truth.assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        Truth.assertThat(result3A.status).isEqualTo(Status3A.OK)
+    }
+
+    @Test
+    fun testUnlockAf(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val unLock3AAsyncTask = GlobalScope.async { controller3A.unlock3A(af = true) }
+
+        // Launch a task to repeatedly invoke a given capture result.
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = unLock3AAsyncTask.await()
+        // Result of unlock3A call shouldn't be complete yet since the AF is locked.
+        Truth.assertThat(result.isCompleted).isFalse()
+
+        // There should be one request to unlock AF.
+        val request1 = requestProcessor.nextEvent().request
+        Truth.assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER])
+            .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_INACTIVE
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        Truth.assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        Truth.assertThat(result3A.status).isEqualTo(Status3A.OK)
+    }
+
+    @Test
+    fun testUnlockAwb(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val unLock3AAsyncTask = GlobalScope.async {
+            controller3A.unlock3A(awb = true)
+        }
+
+        // Launch a task to repeatedly invoke a given capture result.
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AWB_STATE to
+                                CaptureResult.CONTROL_AWB_STATE_LOCKED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = unLock3AAsyncTask.await()
+        // Result of unlock3A call shouldn't be complete yet since the AWB is locked.
+        Truth.assertThat(result.isCompleted).isFalse()
+
+        // There should be one request to lock AWB.
+        val request1 = requestProcessor.nextEvent().request
+        Truth.assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AWB_LOCK])
+            .isEqualTo(false)
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AWB_STATE to CaptureResult.CONTROL_AWB_STATE_SEARCHING
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        Truth.assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        Truth.assertThat(result3A.status).isEqualTo(Status3A.OK)
+    }
+
+    @Test
+    fun testUnlockAeAf(): Unit = runBlocking {
+        initGraphProcessor()
+
+        val unLock3AAsyncTask = GlobalScope.async { controller3A.unlock3A(ae = true, af = true) }
+
+        // Launch a task to repeatedly invoke a given capture result.
+        GlobalScope.launch {
+            while (true) {
+                listener3A.onRequestSequenceCreated(
+                    FakeRequestMetadata(
+                        requestNumber = RequestNumber(1)
+                    )
+                )
+                listener3A.onPartialCaptureResult(
+                    FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                    FrameNumber(101L),
+                    FakeFrameMetadata(
+                        frameNumber = FrameNumber(101L),
+                        resultMetadata = mapOf(
+                            CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_LOCKED,
+                            CaptureResult.CONTROL_AF_STATE to
+                                CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
+                        )
+                    )
+                )
+                delay(FRAME_RATE_MS)
+            }
+        }
+
+        val result = unLock3AAsyncTask.await()
+        // Result of unlock3A call shouldn't be complete yet since the AF is locked.
+        Truth.assertThat(result.isCompleted).isFalse()
+
+        // There should be one request to unlock AF.
+        val request1 = requestProcessor.nextEvent().request
+        Truth.assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER])
+            .isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
+        // Then request to unlock AE.
+        val request2 = requestProcessor.nextEvent().request
+        Truth.assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK])
+            .isEqualTo(false)
+
+        GlobalScope.launch {
+            listener3A.onRequestSequenceCreated(
+                FakeRequestMetadata(
+                    requestNumber = RequestNumber(1)
+                )
+            )
+            listener3A.onPartialCaptureResult(
+                FakeRequestMetadata(requestNumber = RequestNumber(1)),
+                FrameNumber(101L),
+                FakeFrameMetadata(
+                    frameNumber = FrameNumber(101L),
+                    resultMetadata = mapOf(
+                        CaptureResult.CONTROL_AF_STATE to CaptureResult.CONTROL_AF_STATE_INACTIVE,
+                        CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_SEARCHING
+                    )
+                )
+            )
+        }
+
+        val result3A = result.await()
+        Truth.assertThat(result3A.frameNumber.value).isEqualTo(101L)
+        Truth.assertThat(result3A.status).isEqualTo(Status3A.OK)
+    }
+
+    private fun initGraphProcessor() {
+        graphProcessor.attach(requestProcessor)
+        graphProcessor.setRepeating(Request(streams = listOf(StreamId(1))))
+    }
+
+    companion object {
+        // The time duration in milliseconds between two frame results.
+        private const val FRAME_RATE_MS = 33L
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AUpdate3ATest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AUpdate3ATest.kt
index 833e885..48b00fa 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AUpdate3ATest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AUpdate3ATest.kt
@@ -24,12 +24,15 @@
 import androidx.camera.camera2.pipe.AfMode
 import androidx.camera.camera2.pipe.AwbMode
 import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.RequestNumber
 import androidx.camera.camera2.pipe.Status3A
+import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
 import androidx.camera.camera2.pipe.testing.FakeFrameMetadata
 import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
 import androidx.camera.camera2.pipe.testing.FakeRequestMetadata
+import androidx.camera.camera2.pipe.testing.FakeRequestProcessor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,11 +48,14 @@
 class Controller3AUpdate3ATest {
     private val graphProcessor = FakeGraphProcessor()
     private val graphState3A = GraphState3A()
+    private val requestProcessor = FakeRequestProcessor(graphState3A)
     private val listener3A = Listener3A()
     private val controller3A = Controller3A(graphProcessor, graphState3A, listener3A)
 
     @Test
     fun testUpdate3AUpdatesState3A() {
+        initGraphProcessor()
+
         val result = controller3A.update3A(afMode = AfMode.OFF)
         assertThat(graphState3A.readState()[CaptureRequest.CONTROL_AF_MODE]).isEqualTo(
             CaptureRequest.CONTROL_AE_MODE_OFF
@@ -60,6 +66,8 @@
     @ExperimentalCoroutinesApi
     @Test
     fun testUpdate3ACancelsPreviousInProgressUpdate() {
+        initGraphProcessor()
+
         val result = controller3A.update3A(afMode = AfMode.OFF)
         // Invoking update3A before the previous one is complete will cancel the result of the
         // previous call.
@@ -69,6 +77,8 @@
 
     @Test
     fun testAfModeUpdate(): Unit = runBlocking {
+        initGraphProcessor()
+
         val result = controller3A.update3A(afMode = AfMode.OFF)
         GlobalScope.launch {
             listener3A.onRequestSequenceCreated(
@@ -94,6 +104,8 @@
 
     @Test
     fun testAeModeUpdate(): Unit = runBlocking {
+        initGraphProcessor()
+
         val result = controller3A.update3A(aeMode = AeMode.ON_ALWAYS_FLASH)
         GlobalScope.launch {
             listener3A.onRequestSequenceCreated(
@@ -120,6 +132,8 @@
 
     @Test
     fun testAwbModeUpdate(): Unit = runBlocking {
+        initGraphProcessor()
+
         val result = controller3A.update3A(awbMode = AwbMode.CLOUDY_DAYLIGHT)
         GlobalScope.launch {
             listener3A.onRequestSequenceCreated(
@@ -146,6 +160,8 @@
 
     @Test
     fun testAfRegionsUpdate(): Unit = runBlocking {
+        initGraphProcessor()
+
         val result = controller3A.update3A(afRegions = listOf(MeteringRectangle(1, 1, 100, 100, 2)))
         GlobalScope.launch {
             listener3A.onRequestSequenceCreated(
@@ -172,6 +188,8 @@
 
     @Test
     fun testAeRegionsUpdate(): Unit = runBlocking {
+        initGraphProcessor()
+
         val result = controller3A.update3A(aeRegions = listOf(MeteringRectangle(1, 1, 100, 100, 2)))
         GlobalScope.launch {
             listener3A.onRequestSequenceCreated(
@@ -198,6 +216,8 @@
 
     @Test
     fun testAwbRegionsUpdate(): Unit = runBlocking {
+        initGraphProcessor()
+
         val result = controller3A.update3A(
             awbRegions = listOf(
                 MeteringRectangle(1, 1, 100, 100, 2)
@@ -225,4 +245,9 @@
         assertThat(result3A.frameNumber.value).isEqualTo(101L)
         assertThat(result3A.status).isEqualTo(Status3A.OK)
     }
+
+    private fun initGraphProcessor() {
+        graphProcessor.attach(requestProcessor)
+        graphProcessor.setRepeating(Request(streams = listOf(StreamId(1))))
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt
index abdc307..23c63d1 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt
@@ -36,8 +36,8 @@
 class GraphProcessorTest {
     private val globalListener = FakeRequestListener()
 
-    private val fakeProcessor1 = FakeRequestProcessor()
-    private val fakeProcessor2 = FakeRequestProcessor()
+    private val fakeProcessor1 = FakeRequestProcessor(GraphState3A())
+    private val fakeProcessor2 = FakeRequestProcessor(GraphState3A())
 
     private val requestListener1 = FakeRequestListener()
     private val request1 = Request(listOf(StreamId(0)), listeners = listOf(requestListener1))
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt
index 730a1dd..f8007e9 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt
@@ -105,7 +105,7 @@
             virtualSessionState = VirtualSessionState(
                 FakeGraphProcessor(),
                 sessionFactory,
-                FakeRequestProcessor(),
+                FakeRequestProcessor(GraphState3A()),
                 this
             )
         )
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
index 139f0b0..5ab64bc 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
@@ -93,5 +93,6 @@
     }
 
     override fun invalidate() {
+        processor!!.setRepeating(repeatingRequest!!, mapOf(), false)
     }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
index f7e3bed..0f446d5 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
@@ -20,6 +20,7 @@
 import android.view.Surface
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.impl.GraphState3A
 import androidx.camera.camera2.pipe.impl.RequestProcessor
 import androidx.camera.camera2.pipe.impl.TokenLock
 import androidx.camera.camera2.pipe.impl.TokenLockImpl
@@ -30,7 +31,8 @@
 /**
  * Fake implementation of a [RequestProcessor] for tests.
  */
-class FakeRequestProcessor : RequestProcessor, RequestProcessor.Factory {
+class FakeRequestProcessor(private val graphState3A: GraphState3A) :
+    RequestProcessor, RequestProcessor.Factory {
     private val eventChannel = Channel<Event>(Channel.UNLIMITED)
 
     val requestQueue: MutableList<FakeRequest> = mutableListOf()
@@ -71,7 +73,7 @@
         requireSurfacesForAllStreams: Boolean
     ): Boolean {
         val fakeRequest =
-            FakeRequest(listOf(request), extraRequestParameters, requireSurfacesForAllStreams)
+            createFakeRequest(listOf(request), extraRequestParameters, requireSurfacesForAllStreams)
 
         if (rejectRequests || closeInvoked) {
             check(eventChannel.offer(Event(request = fakeRequest, rejected = true)))
@@ -90,7 +92,7 @@
         requireSurfacesForAllStreams: Boolean
     ): Boolean {
         val fakeRequest =
-            FakeRequest(requests, extraRequestParameters, requireSurfacesForAllStreams)
+            createFakeRequest(requests, extraRequestParameters, requireSurfacesForAllStreams)
         if (rejectRequests || closeInvoked) {
             check(eventChannel.offer(Event(request = fakeRequest, rejected = true)))
             return false
@@ -108,7 +110,7 @@
         requireSurfacesForAllStreams: Boolean
     ): Boolean {
         val fakeRequest =
-            FakeRequest(listOf(request), extraRequestParameters, requireSurfacesForAllStreams)
+            createFakeRequest(listOf(request), extraRequestParameters, requireSurfacesForAllStreams)
         if (rejectRequests || closeInvoked) {
             check(eventChannel.offer(Event(request = fakeRequest, rejected = true)))
             return false
@@ -137,9 +139,20 @@
     /**
      * Get the next event from queue with an option to specify a timeout for tests.
      */
-    suspend fun nextEvent(timeMillis: Long = 100): Event = withTimeout(timeMillis) {
+    suspend fun nextEvent(timeMillis: Long = 500): Event = withTimeout(timeMillis) {
         eventChannel.receive()
     }
+
+    private fun createFakeRequest(
+        burst: List<Request>,
+        extraRequestParameters: Map<CaptureRequest.Key<*>, Any>,
+        requireStreams: Boolean
+    ): FakeRequest {
+        val parameterMap = mutableMapOf<CaptureRequest.Key<*>, Any>()
+        parameterMap.putAll(graphState3A.readState())
+        parameterMap.putAll(extraRequestParameters)
+        return FakeRequest(burst, parameterMap, requireStreams)
+    }
 }
 
 data class Event(
diff --git a/camera/camera-camera2/build.gradle b/camera/camera-camera2/build.gradle
index 33822d5..a5ffd1e 100644
--- a/camera/camera-camera2/build.gradle
+++ b/camera/camera-camera2/build.gradle
@@ -63,7 +63,7 @@
     androidTestImplementation(KOTLIN_COROUTINES_ANDROID)
     androidTestImplementation(project(":annotation:annotation-experimental"))
     androidTestImplementation(project(":internal-testutils-truth"))
-    androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.13.1")
+    androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.15.0")
 }
 android {
     defaultConfig {
diff --git a/camera/camera-core/api/public_plus_experimental_1.0.0-beta12.txt b/camera/camera-core/api/public_plus_experimental_1.0.0-beta12.txt
index e432572..45f7159b 100644
--- a/camera/camera-core/api/public_plus_experimental_1.0.0-beta12.txt
+++ b/camera/camera-core/api/public_plus_experimental_1.0.0-beta12.txt
@@ -69,12 +69,14 @@
   }
 
   public final class CameraXConfig {
+    method @androidx.camera.core.ExperimentalAvailableCamerasLimiter public androidx.camera.core.CameraSelector? getAvailableCamerasSelector(androidx.camera.core.CameraSelector?);
     method @androidx.camera.core.ExperimentalLogging public int getMinimumLoggingLevel();
   }
 
   public static final class CameraXConfig.Builder {
     method public androidx.camera.core.CameraXConfig build();
     method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
+    method @androidx.camera.core.ExperimentalAvailableCamerasLimiter public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasSelector(androidx.camera.core.CameraSelector);
     method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
     method @androidx.camera.core.ExperimentalLogging public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
     method @androidx.camera.core.ExperimentalCustomizableThreads public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
@@ -88,6 +90,9 @@
     ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
   }
 
+  @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAvailableCamerasSelector {
+  }
+
   @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraFilter {
   }
 
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index e432572..b10368c 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -69,12 +69,14 @@
   }
 
   public final class CameraXConfig {
+    method @androidx.camera.core.ExperimentalAvailableCamerasLimiter public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
     method @androidx.camera.core.ExperimentalLogging public int getMinimumLoggingLevel();
   }
 
   public static final class CameraXConfig.Builder {
     method public androidx.camera.core.CameraXConfig build();
     method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
+    method @androidx.camera.core.ExperimentalAvailableCamerasLimiter public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
     method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
     method @androidx.camera.core.ExperimentalLogging public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
     method @androidx.camera.core.ExperimentalCustomizableThreads public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
@@ -88,6 +90,9 @@
     ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
   }
 
+  @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAvailableCamerasLimiter {
+  }
+
   @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraFilter {
   }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java
index 13e5d31..e4d5954 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java
@@ -32,7 +32,8 @@
 import java.util.List;
 
 /**
- * A set of requirements and priorities used to select a camera.
+ * A set of requirements and priorities used to select a camera or return a filtered set of
+ * cameras.
  */
 public final class CameraSelector {
 
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 ea5eea6..2d630a2 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
@@ -31,6 +31,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.experimental.UseExperimental;
 import androidx.camera.core.impl.CameraDeviceSurfaceManager;
 import androidx.camera.core.impl.CameraFactory;
 import androidx.camera.core.impl.CameraInternal;
@@ -538,6 +539,7 @@
     /**
      * Initializes camera stack on the given thread and retry recursively until timeout.
      */
+    @UseExperimental(markerClass = ExperimentalAvailableCamerasLimiter.class)
     private void initAndRetryRecursively(
             @NonNull Executor cameraExecutor,
             long startMs,
@@ -562,8 +564,10 @@
                 CameraThreadConfig cameraThreadConfig = CameraThreadConfig.create(mCameraExecutor,
                         mSchedulerHandler);
 
+                CameraSelector availableCamerasLimiter =
+                        mCameraXConfig.getAvailableCamerasLimiter(null);
                 mCameraFactory = cameraFactoryProvider.newInstance(mAppContext,
-                        cameraThreadConfig, null);
+                        cameraThreadConfig, availableCamerasLimiter);
                 CameraDeviceSurfaceManager.Provider surfaceManagerProvider =
                         mCameraXConfig.getDeviceSurfaceManagerProvider(null);
                 if (surfaceManagerProvider == null) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
index 7290fb4..4ac5e41 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraXConfig.java
@@ -101,6 +101,10 @@
             Option.create(
                     "camerax.core.appConfig.minimumLoggingLevel",
                     int.class);
+    static final Option<CameraSelector> OPTION_AVAILABLE_CAMERAS_LIMITER =
+            Option.create(
+                    "camerax.core.appConfig.availableCamerasLimiter",
+                    CameraSelector.class);
 
     // *********************************************************************************************
 
@@ -178,6 +182,16 @@
         return mConfig.retrieveOption(OPTION_MIN_LOGGING_LEVEL, Logger.DEFAULT_MIN_LOG_LEVEL);
     }
 
+    /**
+     * Returns the {@link CameraSelector} used to determine the available cameras.
+     */
+    @ExperimentalAvailableCamerasLimiter
+    @Nullable
+    public CameraSelector getAvailableCamerasLimiter(@Nullable CameraSelector valueIfMissing) {
+        return mConfig.retrieveOption(OPTION_AVAILABLE_CAMERAS_LIMITER, valueIfMissing);
+    }
+
+
     /** @hide */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
@@ -329,6 +343,32 @@
             return this;
         }
 
+        /**
+         * Sets a {@link CameraSelector} to determine the available cameras which defines
+         * which cameras can be used in the application.
+         *
+         * <p>Only cameras selected by this CameraSelector can be used in the applications. If
+         * the application binds the use cases with a CameraSelector that selects a unavailable
+         * camera, a {@link IllegalArgumentException} will be thrown.
+         *
+         * <p>This configuration can help CameraX optimize the latency of CameraX initialization.
+         * The tasks CameraX initialization performs include enumerating cameras, querying
+         * CameraCharacteristics and retrieving properties preparing for resolution determination.
+         * On some low end devices, these could take significant amount of time. Using the API
+         * can avoid the initialization of unnecessary cameras and speed up the time for camera
+         * start-up. For example, if the application uses only back cameras, it can set this
+         * configuration by CameraSelector.DEFAULT_BACK_CAMERA and then CameraX will avoid
+         * initializing front cameras to reduce the latency.
+         */
+        @ExperimentalAvailableCamerasLimiter
+        @NonNull
+        public Builder setAvailableCamerasLimiter(
+                @NonNull CameraSelector availableCameraSelector) {
+            getMutableConfig().insertOption(OPTION_AVAILABLE_CAMERAS_LIMITER,
+                    availableCameraSelector);
+            return this;
+        }
+
         @NonNull
         private MutableConfig getMutableConfig() {
             return mMutableConfig;
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalAvailableCamerasLimiter.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalAvailableCamerasLimiter.java
new file mode 100644
index 0000000..99fc74f
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalAvailableCamerasLimiter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.experimental.Experimental;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Denotes that the annotated method uses an experimental API that configures CameraX to
+ * limit the available cameras applications can use in order to optimize the initialization
+ * latency.
+ *
+ * <p>Once the configuration is enabled, only cameras selected by this CameraSelector can be used
+ * in the applications. If the application binds the use cases with a CameraSelector that selects
+ * a unavailable camera, a {@link IllegalArgumentException} will be thrown.
+ *
+ * <p>CameraX initialization performs tasks including enumerating cameras, querying
+ * CameraCharacteristics and retrieving properties preparing for resolution determination. On
+ * some low end devices, these could take significant amount of time. Using the API can avoid the
+ * initialization of unnecessary cameras and speed up the time for camera start-up. For example,
+ * if the application uses only back cameras, it can set this configuration by
+ * CameraSelector.DEFAULT_BACK_CAMERA and then CameraX will avoid initializing front cameras to
+ * reduce the latency.
+ */
+@Retention(CLASS)
+@Experimental
+public @interface ExperimentalAvailableCamerasLimiter {
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraFactory.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraFactory.java
index 3efbda6..1e87b19 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraFactory.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraFactory.java
@@ -40,14 +40,14 @@
          *
          * @param context the android context
          * @param threadConfig the thread config to run the camera operations
-         * @param availableCamerasSelector a CameraSelector used to specify which cameras will be
+         * @param availableCamerasLimiter a CameraSelector used to specify which cameras will be
          *                                 loaded and available to CameraX.
          * @return the factory instance
          * @throws InitializationException if it fails to create the factory.
          */
         @NonNull CameraFactory newInstance(@NonNull Context context,
                 @NonNull CameraThreadConfig threadConfig,
-                @Nullable CameraSelector availableCamerasSelector) throws InitializationException;
+                @Nullable CameraSelector availableCamerasLimiter) throws InitializationException;
     }
 
     /**
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java b/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java
index d82f3f7..656a101 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraXConfigTest.java
@@ -96,4 +96,13 @@
         final Integer minLoggingLevel = cameraXConfig.getMinimumLoggingLevel();
         assertThat(minLoggingLevel).isEqualTo(Logger.DEFAULT_MIN_LOG_LEVEL);
     }
+
+    @Test
+    public void canGetAvailableCamerasSelector() {
+        CameraSelector cameraSelector = new CameraSelector.Builder().build();
+        CameraXConfig cameraXConfig = new CameraXConfig.Builder()
+                .setAvailableCamerasLimiter(cameraSelector)
+                .build();
+        assertThat(cameraXConfig.getAvailableCamerasLimiter(null)).isEqualTo(cameraSelector);
+    }
 }
diff --git a/camera/camera-lifecycle/build.gradle b/camera/camera-lifecycle/build.gradle
index 57fe2aa..5b54bf6 100644
--- a/camera/camera-lifecycle/build.gradle
+++ b/camera/camera-lifecycle/build.gradle
@@ -44,7 +44,7 @@
     androidTestImplementation(project(":annotation:annotation-experimental"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
     androidTestImplementation(project(":internal-testutils-truth"))
-    androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.13.1")
+    androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.15.0")
 }
 
 android {
diff --git a/camera/camera-video/src/androidTest/AndroidManifest.xml b/camera/camera-video/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..9f27ec7
--- /dev/null
+++ b/camera/camera-video/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.camera.video.test">
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+</manifest>
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
new file mode 100644
index 0000000..db52f2a
--- /dev/null
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.video.internal
+
+import android.Manifest
+import android.media.AudioFormat
+import android.media.MediaRecorder
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.video.internal.encoder.FakeInputBuffer
+import androidx.camera.video.internal.encoder.noInvocation
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.GrantPermissionRule
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import java.util.concurrent.Callable
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class AudioSourceTest {
+
+    companion object {
+        private const val SAMPLE_RATE = 8000
+        private const val DEFAULT_MIN_BUFFER_SIZE = 1024
+    }
+
+    @get:Rule
+    var mAudioPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+        Manifest.permission.RECORD_AUDIO
+    )
+    private lateinit var audioSource: AudioSource
+    private lateinit var fakeBufferProvider: FakeBufferProvider
+    private val bufferFactoryInvocations = mock(Callable::class.java)
+
+    @Before
+    fun setUp() {
+        fakeBufferProvider = FakeBufferProvider {
+            bufferFactoryInvocations.call()
+            FakeInputBuffer()
+        }
+        fakeBufferProvider.setActive(true)
+
+        audioSource = AudioSource.Builder()
+            .setExecutor(CameraXExecutors.ioExecutor())
+            .setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
+            .setSampleRate(SAMPLE_RATE)
+            .setChannelConfig(AudioFormat.CHANNEL_IN_MONO)
+            .setAudioFormat(AudioFormat.ENCODING_PCM_16BIT)
+            .setDefaultBufferSize(DEFAULT_MIN_BUFFER_SIZE)
+            .setBufferProvider(fakeBufferProvider)
+            .build()
+    }
+
+    @After
+    fun tearDown() {
+        if (this::audioSource.isInitialized) {
+            audioSource.release()
+        }
+    }
+
+    @Test
+    fun canRestartAudioSource() {
+        for (i in 0..2) {
+            // Act.
+            audioSource.start()
+
+            // Assert.
+            // It should continuously send audio data by invoking BufferProvider#acquireBuffer
+            verify(bufferFactoryInvocations, timeout(10000L).atLeast(3)).call()
+
+            // Act.
+            audioSource.stop()
+
+            // Assert.
+            verify(bufferFactoryInvocations, noInvocation(3000L, 6000L)).call()
+        }
+    }
+
+    @Test
+    fun bufferProviderStateChange_acquireBufferOrNot() {
+        // Arrange.
+        audioSource.start()
+
+        for (i in 0..2) {
+            // Act.
+            fakeBufferProvider.setActive(true)
+
+            // Assert.
+            // It should continuously send audio data by invoking BufferProvider#acquireBuffer
+            verify(bufferFactoryInvocations, timeout(10000L).atLeast(3)).call()
+
+            // Act.
+            fakeBufferProvider.setActive(false)
+
+            // Assert.
+            verify(bufferFactoryInvocations, noInvocation(3000L, 6000L)).call()
+        }
+    }
+}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/FakeBufferProvider.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/FakeBufferProvider.kt
new file mode 100644
index 0000000..db8e4be
--- /dev/null
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/FakeBufferProvider.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.video.internal
+
+import androidx.annotation.GuardedBy
+import androidx.camera.core.impl.Observable
+import androidx.camera.core.impl.utils.futures.Futures
+import androidx.camera.video.internal.encoder.InputBuffer
+import com.google.common.util.concurrent.ListenableFuture
+import java.lang.IllegalStateException
+import java.util.concurrent.Callable
+import java.util.concurrent.Executor
+
+class FakeBufferProvider(private val bufferFactory: Callable<InputBuffer>) :
+    BufferProvider<InputBuffer> {
+
+    private val lock = Object()
+    @GuardedBy("lock")
+    private val observers = mutableMapOf<Observable.Observer<BufferProvider.State>, Executor>()
+    @GuardedBy("lock")
+    private var state = BufferProvider.State.ACTIVE
+
+    override fun acquireBuffer(): ListenableFuture<InputBuffer> {
+        synchronized(lock) {
+            return if (state == BufferProvider.State.ACTIVE) {
+                Futures.immediateFuture(bufferFactory.call())
+            } else {
+                Futures.immediateFailedFuture(IllegalStateException("Not in ACTIVE state"))
+            }
+        }
+    }
+
+    override fun fetchData(): ListenableFuture<BufferProvider.State> {
+        synchronized(lock) {
+            return Futures.immediateFuture(state)
+        }
+    }
+
+    override fun addObserver(
+        executor: Executor,
+        observer: Observable.Observer<BufferProvider.State>
+    ) {
+        synchronized(observers) {
+            observers[observer] = executor
+        }
+        executor.execute { observer.onNewData(state) }
+    }
+
+    override fun removeObserver(observer: Observable.Observer<BufferProvider.State>) {
+        synchronized(lock) {
+            observers.remove(observer)
+        }
+    }
+
+    fun setActive(active: Boolean) {
+        val newState = if (active) BufferProvider.State.ACTIVE else BufferProvider.State.INACTIVE
+        val localObservers: Map<Observable.Observer<BufferProvider.State>, Executor>
+        synchronized(lock) {
+            if (state == newState) {
+                return
+            }
+            state = newState
+            localObservers = observers
+        }
+        for ((observer, executor) in localObservers) {
+            executor.execute { observer.onNewData(newState) }
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/AudioEncoderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/AudioEncoderTest.kt
index 64d1e7d..ffba5b9 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/AudioEncoderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/AudioEncoderTest.kt
@@ -16,12 +16,17 @@
 package androidx.camera.video.internal.encoder
 
 import android.media.AudioFormat
+import androidx.camera.core.impl.Observable.Observer
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.video.internal.BufferProvider
+import androidx.camera.video.internal.BufferProvider.State
+import androidx.concurrent.futures.await
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import kotlinx.coroutines.Dispatchers
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import org.junit.After
@@ -37,6 +42,10 @@
 import org.mockito.Mockito.verify
 import org.mockito.invocation.InvocationOnMock
 import java.nio.ByteBuffer
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -51,7 +60,7 @@
 
     private lateinit var encoder: Encoder
     private lateinit var encoderCallback: EncoderCallback
-    private lateinit var byteBufferProviderJob: Job
+    private lateinit var fakeAudioLoop: FakeAudioLoop
 
     @Before
     fun setup() {
@@ -74,25 +83,25 @@
         )
         encoder.setEncoderCallback(encoderCallback, CameraXExecutors.directExecutor())
 
-        // Prepare a fake audio source
-        val byteBuffer = ByteBuffer.allocateDirect(1024)
-        byteBufferProviderJob = GlobalScope.launch(Dispatchers.Default) {
-            while (true) {
-                byteBuffer.rewind()
-                (encoder.input as Encoder.ByteBufferInput).putByteBuffer(byteBuffer)
-                delay(200)
-            }
-        }
+        @Suppress("UNCHECKED_CAST")
+        fakeAudioLoop = FakeAudioLoop(encoder.input as BufferProvider<InputBuffer>)
     }
 
     @After
     fun tearDown() {
-        encoder.release()
-        byteBufferProviderJob.cancel(null)
+        if (this::encoder.isInitialized) {
+            encoder.release()
+        }
+        if (this::fakeAudioLoop.isInitialized) {
+            fakeAudioLoop.stop()
+        }
     }
 
     @Test
     fun discardInputBufferBeforeStart() {
+        // Arrange.
+        fakeAudioLoop.start()
+
         // Act.
         // Wait a second to receive data
         Thread.sleep(3000L)
@@ -103,6 +112,9 @@
 
     @Test
     fun canRestartEncoder() {
+        // Arrange.
+        fakeAudioLoop.start()
+
         for (i in 0..3) {
             // Arrange.
             clearInvocations(encoderCallback)
@@ -125,6 +137,9 @@
 
     @Test
     fun canRestartEncoderImmediately() {
+        // Arrange.
+        fakeAudioLoop.start()
+
         // Act.
         encoder.start()
         encoder.stop()
@@ -136,6 +151,9 @@
 
     @Test
     fun canPauseResumeEncoder() {
+        // Arrange.
+        fakeAudioLoop.start()
+
         // Act.
         encoder.start()
 
@@ -162,6 +180,9 @@
 
     @Test
     fun canPauseStopStartEncoder() {
+        // Arrange.
+        fakeAudioLoop.start()
+
         // Act.
         encoder.start()
 
@@ -191,4 +212,128 @@
         // Assert.
         verify(encoderCallback, timeout(15000L).atLeast(5)).onEncodedData(any())
     }
+
+    @Test
+    fun bufferProvider_canAcquireBuffer() {
+        // Arrange.
+        encoder.start()
+
+        for (i in 0..8) {
+            // Act.
+            val inputBuffer = (encoder.input as Encoder.ByteBufferInput)
+                .acquireBuffer()
+                .get(3, TimeUnit.SECONDS)
+
+            // Assert.
+            assertThat(inputBuffer).isNotNull()
+            inputBuffer.cancel()
+        }
+    }
+
+    @Test
+    fun bufferProvider_canReceiveBufferProviderStateChange() {
+        // Arrange.
+        val stateRef = AtomicReference<State>()
+        val lock = Semaphore(0)
+        (encoder.input as Encoder.ByteBufferInput).addObserver(
+            CameraXExecutors.directExecutor(),
+            object : Observer<State> {
+                override fun onNewData(state: State?) {
+                    stateRef.set(state)
+                    lock.release()
+                }
+
+                override fun onError(t: Throwable) {
+                    stateRef.set(null)
+                    lock.release()
+                }
+            }
+        )
+
+        // Assert.
+        assertThat(lock.tryAcquire(3, TimeUnit.SECONDS)).isTrue()
+        assertThat(stateRef.get()).isEqualTo(State.INACTIVE)
+
+        // Act.
+        encoder.start()
+
+        // Assert.
+        assertThat(lock.tryAcquire(3, TimeUnit.SECONDS)).isTrue()
+        assertThat(stateRef.get()).isEqualTo(State.ACTIVE)
+
+        // Act.
+        encoder.pause()
+
+        // Assert
+        assertThat(lock.tryAcquire(3, TimeUnit.SECONDS)).isTrue()
+        assertThat(stateRef.get()).isEqualTo(State.INACTIVE)
+
+        // Act.
+        encoder.start()
+
+        // Assert.
+        assertThat(lock.tryAcquire(3, TimeUnit.SECONDS)).isTrue()
+        assertThat(stateRef.get()).isEqualTo(State.ACTIVE)
+
+        // Act.
+        encoder.stop()
+
+        // Assert.
+        assertThat(lock.tryAcquire(3, TimeUnit.SECONDS)).isTrue()
+        assertThat(stateRef.get()).isEqualTo(State.INACTIVE)
+    }
+
+    private class FakeAudioLoop(private val bufferProvider: BufferProvider<InputBuffer>) {
+        private val inputByteBuffer = ByteBuffer.allocateDirect(1024)
+        private val started = AtomicBoolean(false)
+        private var job: Job? = null
+
+        fun start() {
+            if (started.getAndSet(true)) {
+                return
+            }
+            job = GlobalScope.launch(
+                CameraXExecutors.ioExecutor().asCoroutineDispatcher(),
+            ) {
+                while (true) {
+                    try {
+                        val inputBuffer = bufferProvider.acquireBuffer().await()
+                        inputBuffer.apply {
+                            byteBuffer.apply {
+                                put(
+                                    inputByteBuffer.apply {
+                                        clear()
+                                        limit(limit().coerceAtMost(byteBuffer.capacity()))
+                                    }
+                                )
+                                flip()
+                            }
+                            setPresentationTimeUs(System.nanoTime() / 1000L)
+                            submit()
+                        }
+                    } catch (e: IllegalStateException) {
+                        // For simplicity, AudioLoop doesn't monitor the encoder's state.
+                        // When an IllegalStateException is thrown by encoder which is not started,
+                        // AudioLoop should retry with a delay to avoid busy loop.
+                        // CancellationException is a subclass of IllegalStateException and is
+                        // ambiguous since the cancellation could be caused by ListenableFuture
+                        // was cancelled or coroutine Job was cancelled. For the
+                        // ListenableFuture case, AudioLoop will need to retry with a delay as
+                        // IllegalStateException. For the coroutine Job case, the loop should
+                        // be stopped. The goal can be simply achieved by calling delay() method
+                        // because the method will also get CancellationException if it is
+                        // coroutine Job cancellation, and eventually leave the audio loop.
+                        delay(300L)
+                    }
+                }
+            }
+        }
+
+        fun stop() {
+            if (!started.getAndSet(false)) {
+                return
+            }
+            job!!.cancel()
+        }
+    }
 }
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/FakeInputBuffer.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/FakeInputBuffer.kt
new file mode 100644
index 0000000..72fc1c4
--- /dev/null
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/encoder/FakeInputBuffer.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.video.internal.encoder
+
+import androidx.concurrent.futures.ResolvableFuture
+import com.google.common.util.concurrent.ListenableFuture
+import java.nio.ByteBuffer
+
+class FakeInputBuffer : InputBuffer {
+    private val byteBuffer = ByteBuffer.allocateDirect(1024)
+    private val terminationFuture = ResolvableFuture.create<Void>()
+
+    override fun getByteBuffer(): ByteBuffer {
+        throwIfTerminated()
+        return byteBuffer
+    }
+
+    override fun setPresentationTimeUs(presentationTimeUs: Long) {
+        throwIfTerminated()
+    }
+
+    override fun setEndOfStream(isEndOfStream: Boolean) {
+        throwIfTerminated()
+    }
+
+    override fun submit(): Boolean {
+        return terminationFuture.set(null)
+    }
+
+    override fun cancel(): Boolean {
+        return terminationFuture.set(null)
+    }
+
+    override fun getTerminationFuture(): ListenableFuture<Void> {
+        return terminationFuture
+    }
+
+    private fun throwIfTerminated() {
+        check(!terminationFuture.isDone)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSource.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSource.java
new file mode 100644
index 0000000..5a08d74
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSource.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal;
+
+import static androidx.camera.video.internal.AudioSource.InternalState.CONFIGURED;
+import static androidx.camera.video.internal.AudioSource.InternalState.RELEASED;
+import static androidx.camera.video.internal.AudioSource.InternalState.STARTED;
+
+import android.annotation.SuppressLint;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.AudioTimestamp;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.core.Logger;
+import androidx.camera.core.impl.Observable;
+import androidx.camera.core.impl.annotation.ExecutedBy;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.impl.utils.futures.FutureCallback;
+import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.camera.video.internal.encoder.InputBuffer;
+import androidx.core.util.Preconditions;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executor;
+
+/**
+ * AudioSource is used to obtain audio raw data and write to the buffer from {@link BufferProvider}.
+ *
+ * <p>The audio raw data could be one of sources from the device. The target source can be
+ * specified with {@link Builder#setAudioSource(int)}.
+ *
+ * <p>Calling {@link #start} will start reading audio data from the target source and then write
+ * the data into the buffer from {@link BufferProvider}. Calling {@link #stop} will stop sending
+ * audio data. However, to really read/write data to buffer, the {@link BufferProvider}'s state
+ * must be {@link BufferProvider.State#ACTIVE}. So recording may temporarily pause when the
+ * {@link BufferProvider}'s state is {@link BufferProvider.State#INACTIVE}.
+ *
+ * @see BufferProvider
+ * @see AudioRecord
+ */
+public final class AudioSource {
+    private static final String TAG = "AudioSource";
+
+    enum InternalState {
+        /** The initial state or when {@link #stop} is called after started. */
+        CONFIGURED,
+
+        /** The state is when it is in {@link #CONFIGURED} state and {@link #start} is called. */
+        STARTED,
+
+        /** The state is when {@link #release} is called. */
+        RELEASED,
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    final Executor mExecutor;
+
+    private final BufferProvider<InputBuffer> mBufferProvider;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    final AudioRecord mAudioRecord;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    final int mBufferSize;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    InternalState mState = CONFIGURED;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    BufferProvider.State mBufferProviderState = BufferProvider.State.INACTIVE;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    boolean mIsSendingAudio;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    AudioSource(@NonNull Executor executor,
+            @NonNull BufferProvider<InputBuffer> bufferProvider,
+            int audioSource,
+            int sampleRate,
+            int channelConfig,
+            int audioFormat,
+            int defaultBufferSize)
+            throws AudioSourceAccessException {
+        mExecutor = CameraXExecutors.newSequentialExecutor(Preconditions.checkNotNull(executor));
+        mBufferProvider = Preconditions.checkNotNull(bufferProvider);
+
+        int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
+        if (bufferSize <= 0) {
+            bufferSize = defaultBufferSize;
+        }
+        mBufferSize = bufferSize * 2;
+        try {
+            mAudioRecord = new AudioRecord(audioSource,
+                    sampleRate,
+                    channelConfig,
+                    audioFormat,
+                    mBufferSize);
+        } catch (IllegalArgumentException e) {
+            throw new AudioSourceAccessException("Unable to create AudioRecord", e);
+        }
+
+        if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
+            mAudioRecord.release();
+            throw new AudioSourceAccessException("Unable to initialize AudioRecord");
+        }
+
+        mBufferProvider.addObserver(mExecutor, mStateObserver);
+    }
+
+    /**
+     * Starts the AudioSource.
+     *
+     * <p>Audio data will start being sent to the {@link BufferProvider} when
+     * {@link BufferProvider}'s state is {@link BufferProvider.State#ACTIVE}.
+     *
+     * @throws IllegalStateException if the AudioSource is released.
+     */
+    public void start() {
+        mExecutor.execute(() -> {
+            switch (mState) {
+                case CONFIGURED:
+                    setState(STARTED);
+                    updateSendingAudio();
+                    break;
+                case STARTED:
+                    // Do nothing
+                    break;
+                case RELEASED:
+                    throw new IllegalStateException("AudioRecorder is released");
+            }
+        });
+    }
+
+    /**
+     * Stops the AudioSource.
+     *
+     * <p>Audio data will stop being sent to the {@link BufferProvider}.
+     *
+     * @throws IllegalStateException if it is released.
+     */
+    public void stop() {
+        mExecutor.execute(() -> {
+            switch (mState) {
+                case STARTED:
+                    setState(CONFIGURED);
+                    updateSendingAudio();
+                    break;
+                case CONFIGURED:
+                    // Do nothing
+                    break;
+                case RELEASED:
+                    throw new IllegalStateException("AudioRecorder is released");
+            }
+        });
+    }
+
+    /**
+     * Releases the AudioSource.
+     *
+     * <p>Once the AudioSource is released, it can not be used any more.
+     */
+    public void release() {
+        mExecutor.execute(() -> {
+            switch (mState) {
+                case STARTED:
+                case CONFIGURED:
+                    mBufferProvider.removeObserver(mStateObserver);
+                    mAudioRecord.release();
+                    stopSendingAudio();
+                    setState(RELEASED);
+                    break;
+                case RELEASED:
+                    // Do nothing
+                    break;
+            }
+        });
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @ExecutedBy("mExecutor")
+    void updateSendingAudio() {
+        if (mState == STARTED && mBufferProviderState == BufferProvider.State.ACTIVE) {
+            startSendingAudio();
+        } else {
+            stopSendingAudio();
+        }
+    }
+
+    @ExecutedBy("mExecutor")
+    private void startSendingAudio() {
+        if (mIsSendingAudio) {
+            // Already started, ignore
+            return;
+        }
+        try {
+            Logger.d(TAG, "startSendingAudio");
+            mAudioRecord.startRecording();
+            if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
+                throw new IllegalStateException("Unable to start AudioRecord with state: "
+                                + mAudioRecord.getRecordingState());
+            }
+        } catch (IllegalStateException e) {
+            Logger.w(TAG, "Failed to start AudioRecord", e);
+            return;
+        }
+        mIsSendingAudio = true;
+        sendNextAudio();
+    }
+
+    @ExecutedBy("mExecutor")
+    private void stopSendingAudio() {
+        if (!mIsSendingAudio) {
+            // Already stopped, ignore.
+            return;
+        }
+        mIsSendingAudio = false;
+        try {
+            Logger.d(TAG, "stopSendingAudio");
+            mAudioRecord.stop();
+            if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_STOPPED) {
+                throw new IllegalStateException("Unable to stop AudioRecord with state: "
+                        + mAudioRecord.getRecordingState());
+            }
+        } catch (IllegalStateException e) {
+            Logger.w(TAG, "Failed to stop AudioRecord", e);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @ExecutedBy("mExecutor")
+    void sendNextAudio() {
+        Futures.addCallback(mBufferProvider.acquireBuffer(), mAcquireBufferCallback, mExecutor);
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @ExecutedBy("mExecutor")
+    void setState(InternalState state) {
+        Logger.d(TAG, "Transitioning internal state: " + mState + " --> " + state);
+        mState = state;
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @SuppressLint("UnsafeNewApiCall")
+    long generatePresentationTimeUs() {
+        long presentationTimeUs = -1;
+        if (Build.VERSION.SDK_INT >= 24) {
+            AudioTimestamp audioTimestamp = new AudioTimestamp();
+            if (mAudioRecord.getTimestamp(audioTimestamp, AudioTimestamp.TIMEBASE_MONOTONIC)
+                    == AudioRecord.SUCCESS) {
+                presentationTimeUs = audioTimestamp.nanoTime / 1000L;
+            } else {
+                Logger.w(TAG, "Unable to get audio timestamp");
+            }
+        }
+        if (presentationTimeUs == -1) {
+            presentationTimeUs = System.nanoTime() / 1000L;
+        }
+        return presentationTimeUs;
+    }
+
+    private final FutureCallback<InputBuffer> mAcquireBufferCallback =
+            new FutureCallback<InputBuffer>() {
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onSuccess(InputBuffer inputBuffer) {
+                    if (!mIsSendingAudio) {
+                        inputBuffer.cancel();
+                        return;
+                    }
+                    ByteBuffer byteBuffer = inputBuffer.getByteBuffer();
+
+                    int length = mAudioRecord.read(byteBuffer, mBufferSize);
+                    if (length > 0) {
+                        byteBuffer.limit(length);
+                        inputBuffer.setPresentationTimeUs(generatePresentationTimeUs());
+                        inputBuffer.submit();
+                    } else {
+                        Logger.w(TAG, "Unable to read data from AudioRecord.");
+                        inputBuffer.cancel();
+                    }
+                    sendNextAudio();
+                }
+
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onFailure(Throwable throwable) {
+                    Logger.d(TAG, "Unable to get input buffer, the BufferProvider "
+                            + "could be transitioning to INACTIVE state.");
+                }
+            };
+
+    private final Observable.Observer<BufferProvider.State> mStateObserver =
+            new Observable.Observer<BufferProvider.State>() {
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onNewData(@Nullable BufferProvider.State state) {
+                    Logger.d(TAG, "Receive BufferProvider state change: "
+                            + mBufferProviderState + " to " + state);
+                    mBufferProviderState = state;
+                    updateSendingAudio();
+                }
+
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onError(@NonNull Throwable t) {
+                    // Not define, should not be possible.
+                }
+            };
+
+    /**
+     * The builder of the AudioSource.
+     */
+    public static class Builder {
+        private Executor mExecutor;
+        private int mAudioSource;
+        private int mSampleRate;
+        private int mChannelConfig;
+        private int mAudioFormat;
+        private int mDefaultBufferSize;
+        private BufferProvider<InputBuffer> mBufferProvider;
+
+        /** Sets the executor to run the background task. */
+        @NonNull
+        public Builder setExecutor(@NonNull Executor executor) {
+            mExecutor = executor;
+            return this;
+        }
+
+        /**
+         * Sets the device audio source.
+         *
+         * @see android.media.MediaRecorder.AudioSource#MIC
+         * @see android.media.MediaRecorder.AudioSource#CAMCORDER
+         */
+        @NonNull
+        public Builder setAudioSource(int audioSource) {
+            mAudioSource = audioSource;
+            return this;
+        }
+
+        /** Sets the audio sample rate. */
+        @NonNull
+        public Builder setSampleRate(int sampleRate) {
+            mSampleRate = sampleRate;
+            return this;
+        }
+
+        /**
+         * Sets the channel config.
+         *
+         * @see AudioFormat#CHANNEL_IN_MONO
+         * @see AudioFormat#CHANNEL_IN_STEREO
+         */
+        @NonNull
+        public Builder setChannelConfig(int channelConfig) {
+            mChannelConfig = channelConfig;
+            return this;
+        }
+
+        /**
+         * Sets the audio format.
+         *
+         * @see AudioFormat#ENCODING_PCM_8BIT
+         * @see AudioFormat#ENCODING_PCM_16BIT
+         * @see AudioFormat#ENCODING_PCM_FLOAT
+         */
+        @NonNull
+        public Builder setAudioFormat(int audioFormat) {
+            mAudioFormat = audioFormat;
+            return this;
+        }
+
+        /**
+         * Sets the default buffer size.
+         *
+         * <p>AudioSource will try to generate a buffer size. But if it is unable to get one,
+         * it will apply this default buffer size.
+         */
+        @NonNull
+        public Builder setDefaultBufferSize(int bufferSize) {
+            mDefaultBufferSize = bufferSize;
+            return this;
+        }
+
+        /** Sets the {@link BufferProvider}. */
+        @NonNull
+        public Builder setBufferProvider(@NonNull BufferProvider<InputBuffer> bufferProvider) {
+            mBufferProvider = bufferProvider;
+            return this;
+        }
+
+        /** Build the AudioSource. */
+        @NonNull
+        public AudioSource build() throws AudioSourceAccessException {
+            return new AudioSource(mExecutor,
+                    mBufferProvider,
+                    mAudioSource,
+                    mSampleRate,
+                    mChannelConfig,
+                    mAudioFormat,
+                    mDefaultBufferSize
+            );
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/MalformedVersionException.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSourceAccessException.java
similarity index 63%
rename from car/app/app/src/main/java/androidx/car/app/MalformedVersionException.java
rename to camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSourceAccessException.java
index b685ecb..204ba20 100644
--- a/car/app/app/src/main/java/androidx/car/app/MalformedVersionException.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSourceAccessException.java
@@ -14,24 +14,22 @@
  * limitations under the License.
  */
 
-package androidx.car.app;
+package androidx.camera.video.internal;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-/**
- * An exception for malformed {@link CarAppVersion} strings.
- */
-public class MalformedVersionException extends Exception {
-    public MalformedVersionException(@Nullable String message) {
+/** An exception thrown to indicate an error has occurred during configuring an audio source. */
+public class AudioSourceAccessException extends Exception {
+
+    public AudioSourceAccessException(@Nullable String message) {
         super(message);
     }
 
-    public MalformedVersionException(@NonNull String message, @NonNull Throwable cause) {
+    public AudioSourceAccessException(@Nullable String message, @Nullable Throwable cause) {
         super(message, cause);
     }
 
-    public MalformedVersionException(@Nullable Throwable cause) {
+    public AudioSourceAccessException(@Nullable Throwable cause) {
         super(cause);
     }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/BufferProvider.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/BufferProvider.java
new file mode 100644
index 0000000..1dfd4c1
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/BufferProvider.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.impl.Observable;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * BufferProvider provides buffers for writing data.
+ *
+ * <p>BufferProvider has {@link State}, it could be either {@link State#ACTIVE} or
+ * {@link State#INACTIVE}. The state can be fetched directly through {@link #fetchData()} or use
+ * {@link #addObserver} to receive state changes.
+ *
+ * <p>A buffer for writing data can be acquired with {@link #acquireBuffer()}". The buffer can
+ * only be obtained when the state is {@link State#ACTIVE}. If the state is
+ * {@link State#INACTIVE}, the {@link #acquireBuffer()} will return a failed
+ * {@link ListenableFuture} with {@link IllegalStateException}. If the state is transitioned from
+ * {@link State#ACTIVE} to {@link State#INACTIVE}, the incomplete {@link ListenableFuture} will
+ * get {@link java.util.concurrent.CancellationException}. Buffer acquisition can be cancelled
+ * with {@link ListenableFuture#cancel} if acquisition is not yet complete.
+ *
+ * @param <T> the buffer data type
+ */
+public interface BufferProvider<T> extends Observable<BufferProvider.State> {
+
+    /**
+     * Acquires a buffer.
+     *
+     * <p>A buffer can only be obtained when the state is {@link State#ACTIVE}. If the state is
+     * {@link State#INACTIVE}, the {@link #acquireBuffer()} will return an failed
+     * {@link ListenableFuture} with {@link IllegalStateException}. If the state is transitioned
+     * from {@link State#ACTIVE} to {@link State#INACTIVE}, the incomplete
+     * {@link ListenableFuture} will get {@link java.util.concurrent.CancellationException}.
+     * Buffer acquisition can be cancelled with {@link ListenableFuture#cancel} if acquisition
+     * is not yet complete.
+     *
+     * @return a {@link ListenableFuture} to represent the acquisition.
+     */
+    @NonNull
+    ListenableFuture<T> acquireBuffer();
+
+    /** The state of the BufferProvider. */
+    enum State {
+
+        /** The state means it is able to acquire a buffer. */
+        ACTIVE,
+
+        /**
+         * The state means it is not able to acquire buffer.
+         *
+         * <p>The acquisition via {@link #acquireBuffer()} will get a result with
+         * {@link IllegalStateException}.
+         */
+        INACTIVE,
+    }
+}
+
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncodedDataImpl.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncodedDataImpl.java
index 723edce..1cef3f9 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncodedDataImpl.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncodedDataImpl.java
@@ -88,7 +88,7 @@
         }
         try {
             mMediaCodec.releaseOutputBuffer(mBufferIndex, false);
-        } catch (MediaCodec.CodecException e) {
+        } catch (IllegalStateException e) {
             mClosedCompleter.setException(e);
             return;
         }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/Encoder.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/Encoder.java
index da6ebce..d6fa8a5 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/Encoder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/Encoder.java
@@ -19,8 +19,8 @@
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
+import androidx.camera.video.internal.BufferProvider;
 
-import java.nio.ByteBuffer;
 import java.util.concurrent.Executor;
 
 /**
@@ -108,18 +108,13 @@
         }
     }
 
-    /** A ByteBufferInput provides {@link #putByteBuffer} method to send raw data. */
-    interface ByteBufferInput extends EncoderInput {
-
-        /**
-         * Puts an input raw {@link ByteBuffer} to the encoder.
-         *
-         * <p>The input {@code ByteBuffer} must be put when encoder is in started and not paused
-         * state, otherwise the {@code ByteBuffer} will be dropped directly. Then the encoded data
-         * will be sent via {@link EncoderCallback#onEncodedData} callback.
-         *
-         * @param byteBuffer the input byte buffer
-         */
-        void putByteBuffer(@NonNull ByteBuffer byteBuffer);
+    /**
+     * A ByteBufferInput is a {@link BufferProvider} implementation and provides
+     * {@link InputBuffer} to write input data to the encoder.
+     *
+     * @see BufferProvider
+     * @see InputBuffer
+     */
+    interface ByteBufferInput extends EncoderInput, BufferProvider<InputBuffer> {
     }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java
index 00280df..acd7535 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/EncoderImpl.java
@@ -17,6 +17,7 @@
 package androidx.camera.video.internal.encoder;
 
 import static androidx.camera.video.internal.encoder.EncoderImpl.InternalState.CONFIGURED;
+import static androidx.camera.video.internal.encoder.EncoderImpl.InternalState.ERROR;
 import static androidx.camera.video.internal.encoder.EncoderImpl.InternalState.PAUSED;
 import static androidx.camera.video.internal.encoder.EncoderImpl.InternalState.PENDING_RELEASE;
 import static androidx.camera.video.internal.encoder.EncoderImpl.InternalState.PENDING_START;
@@ -36,24 +37,29 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.camera.core.Logger;
+import androidx.camera.core.impl.Observable;
+import androidx.camera.core.impl.annotation.ExecutedBy;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.core.util.Consumer;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
 import androidx.core.util.Preconditions;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * The encoder implementation.
@@ -106,20 +112,34 @@
          */
         PENDING_RELEASE,
 
+        /**
+         * Then state is when the encoder encounter error. Error state is a transitional state
+         * where encoder user is supposed to wait for {@link EncoderCallback#onEncodeStop} or
+         * {@link EncoderCallback#onEncodeError}. Any method call during this state should be
+         * ignore except {@link #release}.
+         */
+        ERROR,
+
         /** The state is when the encoder is released. */
         RELEASED,
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     final Object mLock = new Object();
+    private final InternalStateObservable mStateObservable = new InternalStateObservable();
     private final MediaFormat mMediaFormat;
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @GuardedBy("mLock")
     final MediaCodec mMediaCodec;
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     final EncoderInput mEncoderInput;
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    final Executor mExecutor;
+    final Executor mEncoderExecutor;
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    final Queue<Integer> mFreeInputBufferIndexQueue = new ArrayDeque<>();
+    private final Queue<Completer<InputBuffer>> mAcquisitionQueue = new ArrayDeque<>();
+    private final Set<InputBuffer> mInputBufferSet = new HashSet<>();
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    final Set<EncodedDataImpl> mEncodedDataSet = new HashSet<>();
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     @GuardedBy("mLock")
@@ -128,7 +148,6 @@
     @GuardedBy("mLock")
     Executor mEncoderCallbackExecutor = CameraXExecutors.mainThreadExecutor();
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @GuardedBy("mLock")
     InternalState mState;
 
     /**
@@ -143,10 +162,26 @@
         Preconditions.checkNotNull(executor);
         Preconditions.checkNotNull(encoderConfig);
 
-        mExecutor = CameraXExecutors.newSequentialExecutor(executor);
+        mEncoderExecutor = CameraXExecutors.newSequentialExecutor(executor);
 
         if (encoderConfig instanceof AudioEncoderConfig) {
-            mEncoderInput = new ByteBufferInput();
+            ByteBufferInput byteBufferInput = new ByteBufferInput();
+            // State change will run on mEncoderExecutor, use direct executor to avoid delay.
+            mStateObservable.addObserver(CameraXExecutors.directExecutor(),
+                    new Observable.Observer<InternalState>() {
+                        @ExecutedBy("mEncoderExecutor")
+                        @Override
+                        public void onNewData(@Nullable InternalState value) {
+                            byteBufferInput.setActive(value == STARTED);
+                        }
+
+                        @ExecutedBy("mEncoderExecutor")
+                        @Override
+                        public void onError(@NonNull Throwable t) {
+                            // No defined. Ignore.
+                        }
+                    });
+            mEncoderInput = byteBufferInput;
         } else if (encoderConfig instanceof VideoEncoderConfig) {
             mEncoderInput = new SurfaceInput();
         } else {
@@ -171,18 +206,22 @@
         setState(CONFIGURED);
     }
 
-    @SuppressWarnings("GuardedBy")
-    // It complains SurfaceInput#resetSurface and ByteBufferInput#clearFreeBuffers don't hold mLock
-    @GuardedBy("mLock")
+    @ExecutedBy("mEncoderExecutor")
     private void reset() {
+        mFreeInputBufferIndexQueue.clear();
+
+        // Cancel incomplete acquisitions if exists.
+        for (Completer<InputBuffer> completer : mAcquisitionQueue) {
+            completer.setCancelled();
+        }
+        mAcquisitionQueue.clear();
+
         mMediaCodec.reset();
         mMediaCodec.setCallback(new MediaCodecCallback());
         mMediaCodec.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
 
         if (mEncoderInput instanceof SurfaceInput) {
             ((SurfaceInput) mEncoderInput).resetSurface();
-        } else if (mEncoderInput instanceof ByteBufferInput) {
-            ((ByteBufferInput) mEncoderInput).clearFreeBuffers();
         }
     }
 
@@ -204,7 +243,7 @@
      */
     @Override
     public void start() {
-        synchronized (mLock) {
+        mEncoderExecutor.execute(() -> {
             switch (mState) {
                 case CONFIGURED:
                     try {
@@ -222,6 +261,7 @@
                     setState(STARTED);
                     break;
                 case STARTED:
+                case ERROR:
                 case PENDING_START:
                     // Do nothing
                     break;
@@ -235,7 +275,7 @@
                 default:
                     throw new IllegalStateException("Unknown state: " + mState);
             }
-        }
+        });
     }
 
     /**
@@ -244,21 +284,26 @@
      * <p>It will trigger {@link EncoderCallback#onEncodeStop} after the last encoded data. It can
      * call {@link #start} to start again.
      */
-    @SuppressWarnings("GuardedBy")
-    // It complains ByteBufferInput#signalEndOfInputStream doesn't hold mLock
     @Override
     public void stop() {
-        synchronized (mLock) {
+        mEncoderExecutor.execute(() -> {
             switch (mState) {
                 case CONFIGURED:
                 case STOPPING:
+                case ERROR:
                     // Do nothing
                     break;
                 case STARTED:
                 case PAUSED:
                     setState(STOPPING);
                     if (mEncoderInput instanceof ByteBufferInput) {
-                        ((ByteBufferInput) mEncoderInput).signalEndOfInputStream();
+                        // Wait for all issued input buffer done to avoid input loss.
+                        List<ListenableFuture<Void>> futures = new ArrayList<>();
+                        for (InputBuffer inputBuffer : mInputBufferSet) {
+                            futures.add(inputBuffer.getTerminationFuture());
+                        }
+                        Futures.successfulAsList(futures).addListener(this::signalEndOfInputStream,
+                                mEncoderExecutor);
                     } else if (mEncoderInput instanceof SurfaceInput) {
                         try {
                             mMediaCodec.signalEndOfInputStream();
@@ -277,7 +322,7 @@
                 default:
                     throw new IllegalStateException("Unknown state: " + mState);
             }
-        }
+        });
     }
 
     /**
@@ -288,10 +333,11 @@
      */
     @Override
     public void pause() {
-        synchronized (mLock) {
+        mEncoderExecutor.execute(() -> {
             switch (mState) {
                 case CONFIGURED:
                 case PAUSED:
+                case ERROR:
                 case STOPPING:
                 case PENDING_START_PAUSED:
                     // Do nothing
@@ -311,7 +357,7 @@
                 default:
                     throw new IllegalStateException("Unknown state: " + mState);
             }
-        }
+        });
     }
 
     /**
@@ -324,11 +370,12 @@
      */
     @Override
     public void release() {
-        synchronized (mLock) {
+        mEncoderExecutor.execute(() -> {
             switch (mState) {
                 case CONFIGURED:
                 case STARTED:
                 case PAUSED:
+                case ERROR:
                     mMediaCodec.release();
                     setState(RELEASED);
                     break;
@@ -344,7 +391,7 @@
                 default:
                     throw new IllegalStateException("Unknown state: " + mState);
             }
-        }
+        });
     }
 
     /**
@@ -363,42 +410,142 @@
         }
     }
 
-    @GuardedBy("mLock")
+    @ExecutedBy("mEncoderExecutor")
     private void setState(InternalState state) {
+        if (mState == state) {
+            return;
+        }
         Logger.d(TAG, "Transitioning encoder internal state: " + mState + " --> " + state);
         mState = state;
+        mStateObservable.notifyState();
     }
 
-    @GuardedBy("mLock")
+    @ExecutedBy("mEncoderExecutor")
     private void updatePauseToMediaCodec(boolean paused) {
         Bundle bundle = new Bundle();
         bundle.putBoolean(MediaCodec.PARAMETER_KEY_SUSPEND, paused);
         mMediaCodec.setParameters(bundle);
     }
 
+    @ExecutedBy("mEncoderExecutor")
+    private void signalEndOfInputStream() {
+        Futures.addCallback(acquireInputBuffer(),
+                new FutureCallback<InputBuffer>() {
+                    @Override
+                    public void onSuccess(InputBuffer inputBuffer) {
+                        inputBuffer.setPresentationTimeUs(generatePresentationTimeUs());
+                        inputBuffer.setEndOfStream(true);
+                        inputBuffer.submit();
+
+                        Futures.addCallback(inputBuffer.getTerminationFuture(),
+                                new FutureCallback<Void>() {
+                                    @ExecutedBy("mEncoderExecutor")
+                                    @Override
+                                    public void onSuccess(@Nullable Void result) {
+                                        // Do nothing.
+                                    }
+
+                                    @ExecutedBy("mEncoderExecutor")
+                                    @Override
+                                    public void onFailure(Throwable t) {
+                                        if (t instanceof MediaCodec.CodecException) {
+                                            handleEncodeError(
+                                                    (MediaCodec.CodecException) t);
+                                        } else {
+                                            handleEncodeError(EncodeException.ERROR_UNKNOWN,
+                                                    t.getMessage(), t);
+                                        }
+                                    }
+                                }, mEncoderExecutor);
+                    }
+
+                    @Override
+                    public void onFailure(Throwable t) {
+                        handleEncodeError(EncodeException.ERROR_UNKNOWN,
+                                "Unable to acquire InputBuffer.", t);
+                    }
+                }, mEncoderExecutor);
+    }
+
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @GuardedBy("mLock")
+    @ExecutedBy("mEncoderExecutor")
     void handleEncodeError(@NonNull MediaCodec.CodecException e) {
         handleEncodeError(EncodeException.ERROR_CODEC, e.getMessage(), e);
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @GuardedBy("mLock")
+    @ExecutedBy("mEncoderExecutor")
     void handleEncodeError(@EncodeException.ErrorType int error, @Nullable String message,
             @Nullable Throwable throwable) {
-        EncoderCallback encoderCallback = mEncoderCallback;
-        try {
-            mEncoderCallbackExecutor.execute(() -> encoderCallback.onEncodeError(
-                    new EncodeException(error, message, throwable)));
-        } catch (RejectedExecutionException re) {
-            Logger.e(TAG, "Unable to post to the supplied executor.", re);
+        switch (mState) {
+            case CONFIGURED:
+                // Unable to start MediaCodec. This is a fatal error. Try to reset the encoder.
+                notifyError(error, message, throwable);
+                reset();
+                break;
+            case STARTED:
+            case PAUSED:
+            case STOPPING:
+            case PENDING_START_PAUSED:
+            case PENDING_START:
+            case PENDING_RELEASE:
+                setState(ERROR);
+                stopMediaCodec(() -> notifyError(error, message, throwable));
+                break;
+            case ERROR:
+                Logger.w(TAG, "Get more than one error: " + message + "(" + error + ")",
+                        throwable);
+                break;
+            case RELEASED:
+                // Do nothing
+                break;
         }
-        mMediaCodec.stop();
-        handleStopped();
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @GuardedBy("mLock")
+    void notifyError(@EncodeException.ErrorType int error, @Nullable String message,
+            @Nullable Throwable throwable) {
+        EncoderCallback callback;
+        Executor executor;
+        synchronized (mLock) {
+            callback = mEncoderCallback;
+            executor = mEncoderCallbackExecutor;
+        }
+        try {
+            executor.execute(
+                    () -> callback.onEncodeError(new EncodeException(error, message, throwable)));
+        } catch (RejectedExecutionException e) {
+            Logger.e(TAG, "Unable to post to the supplied executor.", e);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @ExecutedBy("mEncoderExecutor")
+    void stopMediaCodec(@Nullable Runnable afterStop) {
+        /*
+         * MediaCodec#close will free all its input/output ByteBuffers. Therefore, before calling
+         * MediaCodec#close, it must ensure all dispatched EncodedData(output ByteBuffers) and
+         * InputBuffer(input ByteBuffers) are complete. Otherwise, the ByteBuffer receiver will
+         * get buffer overflow when accessing the ByteBuffers.
+         */
+        List<ListenableFuture<Void>> futures = new ArrayList<>();
+        for (EncodedDataImpl dataToClose : mEncodedDataSet) {
+            futures.add(dataToClose.getClosedFuture());
+        }
+        for (InputBuffer inputBuffer : mInputBufferSet) {
+            futures.add(inputBuffer.getTerminationFuture());
+        }
+        Futures.successfulAsList(futures).addListener(() -> {
+            mMediaCodec.stop();
+            if (afterStop != null) {
+                afterStop.run();
+            }
+            handleStopped();
+        }, mEncoderExecutor);
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @ExecutedBy("mEncoderExecutor")
     void handleStopped() {
         if (mState == PENDING_RELEASE) {
             mMediaCodec.release();
@@ -417,25 +564,115 @@
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @ExecutedBy("mEncoderExecutor")
+    @NonNull
+    ListenableFuture<InputBuffer> acquireInputBuffer() {
+        switch (mState) {
+            case CONFIGURED:
+                return Futures.immediateFailedFuture(new IllegalStateException(
+                        "Encoder is not started yet."));
+            case STARTED:
+            case PAUSED:
+            case STOPPING:
+            case PENDING_START:
+            case PENDING_START_PAUSED:
+            case PENDING_RELEASE:
+                AtomicReference<Completer<InputBuffer>> ref = new AtomicReference<>();
+                ListenableFuture<InputBuffer> future = CallbackToFutureAdapter.getFuture(
+                        completer -> {
+                            ref.set(completer);
+                            return "acquireInputBuffer";
+                        });
+                Completer<InputBuffer> completer = Preconditions.checkNotNull(ref.get());
+                mAcquisitionQueue.offer(completer);
+                completer.addCancellationListener(() -> mAcquisitionQueue.remove(completer),
+                        mEncoderExecutor);
+                matchAcquisitionsAndFreeBufferIndexes();
+                return future;
+            case ERROR:
+                return Futures.immediateFailedFuture(new IllegalStateException(
+                        "Encoder is in error state."));
+            case RELEASED:
+                return Futures.immediateFailedFuture(new IllegalStateException(
+                        "Encoder is released."));
+            default:
+                throw new IllegalStateException("Unknown state: " + mState);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    @ExecutedBy("mEncoderExecutor")
+    void matchAcquisitionsAndFreeBufferIndexes() {
+        while (!mAcquisitionQueue.isEmpty() && !mFreeInputBufferIndexQueue.isEmpty()) {
+            Completer<InputBuffer> completer = mAcquisitionQueue.poll();
+            int bufferIndex = mFreeInputBufferIndexQueue.poll();
+
+            InputBufferImpl inputBuffer;
+            try {
+                inputBuffer = new InputBufferImpl(mMediaCodec, bufferIndex);
+            } catch (MediaCodec.CodecException e) {
+                handleEncodeError(e);
+                return;
+            }
+            if (completer.set(inputBuffer)) {
+                mInputBufferSet.add(inputBuffer);
+                inputBuffer.getTerminationFuture().addListener(
+                        () -> mInputBufferSet.remove(inputBuffer), mEncoderExecutor);
+            } else {
+                inputBuffer.cancel();
+            }
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     static long generatePresentationTimeUs() {
         return System.nanoTime() / 1000L;
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    class InternalStateObservable implements Observable<InternalState> {
+
+        private final Map<Observer<InternalState>, Executor> mObservers = new LinkedHashMap<>();
+
+        @ExecutedBy("mEncoderExecutor")
+        void notifyState() {
+            final InternalState state = mState;
+            for (Map.Entry<Observer<InternalState>, Executor> entry : mObservers.entrySet()) {
+                entry.getValue().execute(() -> entry.getKey().onNewData(state));
+            }
+        }
+
+        @ExecutedBy("mEncoderExecutor")
+        @NonNull
+        @Override
+        public ListenableFuture<InternalState> fetchData() {
+            return Futures.immediateFuture(mState);
+        }
+
+        @ExecutedBy("mEncoderExecutor")
+        @Override
+        public void addObserver(@NonNull Executor executor,
+                @NonNull Observer<InternalState> observer) {
+            final InternalState state = mState;
+            mObservers.put(observer, executor);
+            executor.execute(() -> observer.onNewData(state));
+        }
+
+        @ExecutedBy("mEncoderExecutor")
+        @Override
+        public void removeObserver(@NonNull Observer<InternalState> observer) {
+            mObservers.remove(observer);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     class MediaCodecCallback extends MediaCodec.Callback {
 
-        @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-        @GuardedBy("mLock")
-        final Set<EncodedDataImpl> mEncodedDataSet = new HashSet<>();
-
-        @GuardedBy("mLock")
         private boolean mHasFirstData = false;
 
-        @SuppressWarnings("GuardedBy")
-        // It complains ByteBufferInput#putFreeBufferIndex doesn't hold mLock
         @Override
         public void onInputBufferAvailable(MediaCodec mediaCodec, int index) {
-            synchronized (mLock) {
+            mEncoderExecutor.execute(() -> {
                 switch (mState) {
                     case STARTED:
                     case PAUSED:
@@ -443,24 +680,24 @@
                     case PENDING_START:
                     case PENDING_START_PAUSED:
                     case PENDING_RELEASE:
-                        if (mEncoderInput instanceof ByteBufferInput) {
-                            ((ByteBufferInput) mEncoderInput).putFreeBufferIndex(index);
-                        }
+                        mFreeInputBufferIndexQueue.offer(index);
+                        matchAcquisitionsAndFreeBufferIndexes();
                         break;
                     case CONFIGURED:
+                    case ERROR:
                     case RELEASED:
                         // Do nothing
                         break;
                     default:
                         throw new IllegalStateException("Unknown state: " + mState);
                 }
-            }
+            });
         }
 
         @Override
         public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int index,
                 @NonNull MediaCodec.BufferInfo bufferInfo) {
-            synchronized (mLock) {
+            mEncoderExecutor.execute(() -> {
                 switch (mState) {
                     case STARTED:
                     case PAUSED:
@@ -468,8 +705,12 @@
                     case PENDING_START:
                     case PENDING_START_PAUSED:
                     case PENDING_RELEASE:
-                        final EncoderCallback encoderCallback = mEncoderCallback;
-                        final Executor executor = mEncoderCallbackExecutor;
+                        final EncoderCallback encoderCallback;
+                        final Executor executor;
+                        synchronized (mLock) {
+                            encoderCallback = mEncoderCallback;
+                            executor = mEncoderCallbackExecutor;
+                        }
 
                         // Handle start of stream
                         if (!mHasFirstData) {
@@ -507,25 +748,21 @@
                                     new FutureCallback<Void>() {
                                         @Override
                                         public void onSuccess(@Nullable Void result) {
-                                            synchronized (mLock) {
-                                                mEncodedDataSet.remove(encodedData);
-                                            }
+                                            mEncodedDataSet.remove(encodedData);
                                         }
 
                                         @Override
                                         public void onFailure(Throwable t) {
-                                            synchronized (mLock) {
-                                                mEncodedDataSet.remove(encodedData);
-                                                if (t instanceof MediaCodec.CodecException) {
-                                                    handleEncodeError(
-                                                            (MediaCodec.CodecException) t);
-                                                } else {
-                                                    handleEncodeError(EncodeException.ERROR_UNKNOWN,
-                                                            t.getMessage(), t);
-                                                }
+                                            mEncodedDataSet.remove(encodedData);
+                                            if (t instanceof MediaCodec.CodecException) {
+                                                handleEncodeError(
+                                                        (MediaCodec.CodecException) t);
+                                            } else {
+                                                handleEncodeError(EncodeException.ERROR_UNKNOWN,
+                                                        t.getMessage(), t);
                                             }
                                         }
-                                    }, CameraXExecutors.directExecutor());
+                                    }, mEncoderExecutor);
                             try {
                                 executor.execute(() -> encoderCallback.onEncodedData(encodedData));
                             } catch (RejectedExecutionException e) {
@@ -543,56 +780,29 @@
 
                         // Handle end of stream
                         if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            // Wait for all data closed
-                            List<ListenableFuture<Void>> waitForCloseFutures = new ArrayList<>();
-                            for (EncodedDataImpl dataToClose : mEncodedDataSet) {
-                                waitForCloseFutures.add(dataToClose.getClosedFuture());
-                            }
-                            Futures.addCallback(Futures.allAsList(waitForCloseFutures),
-                                    new FutureCallback<List<Void>>() {
-                                        @Override
-                                        public void onSuccess(@Nullable List<Void> result) {
-                                            synchronized (mLock) {
-                                                mMediaCodec.stop();
-                                                try {
-                                                    executor.execute(encoderCallback::onEncodeStop);
-                                                } catch (RejectedExecutionException e) {
-                                                    Logger.e(TAG,
-                                                            "Unable to post to the supplied "
-                                                                    + "executor.", e);
-                                                }
-                                                handleStopped();
-                                            }
-                                        }
-
-                                        @Override
-                                        public void onFailure(Throwable t) {
-                                            synchronized (mLock) {
-                                                if (t instanceof MediaCodec.CodecException) {
-                                                    handleEncodeError(
-                                                            (MediaCodec.CodecException) t);
-                                                } else {
-                                                    handleEncodeError(EncodeException.ERROR_UNKNOWN,
-                                                            t.getMessage(), t);
-                                                }
-                                            }
-                                        }
-                                    }, CameraXExecutors.directExecutor());
+                            stopMediaCodec(() -> {
+                                try {
+                                    executor.execute(encoderCallback::onEncodeStop);
+                                } catch (RejectedExecutionException e) {
+                                    Logger.e(TAG, "Unable to post to the supplied executor.", e);
+                                }
+                            });
                         }
                         break;
                     case CONFIGURED:
+                    case ERROR:
                     case RELEASED:
                         // Do nothing
                         break;
                     default:
                         throw new IllegalStateException("Unknown state: " + mState);
                 }
-            }
+            });
         }
 
         @Override
         public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
-            synchronized (mLock) {
+            mEncoderExecutor.execute(() -> {
                 switch (mState) {
                     case STARTED:
                     case PAUSED:
@@ -603,19 +813,20 @@
                         handleEncodeError(e);
                         break;
                     case CONFIGURED:
+                    case ERROR:
                     case RELEASED:
                         // Do nothing
                         break;
                     default:
                         throw new IllegalStateException("Unknown state: " + mState);
                 }
-            }
+            });
         }
 
         @Override
         public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec,
                 @NonNull MediaFormat mediaFormat) {
-            synchronized (mLock) {
+            mEncoderExecutor.execute(() -> {
                 switch (mState) {
                     case STARTED:
                     case PAUSED:
@@ -623,28 +834,36 @@
                     case PENDING_START:
                     case PENDING_START_PAUSED:
                     case PENDING_RELEASE:
-                        EncoderCallback encoderCallback = mEncoderCallback;
+                        EncoderCallback encoderCallback;
+                        Executor executor;
+                        synchronized (mLock) {
+                            encoderCallback = mEncoderCallback;
+                            executor = mEncoderCallbackExecutor;
+                        }
                         try {
-                            mEncoderCallbackExecutor.execute(
+                            executor.execute(
                                     () -> encoderCallback.onOutputConfigUpdate(() -> mediaFormat));
                         } catch (RejectedExecutionException e) {
                             Logger.e(TAG, "Unable to post to the supplied executor.", e);
                         }
                         break;
                     case CONFIGURED:
+                    case ERROR:
                     case RELEASED:
                         // Do nothing
                         break;
                     default:
                         throw new IllegalStateException("Unknown state: " + mState);
                 }
-            }
+            });
         }
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     class SurfaceInput implements Encoder.SurfaceInput {
 
+        private final Object mLock = new Object();
+
         @GuardedBy("mLock")
         private Surface mSurface;
 
@@ -663,42 +882,50 @@
         @Override
         public void setOnSurfaceUpdateListener(@NonNull Executor executor,
                 @NonNull OnSurfaceUpdateListener listener) {
+            Surface surface;
             synchronized (mLock) {
                 mSurfaceUpdateListener = Preconditions.checkNotNull(listener);
                 mSurfaceUpdateExecutor = Preconditions.checkNotNull(executor);
-
-                if (mSurface != null) {
-                    notifySurfaceUpdate(mSurface);
-                }
-
+                surface = mSurface;
+            }
+            if (surface != null) {
+                notifySurfaceUpdate(executor, listener, surface);
             }
         }
 
-        @GuardedBy("mLock")
         @SuppressLint("UnsafeNewApiCall")
         void resetSurface() {
-            if (Build.VERSION.SDK_INT >= 23) {
-                if (mSurface == null) {
-                    mSurface = MediaCodec.createPersistentInputSurface();
-                    notifySurfaceUpdate(mSurface);
+            Surface surface;
+            Executor executor;
+            OnSurfaceUpdateListener listener;
+            synchronized (mLock) {
+                if (Build.VERSION.SDK_INT >= 23) {
+                    if (mSurface == null) {
+                        mSurface = MediaCodec.createPersistentInputSurface();
+                        surface = mSurface;
+                    } else {
+                        surface = null;
+                    }
+                    mMediaCodec.setInputSurface(mSurface);
+                } else {
+                    mSurface = mMediaCodec.createInputSurface();
+                    surface = mSurface;
                 }
-                mMediaCodec.setInputSurface(mSurface);
-            } else {
-                mSurface = mMediaCodec.createInputSurface();
-                notifySurfaceUpdate(mSurface);
+                listener = mSurfaceUpdateListener;
+                executor = mSurfaceUpdateExecutor;
+            }
+            if (surface != null && listener != null && executor != null) {
+                notifySurfaceUpdate(executor, listener, surface);
             }
         }
 
-        @GuardedBy("mLock")
-        private void notifySurfaceUpdate(@NonNull Surface surface) {
-            if (mSurfaceUpdateListener != null && mSurfaceUpdateExecutor != null) {
-                OnSurfaceUpdateListener listener = mSurfaceUpdateListener;
-                try {
-                    mSurfaceUpdateExecutor.execute(() -> listener.onSurfaceUpdate(surface));
-                } catch (RejectedExecutionException e) {
-                    Logger.e(TAG, "Unable to post to the supplied executor.", e);
-                    surface.release();
-                }
+        private void notifySurfaceUpdate(@NonNull Executor executor,
+                @NonNull OnSurfaceUpdateListener listener, @NonNull Surface surface) {
+            try {
+                executor.execute(() -> listener.onSurfaceUpdate(surface));
+            } catch (RejectedExecutionException e) {
+                Logger.e(TAG, "Unable to post to the supplied executor.", e);
+                surface.release();
             }
         }
     }
@@ -706,139 +933,91 @@
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     class ByteBufferInput implements Encoder.ByteBufferInput {
 
-        @GuardedBy("mLock")
-        private final Queue<Consumer<Integer>> mListenerQueue = new ArrayDeque<>();
+        private final Map<Observer<State>, Executor> mStateObservers = new LinkedHashMap<>();
 
-        @GuardedBy("mLock")
-        private final Queue<Integer> mFreeBufferIndexQueue = new ArrayDeque<>();
+        private State mBufferProviderState = State.INACTIVE;
+
+        private final List<ListenableFuture<InputBuffer>> mAcquisitionList = new ArrayList<>();
 
         /** {@inheritDoc} */
+        @NonNull
         @Override
-        public void putByteBuffer(@NonNull ByteBuffer byteBuffer) {
-            synchronized (mLock) {
-                switch (mState) {
-                    case STARTED:
-                        // Here it means the byteBuffer should definitely be queued into codec.
-                        acquireFreeBufferIndex(freeBufferIndex -> {
-                            ByteBuffer inputBuffer = null;
-                            synchronized (mLock) {
-                                if (mState == STARTED
-                                        || mState == PAUSED
-                                        || mState == STOPPING
-                                        || mState == PENDING_START
-                                        || mState == PENDING_START_PAUSED
-                                        || mState == PENDING_RELEASE) {
-                                    try {
-                                        inputBuffer = mMediaCodec.getInputBuffer(freeBufferIndex);
-                                    } catch (MediaCodec.CodecException e) {
-                                        handleEncodeError(e);
-                                        return;
-                                    }
-                                }
-                            }
-
-                            if (inputBuffer == null) {
-                                return;
-                            }
-                            inputBuffer.put(byteBuffer);
-
-                            synchronized (mLock) {
-                                if (mState == STARTED
-                                        || mState == PAUSED
-                                        || mState == STOPPING
-                                        || mState == PENDING_START
-                                        || mState == PENDING_START_PAUSED
-                                        || mState == PENDING_RELEASE) {
-                                    try {
-                                        mMediaCodec.queueInputBuffer(freeBufferIndex, 0,
-                                                inputBuffer.position(),
-                                                generatePresentationTimeUs(),
-                                                0);
-                                    } catch (MediaCodec.CodecException e) {
-                                        handleEncodeError(e);
-                                        return;
-                                    }
-                                }
-                            }
-                        });
-                        break;
-                    case PAUSED:
-                        // Drop the data
-                        break;
-                    case CONFIGURED:
-                    case STOPPING:
-                    case PENDING_START:
-                    case PENDING_RELEASE:
-                    case RELEASED:
-                        // Do nothing
-                        break;
-                    default:
-                        throw new IllegalStateException("Unknown state: " + mState);
-                }
-            }
-        }
-
-        @GuardedBy("mLock")
-        void signalEndOfInputStream() {
-            acquireFreeBufferIndex(freeBufferIndex -> {
-                synchronized (mLock) {
-                    switch (mState) {
-                        case STARTED:
-                        case PAUSED:
-                        case STOPPING:
-                        case PENDING_START:
-                        case PENDING_START_PAUSED:
-                        case PENDING_RELEASE:
-                            try {
-                                mMediaCodec.queueInputBuffer(freeBufferIndex, 0, 0,
-                                        generatePresentationTimeUs(),
-                                        MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                            } catch (MediaCodec.CodecException e) {
-                                handleEncodeError(e);
-                            }
-                            break;
-                        case CONFIGURED:
-                        case RELEASED:
-                            // Do nothing
-                            break;
-                        default:
-                            throw new IllegalStateException("Unknown state: " + mState);
-                    }
-                }
+        public ListenableFuture<State> fetchData() {
+            return CallbackToFutureAdapter.getFuture(completer -> {
+                mEncoderExecutor.execute(() -> completer.set(mBufferProviderState));
+                return "fetchData";
             });
         }
 
-        @GuardedBy("mLock")
-        void putFreeBufferIndex(int index) {
-            mFreeBufferIndexQueue.offer(index);
-            match();
+        /** {@inheritDoc} */
+        @NonNull
+        @Override
+        public ListenableFuture<InputBuffer> acquireBuffer() {
+            return CallbackToFutureAdapter.getFuture(completer -> {
+                mEncoderExecutor.execute(() -> {
+                    if (mBufferProviderState == State.ACTIVE) {
+                        ListenableFuture<InputBuffer> future = acquireInputBuffer();
+                        Futures.propagate(future, completer);
+                        // Cancel by outer, also cancel internal future.
+                        completer.addCancellationListener(() -> future.cancel(true),
+                                CameraXExecutors.directExecutor());
+
+                        // Keep tracking the acquisition by internal future. Once the provider state
+                        // transition to inactive, cancel the internal future can also send signal
+                        // to outer future since we propagate the internal result to the completer.
+                        mAcquisitionList.add(future);
+                        future.addListener(() -> mAcquisitionList.remove(future), mEncoderExecutor);
+                    } else if (mBufferProviderState == State.INACTIVE) {
+                        completer.setException(
+                                new IllegalStateException("BufferProvider is not active."));
+                    } else {
+                        completer.setException(
+                                new IllegalStateException(
+                                        "Unknown state: " + mBufferProviderState));
+                    }
+                });
+                return "acquireBuffer";
+            });
         }
 
-        @GuardedBy("mLock")
-        void clearFreeBuffers() {
-            mListenerQueue.clear();
-            mFreeBufferIndexQueue.clear();
+        /** {@inheritDoc} */
+        @Override
+        public void addObserver(@NonNull Executor executor, @NonNull Observer<State> observer) {
+            mEncoderExecutor.execute(() -> {
+                mStateObservers.put(Preconditions.checkNotNull(observer),
+                        Preconditions.checkNotNull(executor));
+                final State state = mBufferProviderState;
+                executor.execute(() -> observer.onNewData(state));
+            });
         }
 
-        @GuardedBy("mLock")
-        private void acquireFreeBufferIndex(
-                @NonNull Consumer<Integer> onFreeBufferIndexListener) {
-            synchronized (mLock) {
-                mListenerQueue.offer(onFreeBufferIndexListener);
-                match();
+        /** {@inheritDoc} */
+        @Override
+        public void removeObserver(@NonNull Observer<State> observer) {
+            mEncoderExecutor.execute(
+                    () -> mStateObservers.remove(Preconditions.checkNotNull(observer)));
+        }
+
+        @ExecutedBy("mEncoderExecutor")
+        void setActive(boolean isActive) {
+            final State newState = isActive ? State.ACTIVE : State.INACTIVE;
+            if (mBufferProviderState == newState) {
+                return;
             }
-        }
+            mBufferProviderState = newState;
 
-        @GuardedBy("mLock")
-        private void match() {
-            if (!mListenerQueue.isEmpty() && !mFreeBufferIndexQueue.isEmpty()) {
-                Consumer<Integer> listener = mListenerQueue.poll();
-                Integer index = mFreeBufferIndexQueue.poll();
+            if (newState == State.INACTIVE) {
+                for (ListenableFuture<InputBuffer> future : mAcquisitionList) {
+                    future.cancel(true);
+                }
+                mAcquisitionList.clear();
+            }
+
+            for (Map.Entry<Observer<State>, Executor> entry : mStateObservers.entrySet()) {
                 try {
-                    mExecutor.execute(() -> listener.accept(index));
+                    entry.getValue().execute(() -> entry.getKey().onNewData(newState));
                 } catch (RejectedExecutionException e) {
                     Logger.e(TAG, "Unable to post to the supplied executor.", e);
-                    putFreeBufferIndex(index);
                 }
             }
         }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/InputBuffer.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/InputBuffer.java
new file mode 100644
index 0000000..fba76c7
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/InputBuffer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.encoder;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.nio.ByteBuffer;
+
+/**
+ * InputBuffer is provided by the {@link Encoder} and used to feed data into the {@link Encoder}.
+ *
+ * <p>Once {@link InputBuffer} is complete or no longer needed, {@link #submit} or
+ * {@link #cancel} must be called to return the request to the encoder, otherwise, it will cause
+ * leakage or failure.
+ */
+public interface InputBuffer {
+
+    /**
+     * Gets the {@link ByteBuffer} of the input buffer.
+     *
+     * <p>Before submitting the InputBuffer, the internal position of the ByteBuffer must be set
+     * to prepare it for reading, e.g. the {@link ByteBuffer#position} is the beginning of the
+     * data, usually 0; the {@link ByteBuffer#limit} is the end of the data. Usually
+     * {@link ByteBuffer#flip} is used after writing data.
+     *
+     * <p>Getting ByteBuffer multiple times won't reset its internal position and data.
+     *
+     * @throws {@link IllegalStateException} if InputBuffer is submitted or canceled.
+     */
+    @NonNull
+    ByteBuffer getByteBuffer();
+
+    /**
+     * Sets the timestamp of the input buffer in microseconds.
+     *
+     * @throws {@link IllegalStateException} if InputBuffer is submitted or canceled.
+     */
+    void setPresentationTimeUs(long presentationTimeUs);
+
+    /**
+     * Denotes the input buffer is the end of the data stream.
+     *
+     * @throws {@link IllegalStateException} if InputBuffer is submitted or canceled.
+     */
+    void setEndOfStream(boolean isEndOfStream);
+
+    /**
+     * Submits the input buffer.
+     *
+     * <p>The data will be written to encoder only when {@link #submit} is called.
+     *
+     * @return {@code true} if submit successfully; {@code false} if already submitted, failed or
+     * has been canceled.
+     */
+    boolean submit();
+
+    /**
+     * Returns the request to encoder without taking any effect.
+     *
+     * @return {@code true} if cancel successfully; {@code false} if already submitted, failed or
+     * has been canceled.
+     */
+    boolean cancel();
+
+    /**
+     * The {@link ListenableFuture} that is complete when {@link #submit} or {@link #cancel} is
+     * called.
+     */
+    @NonNull
+    ListenableFuture<Void> getTerminationFuture();
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/InputBufferImpl.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/InputBufferImpl.java
new file mode 100644
index 0000000..3f88d73
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/encoder/InputBufferImpl.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.encoder;
+
+import android.media.MediaCodec;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
+import androidx.core.util.Preconditions;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+class InputBufferImpl implements InputBuffer {
+    private final MediaCodec mMediaCodec;
+    private final int mBufferIndex;
+    private final ByteBuffer mByteBuffer;
+    private final ListenableFuture<Void> mTerminationFuture;
+    private final CallbackToFutureAdapter.Completer<Void> mTerminationCompleter;
+    private final AtomicBoolean mTerminated = new AtomicBoolean(false);
+    private long mPresentationTimeUs = 0L;
+    private boolean mIsEndOfStream = false;
+
+    InputBufferImpl(@NonNull MediaCodec mediaCodec, @IntRange(from = 0) int bufferIndex)
+            throws MediaCodec.CodecException {
+        mMediaCodec = Preconditions.checkNotNull(mediaCodec);
+        mBufferIndex = Preconditions.checkArgumentNonnegative(bufferIndex);
+        mByteBuffer = mediaCodec.getInputBuffer(bufferIndex);
+        AtomicReference<Completer<Void>> ref = new AtomicReference<>();
+        mTerminationFuture = CallbackToFutureAdapter.getFuture(
+                completer -> {
+                    ref.set(completer);
+                    return "Terminate InputBuffer";
+                });
+        mTerminationCompleter = Preconditions.checkNotNull(ref.get());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @NonNull
+    public ByteBuffer getByteBuffer() {
+        throwIfTerminated();
+        return mByteBuffer;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setPresentationTimeUs(long presentationTimeUs) {
+        throwIfTerminated();
+        Preconditions.checkArgument(presentationTimeUs >= 0L);
+        mPresentationTimeUs = presentationTimeUs;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEndOfStream(boolean endOfStream) {
+        throwIfTerminated();
+        mIsEndOfStream = endOfStream;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean submit() {
+        if (mTerminated.getAndSet(true)) {
+            return false;
+        }
+        try {
+            mMediaCodec.queueInputBuffer(mBufferIndex,
+                    mByteBuffer.position(),
+                    mByteBuffer.limit(),
+                    mPresentationTimeUs,
+                    mIsEndOfStream ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+            mTerminationCompleter.set(null);
+            return true;
+        } catch (IllegalStateException e) {
+            mTerminationCompleter.setException(e);
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean cancel() {
+        if (mTerminated.getAndSet(true)) {
+            return false;
+        }
+        try {
+            mMediaCodec.queueInputBuffer(mBufferIndex, 0, 0, 0, 0);
+            mTerminationCompleter.set(null);
+        } catch (IllegalStateException e) {
+            mTerminationCompleter.setException(e);
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @NonNull
+    public ListenableFuture<Void> getTerminationFuture() {
+        return Futures.nonCancellationPropagating(mTerminationFuture);
+    }
+
+    private void throwIfTerminated() {
+        if (mTerminated.get()) {
+            throw new IllegalStateException("The buffer is submitted or canceled.");
+        }
+    }
+}
diff --git a/camera/integration-tests/coretestapp/build.gradle b/camera/integration-tests/coretestapp/build.gradle
index a304989..bee7e6ae 100644
--- a/camera/integration-tests/coretestapp/build.gradle
+++ b/camera/integration-tests/coretestapp/build.gradle
@@ -69,13 +69,13 @@
     implementation(project(":camera:camera-camera2-pipe-integration"))
     implementation(project(":camera:camera-core"))
     implementation(project(":camera:camera-lifecycle"))
+    implementation(project(":appcompat:appcompat"))
+    implementation(project(":activity:activity"))
+    implementation(project(":fragment:fragment"))
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
 
     // Android Support Library
     api(CONSTRAINT_LAYOUT, { transitive = true })
-    implementation("androidx.appcompat:appcompat:1.1.0")
-    implementation("androidx.activity:activity:1.2.0-alpha05")
-    implementation("androidx.fragment:fragment:1.3.0-alpha05")
     implementation(GUAVA_ANDROID)
     implementation(ESPRESSO_IDLING_RESOURCE)
 
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 5b604f6..4e2edca 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -1,6 +1,12 @@
 // Signature format: 4.0
 package androidx.car.app {
 
+  public final class AppInfo {
+    method public int getLatestCarAppApiLevel();
+    method public String getLibraryVersion();
+    method public int getMinCarAppApiLevel();
+  }
+
   public class AppManager {
     method public void invalidate();
     method public void setSurfaceListener(androidx.car.app.SurfaceListener?);
@@ -16,35 +22,24 @@
 
   public abstract class CarAppService extends android.app.Service implements androidx.lifecycle.LifecycleOwner {
     ctor public CarAppService();
-    method @CallSuper public void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
-    method public void finish();
+    method @CallSuper public final void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
+    method public final void finish();
     method public final androidx.car.app.CarContext getCarContext();
-    method public androidx.car.app.HostInfo? getHostInfo();
-    method public androidx.lifecycle.Lifecycle getLifecycle();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
+    method public final androidx.car.app.HostInfo? getHostInfo();
+    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method @CallSuper public final android.os.IBinder? onBind(android.content.Intent);
     method public void onCarAppFinished();
     method public void onCarConfigurationChanged(android.content.res.Configuration);
     method public abstract androidx.car.app.Screen onCreateScreen(android.content.Intent);
     method public final void onDestroy();
     method public void onNewIntent(android.content.Intent);
     method public final boolean onUnbind(android.content.Intent);
-  }
-
-  public class CarAppVersion {
-    method public boolean isGreaterOrEqualTo(androidx.car.app.CarAppVersion);
-    method public static androidx.car.app.CarAppVersion? of(String) throws androidx.car.app.MalformedVersionException;
-    field public static final androidx.car.app.CarAppVersion HANDSHAKE_MIN_VERSION;
-    field public static final androidx.car.app.CarAppVersion INSTANCE;
-  }
-
-  public enum CarAppVersion.ReleaseSuffix {
-    method public static androidx.car.app.CarAppVersion.ReleaseSuffix fromString(String);
-    enum_constant public static final androidx.car.app.CarAppVersion.ReleaseSuffix RELEASE_SUFFIX_BETA;
-    enum_constant public static final androidx.car.app.CarAppVersion.ReleaseSuffix RELEASE_SUFFIX_EAP;
+    field public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService";
   }
 
   public class CarContext extends android.content.ContextWrapper {
     method public void finishCarApp();
+    method public int getCarAppApiLevel();
     method public Object getCarService(String);
     method public <T> T getCarService(Class<T!>);
     method public String getCarServiceName(Class<?>);
@@ -53,11 +48,11 @@
     method public void startCarApp(android.content.Intent);
     method public static void startCarApp(android.content.Intent, android.content.Intent);
     field public static final String ACTION_NAVIGATE = "androidx.car.app.action.NAVIGATE";
-    field public static final String APP_SERVICE = "app_manager";
+    field public static final String APP_SERVICE = "app";
     field public static final String CAR_SERVICE = "car";
-    field public static final String NAVIGATION_SERVICE = "navigation_manager";
-    field public static final String SCREEN_MANAGER_SERVICE = "screen_manager";
-    field public static final String START_CAR_APP_BINDER_KEY = "StartCarAppBinderKey";
+    field public static final String EXTRA_START_CAR_APP_BINDER_KEY = "androidx.car.app.extra.START_CAR_APP_BINDER_KEY";
+    field public static final String NAVIGATION_SERVICE = "navigation";
+    field public static final String SCREEN_SERVICE = "screen";
   }
 
   public final class CarToast {
@@ -89,21 +84,31 @@
   }
 
   public class HostInfo {
-    ctor public HostInfo(String, int);
     method public String getPackageName();
     method public int getUid();
   }
 
-  public class MalformedVersionException extends java.lang.Exception {
-    ctor public MalformedVersionException(String?);
-    ctor public MalformedVersionException(String, Throwable);
-    ctor public MalformedVersionException(Throwable?);
+  public interface OnCheckedChangeListenerWrapper {
+    method public void onCheckedChange(boolean, androidx.car.app.OnDoneCallback);
+  }
+
+  public interface OnDoneCallback {
+    method public void onFailure(androidx.car.app.serialization.Bundleable);
+    method public void onSuccess(androidx.car.app.serialization.Bundleable?);
+  }
+
+  public interface OnItemVisibilityChangedListenerWrapper {
+    method public void onItemVisibilityChanged(int, int, androidx.car.app.OnDoneCallback);
   }
 
   public interface OnScreenResultCallback {
     method public void onScreenResult(Object?);
   }
 
+  public interface OnSelectedListenerWrapper {
+    method public void onSelected(int, androidx.car.app.OnDoneCallback);
+  }
+
   public abstract class Screen implements androidx.lifecycle.LifecycleOwner {
     ctor protected Screen(androidx.car.app.CarContext);
     method public final void finish();
@@ -111,8 +116,8 @@
     method public final androidx.lifecycle.Lifecycle getLifecycle();
     method public String? getMarker();
     method public final androidx.car.app.ScreenManager getScreenManager();
-    method public abstract androidx.car.app.model.Template getTemplate();
     method public final void invalidate();
+    method public abstract androidx.car.app.model.Template onGetTemplate();
     method public void setMarker(String?);
     method public void setResult(Object?);
     field public static final String ROOT = "ROOT";
@@ -132,6 +137,11 @@
     method public void onSearchTextChanged(String);
   }
 
+  public interface SearchListenerWrapper {
+    method public void onSearchSubmitted(String, androidx.car.app.OnDoneCallback);
+    method public void onSearchTextChanged(String, androidx.car.app.OnDoneCallback);
+  }
+
   public class SurfaceContainer {
     ctor public SurfaceContainer(android.view.Surface?, int, int, int);
     method public int getDpi();
@@ -153,37 +163,10 @@
 
 }
 
-package androidx.car.app.host {
+package androidx.car.app.annotations {
 
-  public interface OnCheckedChangeListenerWrapper {
-    method public void onCheckedChange(boolean, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface OnDoneCallback {
-    method public void onFailure(androidx.car.app.serialization.Bundleable);
-    method public void onSuccess(androidx.car.app.serialization.Bundleable?);
-  }
-
-  public interface OnItemVisibilityChangedListenerWrapper {
-    method public void onItemVisibilityChanged(int, int, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface OnSelectedListenerWrapper {
-    method public void onSelected(int, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface SearchListenerWrapper {
-    method public void onSearchSubmitted(String, androidx.car.app.host.OnDoneCallback);
-    method public void onSearchTextChanged(String, androidx.car.app.host.OnDoneCallback);
-  }
-
-}
-
-package androidx.car.app.host.model {
-
-  public interface OnClickListenerWrapper {
-    method public boolean isParkedOnly();
-    method public void onClick(androidx.car.app.host.OnDoneCallback);
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface RequiresCarApi {
+    method public abstract int value();
   }
 
 }
@@ -194,7 +177,7 @@
     method public static androidx.car.app.model.Action.Builder builder();
     method public androidx.car.app.model.CarColor getBackgroundColor();
     method public androidx.car.app.model.CarIcon? getIcon();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public androidx.car.app.model.CarText? getTitle();
     method public int getType();
     method public boolean isStandard();
@@ -231,7 +214,6 @@
     ctor public ActionStrip.Builder();
     method public androidx.car.app.model.ActionStrip.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.ActionStrip build();
-    method public androidx.car.app.model.ActionStrip.Builder clearActions();
   }
 
   public class CarColor {
@@ -273,8 +255,6 @@
     field public static final int TYPE_CUSTOM = 1; // 0x1
     field public static final int TYPE_ERROR = 6; // 0x6
     field public static final int TYPE_UNKNOWN = 0; // 0x0
-    field public static final int TYPE_WILLIAM_ALERT = 7; // 0x7
-    field public static final androidx.car.app.model.CarIcon WILLIAM_ALERT;
   }
 
   public static final class CarIcon.Builder {
@@ -358,7 +338,7 @@
     method public static androidx.car.app.model.GridItem.Builder builder();
     method public androidx.car.app.model.CarIcon getImage();
     method public int getImageType();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public androidx.car.app.model.CarText? getText();
     method public androidx.car.app.model.CarText? getTitle();
     method public androidx.car.app.model.Toggle? getToggle();
@@ -404,10 +384,9 @@
     method public static androidx.car.app.model.ItemList.Builder builder();
     method public java.util.List<java.lang.Object!> getItems();
     method public androidx.car.app.model.CarText? getNoItemsMessage();
-    method public androidx.car.app.host.OnItemVisibilityChangedListenerWrapper? getOnItemsVisibilityChangeListener();
-    method public androidx.car.app.host.OnSelectedListenerWrapper? getOnSelectedListener();
+    method public androidx.car.app.OnItemVisibilityChangedListenerWrapper? getOnItemsVisibilityChangedListener();
+    method public androidx.car.app.OnSelectedListenerWrapper? getOnSelectedListener();
     method public int getSelectedIndex();
-    method public boolean isRefresh(androidx.car.app.model.ItemList?, androidx.car.app.utils.Logger);
   }
 
   public static final class ItemList.Builder {
@@ -416,8 +395,8 @@
     method public androidx.car.app.model.ItemList build();
     method public androidx.car.app.model.ItemList.Builder clearItems();
     method public androidx.car.app.model.ItemList.Builder setNoItemsMessage(CharSequence?);
-    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangeListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener?);
-    method public androidx.car.app.model.ItemList.Builder setSelectable(androidx.car.app.model.ItemList.OnSelectedListener?);
+    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangedListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener?);
+    method public androidx.car.app.model.ItemList.Builder setOnSelectedListener(androidx.car.app.model.ItemList.OnSelectedListener?);
     method public androidx.car.app.model.ItemList.Builder setSelectedIndex(int);
   }
 
@@ -449,7 +428,6 @@
   public static final class ListTemplate.Builder {
     method public androidx.car.app.model.ListTemplate.Builder addList(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.ListTemplate build();
-    method public androidx.car.app.model.ListTemplate.Builder clearAllLists();
     method public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
     method public androidx.car.app.model.ListTemplate.Builder setLoading(boolean);
@@ -459,7 +437,7 @@
 
   public final class MessageTemplate implements androidx.car.app.model.Template {
     method public static androidx.car.app.model.MessageTemplate.Builder builder(CharSequence);
-    method public androidx.car.app.model.ActionList? getActionList();
+    method public androidx.car.app.model.ActionList? getActions();
     method public androidx.car.app.model.CarText? getDebugMessage();
     method public androidx.car.app.model.Action? getHeaderAction();
     method public androidx.car.app.model.CarIcon? getIcon();
@@ -495,19 +473,22 @@
     method public void onClick();
   }
 
+  public interface OnClickListenerWrapper {
+    method public boolean isParkedOnly();
+    method public void onClick(androidx.car.app.OnDoneCallback);
+  }
+
   public final class Pane {
     method public static androidx.car.app.model.Pane.Builder builder();
-    method public androidx.car.app.model.ActionList? getActionList();
+    method public androidx.car.app.model.ActionList? getActions();
     method public java.util.List<java.lang.Object!> getRows();
     method public boolean isLoading();
-    method public boolean isRefresh(androidx.car.app.model.Pane?, androidx.car.app.utils.Logger);
   }
 
   public static final class Pane.Builder {
     ctor public Pane.Builder();
     method public androidx.car.app.model.Pane.Builder addRow(androidx.car.app.model.Row);
     method public androidx.car.app.model.Pane build();
-    method public androidx.car.app.model.Pane.Builder clearRows();
     method public androidx.car.app.model.Pane.Builder setActions(java.util.List<androidx.car.app.model.Action!>);
     method public androidx.car.app.model.Pane.Builder setLoading(boolean);
   }
@@ -590,10 +571,9 @@
 
   public class Row implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.Row.Builder builder();
-    method public int getFlags();
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.Metadata getMetadata();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public int getRowImageType();
     method public java.util.List<androidx.car.app.model.CarText!> getTexts();
     method public androidx.car.app.model.CarText getTitle();
@@ -604,17 +584,12 @@
     field public static final int IMAGE_TYPE_ICON = 4; // 0x4
     field public static final int IMAGE_TYPE_LARGE = 2; // 0x2
     field public static final int IMAGE_TYPE_SMALL = 1; // 0x1
-    field public static final int ROW_FLAG_NONE = 1; // 0x1
-    field public static final int ROW_FLAG_SECTION_HEADER = 4; // 0x4
-    field public static final int ROW_FLAG_SHOW_DIVIDERS = 2; // 0x2
   }
 
   public static final class Row.Builder {
     method public androidx.car.app.model.Row.Builder addText(CharSequence);
     method public androidx.car.app.model.Row build();
-    method public androidx.car.app.model.Row.Builder clearText();
     method public androidx.car.app.model.Row.Builder setBrowsable(boolean);
-    method public androidx.car.app.model.Row.Builder setFlags(int);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon?);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon?, int);
     method public androidx.car.app.model.Row.Builder setMetadata(androidx.car.app.model.Metadata);
@@ -630,7 +605,7 @@
     method public String? getInitialSearchText();
     method public androidx.car.app.model.ItemList? getItemList();
     method public String? getSearchHint();
-    method public androidx.car.app.host.SearchListenerWrapper getSearchListener();
+    method public androidx.car.app.SearchListenerWrapper getSearchListener();
     method public boolean isLoading();
     method public boolean isShowKeyboardByDefault();
   }
@@ -654,7 +629,6 @@
 
   public interface Template {
     method public default void checkPermissions(android.content.Context);
-    method public default boolean isRefresh(androidx.car.app.model.Template, androidx.car.app.utils.Logger);
   }
 
   public final class TemplateInfo {
@@ -680,7 +654,7 @@
 
   public class Toggle {
     method public static androidx.car.app.model.Toggle.Builder builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
-    method public androidx.car.app.host.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
+    method public androidx.car.app.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
     method public boolean isChecked();
   }
 
@@ -735,7 +709,6 @@
   public class RowConstraints {
     method public static androidx.car.app.model.constraints.RowConstraints.Builder builder();
     method public androidx.car.app.model.constraints.CarIconConstraints getCarIconConstraints();
-    method public int getFlagOverrides();
     method public int getMaxActionsExclusive();
     method public int getMaxTextLinesPerRow();
     method public boolean isImageAllowed();
@@ -753,7 +726,6 @@
   public static final class RowConstraints.Builder {
     method public androidx.car.app.model.constraints.RowConstraints build();
     method public androidx.car.app.model.constraints.RowConstraints.Builder setCarIconConstraints(androidx.car.app.model.constraints.CarIconConstraints);
-    method public androidx.car.app.model.constraints.RowConstraints.Builder setFlagOverrides(int);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setImageAllowed(boolean);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setMaxActionsExclusive(int);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setMaxTextLinesPerRow(int);
@@ -794,15 +766,17 @@
 package androidx.car.app.navigation {
 
   public class NavigationManager {
+    method @MainThread public void clearNavigationManagerListener();
     method @MainThread public void navigationEnded();
     method @MainThread public void navigationStarted();
-    method @MainThread public void setListener(androidx.car.app.navigation.NavigationManagerListener?);
+    method @MainThread public void setNavigationManagerListener(androidx.car.app.navigation.NavigationManagerListener);
+    method @MainThread public void setNavigationManagerListener(java.util.concurrent.Executor, androidx.car.app.navigation.NavigationManagerListener);
     method @MainThread public void updateTrip(androidx.car.app.navigation.model.Trip);
   }
 
   public interface NavigationManagerListener {
     method public void onAutoDriveEnabled();
-    method public void stopNavigation();
+    method public void onStopNavigation();
   }
 
 }
@@ -864,7 +838,11 @@
     field public static final int TYPE_DESTINATION_RIGHT = 42; // 0x2a
     field public static final int TYPE_DESTINATION_STRAIGHT = 40; // 0x28
     field public static final int TYPE_FERRY_BOAT = 37; // 0x25
+    field public static final int TYPE_FERRY_BOAT_LEFT = 47; // 0x2f
+    field public static final int TYPE_FERRY_BOAT_RIGHT = 48; // 0x30
     field public static final int TYPE_FERRY_TRAIN = 38; // 0x26
+    field public static final int TYPE_FERRY_TRAIN_LEFT = 49; // 0x31
+    field public static final int TYPE_FERRY_TRAIN_RIGHT = 50; // 0x32
     field public static final int TYPE_FORK_LEFT = 25; // 0x19
     field public static final int TYPE_FORK_RIGHT = 26; // 0x1a
     field public static final int TYPE_KEEP_LEFT = 3; // 0x3
@@ -885,12 +863,16 @@
     field public static final int TYPE_ON_RAMP_SLIGHT_RIGHT = 14; // 0xe
     field public static final int TYPE_ON_RAMP_U_TURN_LEFT = 19; // 0x13
     field public static final int TYPE_ON_RAMP_U_TURN_RIGHT = 20; // 0x14
-    field public static final int TYPE_ROUNDABOUT_ENTER = 30; // 0x1e
+    field @Deprecated public static final int TYPE_ROUNDABOUT_ENTER = 30; // 0x1e
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW = 34; // 0x22
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE = 35; // 0x23
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW = 32; // 0x20
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE = 33; // 0x21
-    field public static final int TYPE_ROUNDABOUT_EXIT = 31; // 0x1f
+    field public static final int TYPE_ROUNDABOUT_ENTER_CCW = 45; // 0x2d
+    field public static final int TYPE_ROUNDABOUT_ENTER_CW = 43; // 0x2b
+    field @Deprecated public static final int TYPE_ROUNDABOUT_EXIT = 31; // 0x1f
+    field public static final int TYPE_ROUNDABOUT_EXIT_CCW = 46; // 0x2e
+    field public static final int TYPE_ROUNDABOUT_EXIT_CW = 44; // 0x2c
     field public static final int TYPE_STRAIGHT = 36; // 0x24
     field public static final int TYPE_TURN_NORMAL_LEFT = 7; // 0x7
     field public static final int TYPE_TURN_NORMAL_RIGHT = 8; // 0x8
@@ -957,9 +939,9 @@
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate build();
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
-    method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList?);
     method @VisibleForTesting public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemListForTesting(androidx.car.app.model.ItemList?);
+    method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setTitle(CharSequence?);
   }
 
@@ -978,9 +960,9 @@
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate build();
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
-    method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList?);
     method @VisibleForTesting public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemListForTesting(androidx.car.app.model.ItemList?);
+    method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setNavigateAction(androidx.car.app.model.Action);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence?);
   }
@@ -997,8 +979,8 @@
   public static final class RoutingInfo.Builder {
     method public androidx.car.app.navigation.model.RoutingInfo build();
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setCurrentStep(androidx.car.app.navigation.model.Step, androidx.car.app.model.Distance);
-    method public androidx.car.app.navigation.model.RoutingInfo.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setJunctionImage(androidx.car.app.model.CarIcon?);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setNextStep(androidx.car.app.navigation.model.Step?);
   }
 
@@ -1023,8 +1005,8 @@
   }
 
   public final class TravelEstimate {
-    method public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, long, androidx.car.app.model.DateTimeWithZone);
-    method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, java.time.Duration, java.time.ZonedDateTime);
+    method public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, androidx.car.app.model.DateTimeWithZone);
+    method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, java.time.ZonedDateTime);
     method public static androidx.car.app.navigation.model.TravelEstimate create(androidx.car.app.model.Distance, long, androidx.car.app.model.DateTimeWithZone);
     method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate create(androidx.car.app.model.Distance, java.time.Duration, java.time.ZonedDateTime);
     method public androidx.car.app.model.DateTimeWithZone? getArrivalTimeAtDestination();
@@ -1032,12 +1014,15 @@
     method public androidx.car.app.model.CarColor getRemainingDistanceColor();
     method public androidx.car.app.model.CarColor getRemainingTimeColor();
     method public long getRemainingTimeSeconds();
+    field public static final long REMAINING_TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
   }
 
   public static final class TravelEstimate.Builder {
     method public androidx.car.app.navigation.model.TravelEstimate build();
     method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingDistanceColor(androidx.car.app.model.CarColor);
+    method @RequiresApi(26) public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTime(java.time.Duration);
     method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeSeconds(long);
   }
 
   public final class Trip {
@@ -1062,7 +1047,7 @@
     method public androidx.car.app.navigation.model.Trip.Builder clearStepTravelEstimates();
     method public androidx.car.app.navigation.model.Trip.Builder clearSteps();
     method public androidx.car.app.navigation.model.Trip.Builder setCurrentRoad(CharSequence?);
-    method public androidx.car.app.navigation.model.Trip.Builder setIsLoading(boolean);
+    method public androidx.car.app.navigation.model.Trip.Builder setLoading(boolean);
   }
 
 }
@@ -1079,8 +1064,8 @@
     method public CharSequence? getContentTitle();
     method public android.app.PendingIntent? getDeleteIntent();
     method public int getImportance();
-    method public android.graphics.Bitmap? getLargeIconBitmap();
-    method public int getSmallIconResId();
+    method public android.graphics.Bitmap? getLargeIcon();
+    method @DrawableRes public int getSmallIcon();
     method public boolean isExtended();
     method public static boolean isExtended(android.app.Notification);
   }
@@ -1089,7 +1074,6 @@
     ctor public CarAppExtender.Builder();
     method public androidx.car.app.notification.CarAppExtender.Builder addAction(@DrawableRes int, CharSequence, android.app.PendingIntent);
     method public androidx.car.app.notification.CarAppExtender build();
-    method public androidx.car.app.notification.CarAppExtender.Builder clearActions();
     method public androidx.car.app.notification.CarAppExtender.Builder setContentIntent(android.app.PendingIntent?);
     method public androidx.car.app.notification.CarAppExtender.Builder setContentText(CharSequence?);
     method public androidx.car.app.notification.CarAppExtender.Builder setContentTitle(CharSequence?);
@@ -1120,10 +1104,6 @@
 
 package androidx.car.app.utils {
 
-  public interface Logger {
-    method public void log(String);
-  }
-
   public class ThreadUtils {
     method public static void checkMainThread();
     method public static void runOnMain(Runnable);
@@ -1131,3 +1111,13 @@
 
 }
 
+package androidx.car.app.versioning {
+
+  public class CarAppApiLevels {
+    field public static final int LATEST = 1; // 0x1
+    field public static final int LEVEL_1 = 1; // 0x1
+    field public static final int OLDEST = 1; // 0x1
+  }
+
+}
+
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 5b604f6..4e2edca 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -1,6 +1,12 @@
 // Signature format: 4.0
 package androidx.car.app {
 
+  public final class AppInfo {
+    method public int getLatestCarAppApiLevel();
+    method public String getLibraryVersion();
+    method public int getMinCarAppApiLevel();
+  }
+
   public class AppManager {
     method public void invalidate();
     method public void setSurfaceListener(androidx.car.app.SurfaceListener?);
@@ -16,35 +22,24 @@
 
   public abstract class CarAppService extends android.app.Service implements androidx.lifecycle.LifecycleOwner {
     ctor public CarAppService();
-    method @CallSuper public void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
-    method public void finish();
+    method @CallSuper public final void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
+    method public final void finish();
     method public final androidx.car.app.CarContext getCarContext();
-    method public androidx.car.app.HostInfo? getHostInfo();
-    method public androidx.lifecycle.Lifecycle getLifecycle();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
+    method public final androidx.car.app.HostInfo? getHostInfo();
+    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method @CallSuper public final android.os.IBinder? onBind(android.content.Intent);
     method public void onCarAppFinished();
     method public void onCarConfigurationChanged(android.content.res.Configuration);
     method public abstract androidx.car.app.Screen onCreateScreen(android.content.Intent);
     method public final void onDestroy();
     method public void onNewIntent(android.content.Intent);
     method public final boolean onUnbind(android.content.Intent);
-  }
-
-  public class CarAppVersion {
-    method public boolean isGreaterOrEqualTo(androidx.car.app.CarAppVersion);
-    method public static androidx.car.app.CarAppVersion? of(String) throws androidx.car.app.MalformedVersionException;
-    field public static final androidx.car.app.CarAppVersion HANDSHAKE_MIN_VERSION;
-    field public static final androidx.car.app.CarAppVersion INSTANCE;
-  }
-
-  public enum CarAppVersion.ReleaseSuffix {
-    method public static androidx.car.app.CarAppVersion.ReleaseSuffix fromString(String);
-    enum_constant public static final androidx.car.app.CarAppVersion.ReleaseSuffix RELEASE_SUFFIX_BETA;
-    enum_constant public static final androidx.car.app.CarAppVersion.ReleaseSuffix RELEASE_SUFFIX_EAP;
+    field public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService";
   }
 
   public class CarContext extends android.content.ContextWrapper {
     method public void finishCarApp();
+    method public int getCarAppApiLevel();
     method public Object getCarService(String);
     method public <T> T getCarService(Class<T!>);
     method public String getCarServiceName(Class<?>);
@@ -53,11 +48,11 @@
     method public void startCarApp(android.content.Intent);
     method public static void startCarApp(android.content.Intent, android.content.Intent);
     field public static final String ACTION_NAVIGATE = "androidx.car.app.action.NAVIGATE";
-    field public static final String APP_SERVICE = "app_manager";
+    field public static final String APP_SERVICE = "app";
     field public static final String CAR_SERVICE = "car";
-    field public static final String NAVIGATION_SERVICE = "navigation_manager";
-    field public static final String SCREEN_MANAGER_SERVICE = "screen_manager";
-    field public static final String START_CAR_APP_BINDER_KEY = "StartCarAppBinderKey";
+    field public static final String EXTRA_START_CAR_APP_BINDER_KEY = "androidx.car.app.extra.START_CAR_APP_BINDER_KEY";
+    field public static final String NAVIGATION_SERVICE = "navigation";
+    field public static final String SCREEN_SERVICE = "screen";
   }
 
   public final class CarToast {
@@ -89,21 +84,31 @@
   }
 
   public class HostInfo {
-    ctor public HostInfo(String, int);
     method public String getPackageName();
     method public int getUid();
   }
 
-  public class MalformedVersionException extends java.lang.Exception {
-    ctor public MalformedVersionException(String?);
-    ctor public MalformedVersionException(String, Throwable);
-    ctor public MalformedVersionException(Throwable?);
+  public interface OnCheckedChangeListenerWrapper {
+    method public void onCheckedChange(boolean, androidx.car.app.OnDoneCallback);
+  }
+
+  public interface OnDoneCallback {
+    method public void onFailure(androidx.car.app.serialization.Bundleable);
+    method public void onSuccess(androidx.car.app.serialization.Bundleable?);
+  }
+
+  public interface OnItemVisibilityChangedListenerWrapper {
+    method public void onItemVisibilityChanged(int, int, androidx.car.app.OnDoneCallback);
   }
 
   public interface OnScreenResultCallback {
     method public void onScreenResult(Object?);
   }
 
+  public interface OnSelectedListenerWrapper {
+    method public void onSelected(int, androidx.car.app.OnDoneCallback);
+  }
+
   public abstract class Screen implements androidx.lifecycle.LifecycleOwner {
     ctor protected Screen(androidx.car.app.CarContext);
     method public final void finish();
@@ -111,8 +116,8 @@
     method public final androidx.lifecycle.Lifecycle getLifecycle();
     method public String? getMarker();
     method public final androidx.car.app.ScreenManager getScreenManager();
-    method public abstract androidx.car.app.model.Template getTemplate();
     method public final void invalidate();
+    method public abstract androidx.car.app.model.Template onGetTemplate();
     method public void setMarker(String?);
     method public void setResult(Object?);
     field public static final String ROOT = "ROOT";
@@ -132,6 +137,11 @@
     method public void onSearchTextChanged(String);
   }
 
+  public interface SearchListenerWrapper {
+    method public void onSearchSubmitted(String, androidx.car.app.OnDoneCallback);
+    method public void onSearchTextChanged(String, androidx.car.app.OnDoneCallback);
+  }
+
   public class SurfaceContainer {
     ctor public SurfaceContainer(android.view.Surface?, int, int, int);
     method public int getDpi();
@@ -153,37 +163,10 @@
 
 }
 
-package androidx.car.app.host {
+package androidx.car.app.annotations {
 
-  public interface OnCheckedChangeListenerWrapper {
-    method public void onCheckedChange(boolean, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface OnDoneCallback {
-    method public void onFailure(androidx.car.app.serialization.Bundleable);
-    method public void onSuccess(androidx.car.app.serialization.Bundleable?);
-  }
-
-  public interface OnItemVisibilityChangedListenerWrapper {
-    method public void onItemVisibilityChanged(int, int, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface OnSelectedListenerWrapper {
-    method public void onSelected(int, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface SearchListenerWrapper {
-    method public void onSearchSubmitted(String, androidx.car.app.host.OnDoneCallback);
-    method public void onSearchTextChanged(String, androidx.car.app.host.OnDoneCallback);
-  }
-
-}
-
-package androidx.car.app.host.model {
-
-  public interface OnClickListenerWrapper {
-    method public boolean isParkedOnly();
-    method public void onClick(androidx.car.app.host.OnDoneCallback);
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface RequiresCarApi {
+    method public abstract int value();
   }
 
 }
@@ -194,7 +177,7 @@
     method public static androidx.car.app.model.Action.Builder builder();
     method public androidx.car.app.model.CarColor getBackgroundColor();
     method public androidx.car.app.model.CarIcon? getIcon();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public androidx.car.app.model.CarText? getTitle();
     method public int getType();
     method public boolean isStandard();
@@ -231,7 +214,6 @@
     ctor public ActionStrip.Builder();
     method public androidx.car.app.model.ActionStrip.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.ActionStrip build();
-    method public androidx.car.app.model.ActionStrip.Builder clearActions();
   }
 
   public class CarColor {
@@ -273,8 +255,6 @@
     field public static final int TYPE_CUSTOM = 1; // 0x1
     field public static final int TYPE_ERROR = 6; // 0x6
     field public static final int TYPE_UNKNOWN = 0; // 0x0
-    field public static final int TYPE_WILLIAM_ALERT = 7; // 0x7
-    field public static final androidx.car.app.model.CarIcon WILLIAM_ALERT;
   }
 
   public static final class CarIcon.Builder {
@@ -358,7 +338,7 @@
     method public static androidx.car.app.model.GridItem.Builder builder();
     method public androidx.car.app.model.CarIcon getImage();
     method public int getImageType();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public androidx.car.app.model.CarText? getText();
     method public androidx.car.app.model.CarText? getTitle();
     method public androidx.car.app.model.Toggle? getToggle();
@@ -404,10 +384,9 @@
     method public static androidx.car.app.model.ItemList.Builder builder();
     method public java.util.List<java.lang.Object!> getItems();
     method public androidx.car.app.model.CarText? getNoItemsMessage();
-    method public androidx.car.app.host.OnItemVisibilityChangedListenerWrapper? getOnItemsVisibilityChangeListener();
-    method public androidx.car.app.host.OnSelectedListenerWrapper? getOnSelectedListener();
+    method public androidx.car.app.OnItemVisibilityChangedListenerWrapper? getOnItemsVisibilityChangedListener();
+    method public androidx.car.app.OnSelectedListenerWrapper? getOnSelectedListener();
     method public int getSelectedIndex();
-    method public boolean isRefresh(androidx.car.app.model.ItemList?, androidx.car.app.utils.Logger);
   }
 
   public static final class ItemList.Builder {
@@ -416,8 +395,8 @@
     method public androidx.car.app.model.ItemList build();
     method public androidx.car.app.model.ItemList.Builder clearItems();
     method public androidx.car.app.model.ItemList.Builder setNoItemsMessage(CharSequence?);
-    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangeListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener?);
-    method public androidx.car.app.model.ItemList.Builder setSelectable(androidx.car.app.model.ItemList.OnSelectedListener?);
+    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangedListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener?);
+    method public androidx.car.app.model.ItemList.Builder setOnSelectedListener(androidx.car.app.model.ItemList.OnSelectedListener?);
     method public androidx.car.app.model.ItemList.Builder setSelectedIndex(int);
   }
 
@@ -449,7 +428,6 @@
   public static final class ListTemplate.Builder {
     method public androidx.car.app.model.ListTemplate.Builder addList(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.ListTemplate build();
-    method public androidx.car.app.model.ListTemplate.Builder clearAllLists();
     method public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
     method public androidx.car.app.model.ListTemplate.Builder setLoading(boolean);
@@ -459,7 +437,7 @@
 
   public final class MessageTemplate implements androidx.car.app.model.Template {
     method public static androidx.car.app.model.MessageTemplate.Builder builder(CharSequence);
-    method public androidx.car.app.model.ActionList? getActionList();
+    method public androidx.car.app.model.ActionList? getActions();
     method public androidx.car.app.model.CarText? getDebugMessage();
     method public androidx.car.app.model.Action? getHeaderAction();
     method public androidx.car.app.model.CarIcon? getIcon();
@@ -495,19 +473,22 @@
     method public void onClick();
   }
 
+  public interface OnClickListenerWrapper {
+    method public boolean isParkedOnly();
+    method public void onClick(androidx.car.app.OnDoneCallback);
+  }
+
   public final class Pane {
     method public static androidx.car.app.model.Pane.Builder builder();
-    method public androidx.car.app.model.ActionList? getActionList();
+    method public androidx.car.app.model.ActionList? getActions();
     method public java.util.List<java.lang.Object!> getRows();
     method public boolean isLoading();
-    method public boolean isRefresh(androidx.car.app.model.Pane?, androidx.car.app.utils.Logger);
   }
 
   public static final class Pane.Builder {
     ctor public Pane.Builder();
     method public androidx.car.app.model.Pane.Builder addRow(androidx.car.app.model.Row);
     method public androidx.car.app.model.Pane build();
-    method public androidx.car.app.model.Pane.Builder clearRows();
     method public androidx.car.app.model.Pane.Builder setActions(java.util.List<androidx.car.app.model.Action!>);
     method public androidx.car.app.model.Pane.Builder setLoading(boolean);
   }
@@ -590,10 +571,9 @@
 
   public class Row implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.Row.Builder builder();
-    method public int getFlags();
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.Metadata getMetadata();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public int getRowImageType();
     method public java.util.List<androidx.car.app.model.CarText!> getTexts();
     method public androidx.car.app.model.CarText getTitle();
@@ -604,17 +584,12 @@
     field public static final int IMAGE_TYPE_ICON = 4; // 0x4
     field public static final int IMAGE_TYPE_LARGE = 2; // 0x2
     field public static final int IMAGE_TYPE_SMALL = 1; // 0x1
-    field public static final int ROW_FLAG_NONE = 1; // 0x1
-    field public static final int ROW_FLAG_SECTION_HEADER = 4; // 0x4
-    field public static final int ROW_FLAG_SHOW_DIVIDERS = 2; // 0x2
   }
 
   public static final class Row.Builder {
     method public androidx.car.app.model.Row.Builder addText(CharSequence);
     method public androidx.car.app.model.Row build();
-    method public androidx.car.app.model.Row.Builder clearText();
     method public androidx.car.app.model.Row.Builder setBrowsable(boolean);
-    method public androidx.car.app.model.Row.Builder setFlags(int);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon?);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon?, int);
     method public androidx.car.app.model.Row.Builder setMetadata(androidx.car.app.model.Metadata);
@@ -630,7 +605,7 @@
     method public String? getInitialSearchText();
     method public androidx.car.app.model.ItemList? getItemList();
     method public String? getSearchHint();
-    method public androidx.car.app.host.SearchListenerWrapper getSearchListener();
+    method public androidx.car.app.SearchListenerWrapper getSearchListener();
     method public boolean isLoading();
     method public boolean isShowKeyboardByDefault();
   }
@@ -654,7 +629,6 @@
 
   public interface Template {
     method public default void checkPermissions(android.content.Context);
-    method public default boolean isRefresh(androidx.car.app.model.Template, androidx.car.app.utils.Logger);
   }
 
   public final class TemplateInfo {
@@ -680,7 +654,7 @@
 
   public class Toggle {
     method public static androidx.car.app.model.Toggle.Builder builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
-    method public androidx.car.app.host.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
+    method public androidx.car.app.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
     method public boolean isChecked();
   }
 
@@ -735,7 +709,6 @@
   public class RowConstraints {
     method public static androidx.car.app.model.constraints.RowConstraints.Builder builder();
     method public androidx.car.app.model.constraints.CarIconConstraints getCarIconConstraints();
-    method public int getFlagOverrides();
     method public int getMaxActionsExclusive();
     method public int getMaxTextLinesPerRow();
     method public boolean isImageAllowed();
@@ -753,7 +726,6 @@
   public static final class RowConstraints.Builder {
     method public androidx.car.app.model.constraints.RowConstraints build();
     method public androidx.car.app.model.constraints.RowConstraints.Builder setCarIconConstraints(androidx.car.app.model.constraints.CarIconConstraints);
-    method public androidx.car.app.model.constraints.RowConstraints.Builder setFlagOverrides(int);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setImageAllowed(boolean);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setMaxActionsExclusive(int);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setMaxTextLinesPerRow(int);
@@ -794,15 +766,17 @@
 package androidx.car.app.navigation {
 
   public class NavigationManager {
+    method @MainThread public void clearNavigationManagerListener();
     method @MainThread public void navigationEnded();
     method @MainThread public void navigationStarted();
-    method @MainThread public void setListener(androidx.car.app.navigation.NavigationManagerListener?);
+    method @MainThread public void setNavigationManagerListener(androidx.car.app.navigation.NavigationManagerListener);
+    method @MainThread public void setNavigationManagerListener(java.util.concurrent.Executor, androidx.car.app.navigation.NavigationManagerListener);
     method @MainThread public void updateTrip(androidx.car.app.navigation.model.Trip);
   }
 
   public interface NavigationManagerListener {
     method public void onAutoDriveEnabled();
-    method public void stopNavigation();
+    method public void onStopNavigation();
   }
 
 }
@@ -864,7 +838,11 @@
     field public static final int TYPE_DESTINATION_RIGHT = 42; // 0x2a
     field public static final int TYPE_DESTINATION_STRAIGHT = 40; // 0x28
     field public static final int TYPE_FERRY_BOAT = 37; // 0x25
+    field public static final int TYPE_FERRY_BOAT_LEFT = 47; // 0x2f
+    field public static final int TYPE_FERRY_BOAT_RIGHT = 48; // 0x30
     field public static final int TYPE_FERRY_TRAIN = 38; // 0x26
+    field public static final int TYPE_FERRY_TRAIN_LEFT = 49; // 0x31
+    field public static final int TYPE_FERRY_TRAIN_RIGHT = 50; // 0x32
     field public static final int TYPE_FORK_LEFT = 25; // 0x19
     field public static final int TYPE_FORK_RIGHT = 26; // 0x1a
     field public static final int TYPE_KEEP_LEFT = 3; // 0x3
@@ -885,12 +863,16 @@
     field public static final int TYPE_ON_RAMP_SLIGHT_RIGHT = 14; // 0xe
     field public static final int TYPE_ON_RAMP_U_TURN_LEFT = 19; // 0x13
     field public static final int TYPE_ON_RAMP_U_TURN_RIGHT = 20; // 0x14
-    field public static final int TYPE_ROUNDABOUT_ENTER = 30; // 0x1e
+    field @Deprecated public static final int TYPE_ROUNDABOUT_ENTER = 30; // 0x1e
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW = 34; // 0x22
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE = 35; // 0x23
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW = 32; // 0x20
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE = 33; // 0x21
-    field public static final int TYPE_ROUNDABOUT_EXIT = 31; // 0x1f
+    field public static final int TYPE_ROUNDABOUT_ENTER_CCW = 45; // 0x2d
+    field public static final int TYPE_ROUNDABOUT_ENTER_CW = 43; // 0x2b
+    field @Deprecated public static final int TYPE_ROUNDABOUT_EXIT = 31; // 0x1f
+    field public static final int TYPE_ROUNDABOUT_EXIT_CCW = 46; // 0x2e
+    field public static final int TYPE_ROUNDABOUT_EXIT_CW = 44; // 0x2c
     field public static final int TYPE_STRAIGHT = 36; // 0x24
     field public static final int TYPE_TURN_NORMAL_LEFT = 7; // 0x7
     field public static final int TYPE_TURN_NORMAL_RIGHT = 8; // 0x8
@@ -957,9 +939,9 @@
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate build();
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
-    method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList?);
     method @VisibleForTesting public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemListForTesting(androidx.car.app.model.ItemList?);
+    method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setTitle(CharSequence?);
   }
 
@@ -978,9 +960,9 @@
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate build();
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
-    method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList?);
     method @VisibleForTesting public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemListForTesting(androidx.car.app.model.ItemList?);
+    method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setNavigateAction(androidx.car.app.model.Action);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence?);
   }
@@ -997,8 +979,8 @@
   public static final class RoutingInfo.Builder {
     method public androidx.car.app.navigation.model.RoutingInfo build();
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setCurrentStep(androidx.car.app.navigation.model.Step, androidx.car.app.model.Distance);
-    method public androidx.car.app.navigation.model.RoutingInfo.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setJunctionImage(androidx.car.app.model.CarIcon?);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setNextStep(androidx.car.app.navigation.model.Step?);
   }
 
@@ -1023,8 +1005,8 @@
   }
 
   public final class TravelEstimate {
-    method public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, long, androidx.car.app.model.DateTimeWithZone);
-    method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, java.time.Duration, java.time.ZonedDateTime);
+    method public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, androidx.car.app.model.DateTimeWithZone);
+    method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, java.time.ZonedDateTime);
     method public static androidx.car.app.navigation.model.TravelEstimate create(androidx.car.app.model.Distance, long, androidx.car.app.model.DateTimeWithZone);
     method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate create(androidx.car.app.model.Distance, java.time.Duration, java.time.ZonedDateTime);
     method public androidx.car.app.model.DateTimeWithZone? getArrivalTimeAtDestination();
@@ -1032,12 +1014,15 @@
     method public androidx.car.app.model.CarColor getRemainingDistanceColor();
     method public androidx.car.app.model.CarColor getRemainingTimeColor();
     method public long getRemainingTimeSeconds();
+    field public static final long REMAINING_TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
   }
 
   public static final class TravelEstimate.Builder {
     method public androidx.car.app.navigation.model.TravelEstimate build();
     method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingDistanceColor(androidx.car.app.model.CarColor);
+    method @RequiresApi(26) public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTime(java.time.Duration);
     method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeSeconds(long);
   }
 
   public final class Trip {
@@ -1062,7 +1047,7 @@
     method public androidx.car.app.navigation.model.Trip.Builder clearStepTravelEstimates();
     method public androidx.car.app.navigation.model.Trip.Builder clearSteps();
     method public androidx.car.app.navigation.model.Trip.Builder setCurrentRoad(CharSequence?);
-    method public androidx.car.app.navigation.model.Trip.Builder setIsLoading(boolean);
+    method public androidx.car.app.navigation.model.Trip.Builder setLoading(boolean);
   }
 
 }
@@ -1079,8 +1064,8 @@
     method public CharSequence? getContentTitle();
     method public android.app.PendingIntent? getDeleteIntent();
     method public int getImportance();
-    method public android.graphics.Bitmap? getLargeIconBitmap();
-    method public int getSmallIconResId();
+    method public android.graphics.Bitmap? getLargeIcon();
+    method @DrawableRes public int getSmallIcon();
     method public boolean isExtended();
     method public static boolean isExtended(android.app.Notification);
   }
@@ -1089,7 +1074,6 @@
     ctor public CarAppExtender.Builder();
     method public androidx.car.app.notification.CarAppExtender.Builder addAction(@DrawableRes int, CharSequence, android.app.PendingIntent);
     method public androidx.car.app.notification.CarAppExtender build();
-    method public androidx.car.app.notification.CarAppExtender.Builder clearActions();
     method public androidx.car.app.notification.CarAppExtender.Builder setContentIntent(android.app.PendingIntent?);
     method public androidx.car.app.notification.CarAppExtender.Builder setContentText(CharSequence?);
     method public androidx.car.app.notification.CarAppExtender.Builder setContentTitle(CharSequence?);
@@ -1120,10 +1104,6 @@
 
 package androidx.car.app.utils {
 
-  public interface Logger {
-    method public void log(String);
-  }
-
   public class ThreadUtils {
     method public static void checkMainThread();
     method public static void runOnMain(Runnable);
@@ -1131,3 +1111,13 @@
 
 }
 
+package androidx.car.app.versioning {
+
+  public class CarAppApiLevels {
+    field public static final int LATEST = 1; // 0x1
+    field public static final int LEVEL_1 = 1; // 0x1
+    field public static final int OLDEST = 1; // 0x1
+  }
+
+}
+
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 5b604f6..4e2edca 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -1,6 +1,12 @@
 // Signature format: 4.0
 package androidx.car.app {
 
+  public final class AppInfo {
+    method public int getLatestCarAppApiLevel();
+    method public String getLibraryVersion();
+    method public int getMinCarAppApiLevel();
+  }
+
   public class AppManager {
     method public void invalidate();
     method public void setSurfaceListener(androidx.car.app.SurfaceListener?);
@@ -16,35 +22,24 @@
 
   public abstract class CarAppService extends android.app.Service implements androidx.lifecycle.LifecycleOwner {
     ctor public CarAppService();
-    method @CallSuper public void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
-    method public void finish();
+    method @CallSuper public final void dump(java.io.FileDescriptor, java.io.PrintWriter, String![]?);
+    method public final void finish();
     method public final androidx.car.app.CarContext getCarContext();
-    method public androidx.car.app.HostInfo? getHostInfo();
-    method public androidx.lifecycle.Lifecycle getLifecycle();
-    method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
+    method public final androidx.car.app.HostInfo? getHostInfo();
+    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method @CallSuper public final android.os.IBinder? onBind(android.content.Intent);
     method public void onCarAppFinished();
     method public void onCarConfigurationChanged(android.content.res.Configuration);
     method public abstract androidx.car.app.Screen onCreateScreen(android.content.Intent);
     method public final void onDestroy();
     method public void onNewIntent(android.content.Intent);
     method public final boolean onUnbind(android.content.Intent);
-  }
-
-  public class CarAppVersion {
-    method public boolean isGreaterOrEqualTo(androidx.car.app.CarAppVersion);
-    method public static androidx.car.app.CarAppVersion? of(String) throws androidx.car.app.MalformedVersionException;
-    field public static final androidx.car.app.CarAppVersion HANDSHAKE_MIN_VERSION;
-    field public static final androidx.car.app.CarAppVersion INSTANCE;
-  }
-
-  public enum CarAppVersion.ReleaseSuffix {
-    method public static androidx.car.app.CarAppVersion.ReleaseSuffix fromString(String);
-    enum_constant public static final androidx.car.app.CarAppVersion.ReleaseSuffix RELEASE_SUFFIX_BETA;
-    enum_constant public static final androidx.car.app.CarAppVersion.ReleaseSuffix RELEASE_SUFFIX_EAP;
+    field public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService";
   }
 
   public class CarContext extends android.content.ContextWrapper {
     method public void finishCarApp();
+    method public int getCarAppApiLevel();
     method public Object getCarService(String);
     method public <T> T getCarService(Class<T!>);
     method public String getCarServiceName(Class<?>);
@@ -53,11 +48,11 @@
     method public void startCarApp(android.content.Intent);
     method public static void startCarApp(android.content.Intent, android.content.Intent);
     field public static final String ACTION_NAVIGATE = "androidx.car.app.action.NAVIGATE";
-    field public static final String APP_SERVICE = "app_manager";
+    field public static final String APP_SERVICE = "app";
     field public static final String CAR_SERVICE = "car";
-    field public static final String NAVIGATION_SERVICE = "navigation_manager";
-    field public static final String SCREEN_MANAGER_SERVICE = "screen_manager";
-    field public static final String START_CAR_APP_BINDER_KEY = "StartCarAppBinderKey";
+    field public static final String EXTRA_START_CAR_APP_BINDER_KEY = "androidx.car.app.extra.START_CAR_APP_BINDER_KEY";
+    field public static final String NAVIGATION_SERVICE = "navigation";
+    field public static final String SCREEN_SERVICE = "screen";
   }
 
   public final class CarToast {
@@ -89,21 +84,31 @@
   }
 
   public class HostInfo {
-    ctor public HostInfo(String, int);
     method public String getPackageName();
     method public int getUid();
   }
 
-  public class MalformedVersionException extends java.lang.Exception {
-    ctor public MalformedVersionException(String?);
-    ctor public MalformedVersionException(String, Throwable);
-    ctor public MalformedVersionException(Throwable?);
+  public interface OnCheckedChangeListenerWrapper {
+    method public void onCheckedChange(boolean, androidx.car.app.OnDoneCallback);
+  }
+
+  public interface OnDoneCallback {
+    method public void onFailure(androidx.car.app.serialization.Bundleable);
+    method public void onSuccess(androidx.car.app.serialization.Bundleable?);
+  }
+
+  public interface OnItemVisibilityChangedListenerWrapper {
+    method public void onItemVisibilityChanged(int, int, androidx.car.app.OnDoneCallback);
   }
 
   public interface OnScreenResultCallback {
     method public void onScreenResult(Object?);
   }
 
+  public interface OnSelectedListenerWrapper {
+    method public void onSelected(int, androidx.car.app.OnDoneCallback);
+  }
+
   public abstract class Screen implements androidx.lifecycle.LifecycleOwner {
     ctor protected Screen(androidx.car.app.CarContext);
     method public final void finish();
@@ -111,8 +116,8 @@
     method public final androidx.lifecycle.Lifecycle getLifecycle();
     method public String? getMarker();
     method public final androidx.car.app.ScreenManager getScreenManager();
-    method public abstract androidx.car.app.model.Template getTemplate();
     method public final void invalidate();
+    method public abstract androidx.car.app.model.Template onGetTemplate();
     method public void setMarker(String?);
     method public void setResult(Object?);
     field public static final String ROOT = "ROOT";
@@ -132,6 +137,11 @@
     method public void onSearchTextChanged(String);
   }
 
+  public interface SearchListenerWrapper {
+    method public void onSearchSubmitted(String, androidx.car.app.OnDoneCallback);
+    method public void onSearchTextChanged(String, androidx.car.app.OnDoneCallback);
+  }
+
   public class SurfaceContainer {
     ctor public SurfaceContainer(android.view.Surface?, int, int, int);
     method public int getDpi();
@@ -153,37 +163,10 @@
 
 }
 
-package androidx.car.app.host {
+package androidx.car.app.annotations {
 
-  public interface OnCheckedChangeListenerWrapper {
-    method public void onCheckedChange(boolean, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface OnDoneCallback {
-    method public void onFailure(androidx.car.app.serialization.Bundleable);
-    method public void onSuccess(androidx.car.app.serialization.Bundleable?);
-  }
-
-  public interface OnItemVisibilityChangedListenerWrapper {
-    method public void onItemVisibilityChanged(int, int, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface OnSelectedListenerWrapper {
-    method public void onSelected(int, androidx.car.app.host.OnDoneCallback);
-  }
-
-  public interface SearchListenerWrapper {
-    method public void onSearchSubmitted(String, androidx.car.app.host.OnDoneCallback);
-    method public void onSearchTextChanged(String, androidx.car.app.host.OnDoneCallback);
-  }
-
-}
-
-package androidx.car.app.host.model {
-
-  public interface OnClickListenerWrapper {
-    method public boolean isParkedOnly();
-    method public void onClick(androidx.car.app.host.OnDoneCallback);
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface RequiresCarApi {
+    method public abstract int value();
   }
 
 }
@@ -194,7 +177,7 @@
     method public static androidx.car.app.model.Action.Builder builder();
     method public androidx.car.app.model.CarColor getBackgroundColor();
     method public androidx.car.app.model.CarIcon? getIcon();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public androidx.car.app.model.CarText? getTitle();
     method public int getType();
     method public boolean isStandard();
@@ -231,7 +214,6 @@
     ctor public ActionStrip.Builder();
     method public androidx.car.app.model.ActionStrip.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.ActionStrip build();
-    method public androidx.car.app.model.ActionStrip.Builder clearActions();
   }
 
   public class CarColor {
@@ -273,8 +255,6 @@
     field public static final int TYPE_CUSTOM = 1; // 0x1
     field public static final int TYPE_ERROR = 6; // 0x6
     field public static final int TYPE_UNKNOWN = 0; // 0x0
-    field public static final int TYPE_WILLIAM_ALERT = 7; // 0x7
-    field public static final androidx.car.app.model.CarIcon WILLIAM_ALERT;
   }
 
   public static final class CarIcon.Builder {
@@ -358,7 +338,7 @@
     method public static androidx.car.app.model.GridItem.Builder builder();
     method public androidx.car.app.model.CarIcon getImage();
     method public int getImageType();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public androidx.car.app.model.CarText? getText();
     method public androidx.car.app.model.CarText? getTitle();
     method public androidx.car.app.model.Toggle? getToggle();
@@ -404,10 +384,9 @@
     method public static androidx.car.app.model.ItemList.Builder builder();
     method public java.util.List<java.lang.Object!> getItems();
     method public androidx.car.app.model.CarText? getNoItemsMessage();
-    method public androidx.car.app.host.OnItemVisibilityChangedListenerWrapper? getOnItemsVisibilityChangeListener();
-    method public androidx.car.app.host.OnSelectedListenerWrapper? getOnSelectedListener();
+    method public androidx.car.app.OnItemVisibilityChangedListenerWrapper? getOnItemsVisibilityChangedListener();
+    method public androidx.car.app.OnSelectedListenerWrapper? getOnSelectedListener();
     method public int getSelectedIndex();
-    method public boolean isRefresh(androidx.car.app.model.ItemList?, androidx.car.app.utils.Logger);
   }
 
   public static final class ItemList.Builder {
@@ -416,8 +395,8 @@
     method public androidx.car.app.model.ItemList build();
     method public androidx.car.app.model.ItemList.Builder clearItems();
     method public androidx.car.app.model.ItemList.Builder setNoItemsMessage(CharSequence?);
-    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangeListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener?);
-    method public androidx.car.app.model.ItemList.Builder setSelectable(androidx.car.app.model.ItemList.OnSelectedListener?);
+    method public androidx.car.app.model.ItemList.Builder setOnItemsVisibilityChangedListener(androidx.car.app.model.ItemList.OnItemVisibilityChangedListener?);
+    method public androidx.car.app.model.ItemList.Builder setOnSelectedListener(androidx.car.app.model.ItemList.OnSelectedListener?);
     method public androidx.car.app.model.ItemList.Builder setSelectedIndex(int);
   }
 
@@ -449,7 +428,6 @@
   public static final class ListTemplate.Builder {
     method public androidx.car.app.model.ListTemplate.Builder addList(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.ListTemplate build();
-    method public androidx.car.app.model.ListTemplate.Builder clearAllLists();
     method public androidx.car.app.model.ListTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.model.ListTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
     method public androidx.car.app.model.ListTemplate.Builder setLoading(boolean);
@@ -459,7 +437,7 @@
 
   public final class MessageTemplate implements androidx.car.app.model.Template {
     method public static androidx.car.app.model.MessageTemplate.Builder builder(CharSequence);
-    method public androidx.car.app.model.ActionList? getActionList();
+    method public androidx.car.app.model.ActionList? getActions();
     method public androidx.car.app.model.CarText? getDebugMessage();
     method public androidx.car.app.model.Action? getHeaderAction();
     method public androidx.car.app.model.CarIcon? getIcon();
@@ -495,19 +473,22 @@
     method public void onClick();
   }
 
+  public interface OnClickListenerWrapper {
+    method public boolean isParkedOnly();
+    method public void onClick(androidx.car.app.OnDoneCallback);
+  }
+
   public final class Pane {
     method public static androidx.car.app.model.Pane.Builder builder();
-    method public androidx.car.app.model.ActionList? getActionList();
+    method public androidx.car.app.model.ActionList? getActions();
     method public java.util.List<java.lang.Object!> getRows();
     method public boolean isLoading();
-    method public boolean isRefresh(androidx.car.app.model.Pane?, androidx.car.app.utils.Logger);
   }
 
   public static final class Pane.Builder {
     ctor public Pane.Builder();
     method public androidx.car.app.model.Pane.Builder addRow(androidx.car.app.model.Row);
     method public androidx.car.app.model.Pane build();
-    method public androidx.car.app.model.Pane.Builder clearRows();
     method public androidx.car.app.model.Pane.Builder setActions(java.util.List<androidx.car.app.model.Action!>);
     method public androidx.car.app.model.Pane.Builder setLoading(boolean);
   }
@@ -590,10 +571,9 @@
 
   public class Row implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.Row.Builder builder();
-    method public int getFlags();
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.Metadata getMetadata();
-    method public androidx.car.app.host.model.OnClickListenerWrapper? getOnClickListener();
+    method public androidx.car.app.model.OnClickListenerWrapper? getOnClickListener();
     method public int getRowImageType();
     method public java.util.List<androidx.car.app.model.CarText!> getTexts();
     method public androidx.car.app.model.CarText getTitle();
@@ -604,17 +584,12 @@
     field public static final int IMAGE_TYPE_ICON = 4; // 0x4
     field public static final int IMAGE_TYPE_LARGE = 2; // 0x2
     field public static final int IMAGE_TYPE_SMALL = 1; // 0x1
-    field public static final int ROW_FLAG_NONE = 1; // 0x1
-    field public static final int ROW_FLAG_SECTION_HEADER = 4; // 0x4
-    field public static final int ROW_FLAG_SHOW_DIVIDERS = 2; // 0x2
   }
 
   public static final class Row.Builder {
     method public androidx.car.app.model.Row.Builder addText(CharSequence);
     method public androidx.car.app.model.Row build();
-    method public androidx.car.app.model.Row.Builder clearText();
     method public androidx.car.app.model.Row.Builder setBrowsable(boolean);
-    method public androidx.car.app.model.Row.Builder setFlags(int);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon?);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon?, int);
     method public androidx.car.app.model.Row.Builder setMetadata(androidx.car.app.model.Metadata);
@@ -630,7 +605,7 @@
     method public String? getInitialSearchText();
     method public androidx.car.app.model.ItemList? getItemList();
     method public String? getSearchHint();
-    method public androidx.car.app.host.SearchListenerWrapper getSearchListener();
+    method public androidx.car.app.SearchListenerWrapper getSearchListener();
     method public boolean isLoading();
     method public boolean isShowKeyboardByDefault();
   }
@@ -654,7 +629,6 @@
 
   public interface Template {
     method public default void checkPermissions(android.content.Context);
-    method public default boolean isRefresh(androidx.car.app.model.Template, androidx.car.app.utils.Logger);
   }
 
   public final class TemplateInfo {
@@ -680,7 +654,7 @@
 
   public class Toggle {
     method public static androidx.car.app.model.Toggle.Builder builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
-    method public androidx.car.app.host.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
+    method public androidx.car.app.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
     method public boolean isChecked();
   }
 
@@ -735,7 +709,6 @@
   public class RowConstraints {
     method public static androidx.car.app.model.constraints.RowConstraints.Builder builder();
     method public androidx.car.app.model.constraints.CarIconConstraints getCarIconConstraints();
-    method public int getFlagOverrides();
     method public int getMaxActionsExclusive();
     method public int getMaxTextLinesPerRow();
     method public boolean isImageAllowed();
@@ -753,7 +726,6 @@
   public static final class RowConstraints.Builder {
     method public androidx.car.app.model.constraints.RowConstraints build();
     method public androidx.car.app.model.constraints.RowConstraints.Builder setCarIconConstraints(androidx.car.app.model.constraints.CarIconConstraints);
-    method public androidx.car.app.model.constraints.RowConstraints.Builder setFlagOverrides(int);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setImageAllowed(boolean);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setMaxActionsExclusive(int);
     method public androidx.car.app.model.constraints.RowConstraints.Builder setMaxTextLinesPerRow(int);
@@ -794,15 +766,17 @@
 package androidx.car.app.navigation {
 
   public class NavigationManager {
+    method @MainThread public void clearNavigationManagerListener();
     method @MainThread public void navigationEnded();
     method @MainThread public void navigationStarted();
-    method @MainThread public void setListener(androidx.car.app.navigation.NavigationManagerListener?);
+    method @MainThread public void setNavigationManagerListener(androidx.car.app.navigation.NavigationManagerListener);
+    method @MainThread public void setNavigationManagerListener(java.util.concurrent.Executor, androidx.car.app.navigation.NavigationManagerListener);
     method @MainThread public void updateTrip(androidx.car.app.navigation.model.Trip);
   }
 
   public interface NavigationManagerListener {
     method public void onAutoDriveEnabled();
-    method public void stopNavigation();
+    method public void onStopNavigation();
   }
 
 }
@@ -864,7 +838,11 @@
     field public static final int TYPE_DESTINATION_RIGHT = 42; // 0x2a
     field public static final int TYPE_DESTINATION_STRAIGHT = 40; // 0x28
     field public static final int TYPE_FERRY_BOAT = 37; // 0x25
+    field public static final int TYPE_FERRY_BOAT_LEFT = 47; // 0x2f
+    field public static final int TYPE_FERRY_BOAT_RIGHT = 48; // 0x30
     field public static final int TYPE_FERRY_TRAIN = 38; // 0x26
+    field public static final int TYPE_FERRY_TRAIN_LEFT = 49; // 0x31
+    field public static final int TYPE_FERRY_TRAIN_RIGHT = 50; // 0x32
     field public static final int TYPE_FORK_LEFT = 25; // 0x19
     field public static final int TYPE_FORK_RIGHT = 26; // 0x1a
     field public static final int TYPE_KEEP_LEFT = 3; // 0x3
@@ -885,12 +863,16 @@
     field public static final int TYPE_ON_RAMP_SLIGHT_RIGHT = 14; // 0xe
     field public static final int TYPE_ON_RAMP_U_TURN_LEFT = 19; // 0x13
     field public static final int TYPE_ON_RAMP_U_TURN_RIGHT = 20; // 0x14
-    field public static final int TYPE_ROUNDABOUT_ENTER = 30; // 0x1e
+    field @Deprecated public static final int TYPE_ROUNDABOUT_ENTER = 30; // 0x1e
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW = 34; // 0x22
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE = 35; // 0x23
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW = 32; // 0x20
     field public static final int TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE = 33; // 0x21
-    field public static final int TYPE_ROUNDABOUT_EXIT = 31; // 0x1f
+    field public static final int TYPE_ROUNDABOUT_ENTER_CCW = 45; // 0x2d
+    field public static final int TYPE_ROUNDABOUT_ENTER_CW = 43; // 0x2b
+    field @Deprecated public static final int TYPE_ROUNDABOUT_EXIT = 31; // 0x1f
+    field public static final int TYPE_ROUNDABOUT_EXIT_CCW = 46; // 0x2e
+    field public static final int TYPE_ROUNDABOUT_EXIT_CW = 44; // 0x2c
     field public static final int TYPE_STRAIGHT = 36; // 0x24
     field public static final int TYPE_TURN_NORMAL_LEFT = 7; // 0x7
     field public static final int TYPE_TURN_NORMAL_RIGHT = 8; // 0x8
@@ -957,9 +939,9 @@
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate build();
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
-    method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList?);
     method @VisibleForTesting public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setItemListForTesting(androidx.car.app.model.ItemList?);
+    method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.PlaceListNavigationTemplate.Builder setTitle(CharSequence?);
   }
 
@@ -978,9 +960,9 @@
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate build();
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip?);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setHeaderAction(androidx.car.app.model.Action?);
-    method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemList(androidx.car.app.model.ItemList?);
     method @VisibleForTesting public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setItemListForTesting(androidx.car.app.model.ItemList?);
+    method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setNavigateAction(androidx.car.app.model.Action);
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence?);
   }
@@ -997,8 +979,8 @@
   public static final class RoutingInfo.Builder {
     method public androidx.car.app.navigation.model.RoutingInfo build();
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setCurrentStep(androidx.car.app.navigation.model.Step, androidx.car.app.model.Distance);
-    method public androidx.car.app.navigation.model.RoutingInfo.Builder setIsLoading(boolean);
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setJunctionImage(androidx.car.app.model.CarIcon?);
+    method public androidx.car.app.navigation.model.RoutingInfo.Builder setLoading(boolean);
     method public androidx.car.app.navigation.model.RoutingInfo.Builder setNextStep(androidx.car.app.navigation.model.Step?);
   }
 
@@ -1023,8 +1005,8 @@
   }
 
   public final class TravelEstimate {
-    method public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, long, androidx.car.app.model.DateTimeWithZone);
-    method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, java.time.Duration, java.time.ZonedDateTime);
+    method public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, androidx.car.app.model.DateTimeWithZone);
+    method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate.Builder builder(androidx.car.app.model.Distance, java.time.ZonedDateTime);
     method public static androidx.car.app.navigation.model.TravelEstimate create(androidx.car.app.model.Distance, long, androidx.car.app.model.DateTimeWithZone);
     method @RequiresApi(26) public static androidx.car.app.navigation.model.TravelEstimate create(androidx.car.app.model.Distance, java.time.Duration, java.time.ZonedDateTime);
     method public androidx.car.app.model.DateTimeWithZone? getArrivalTimeAtDestination();
@@ -1032,12 +1014,15 @@
     method public androidx.car.app.model.CarColor getRemainingDistanceColor();
     method public androidx.car.app.model.CarColor getRemainingTimeColor();
     method public long getRemainingTimeSeconds();
+    field public static final long REMAINING_TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
   }
 
   public static final class TravelEstimate.Builder {
     method public androidx.car.app.navigation.model.TravelEstimate build();
     method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingDistanceColor(androidx.car.app.model.CarColor);
+    method @RequiresApi(26) public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTime(java.time.Duration);
     method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeColor(androidx.car.app.model.CarColor);
+    method public androidx.car.app.navigation.model.TravelEstimate.Builder setRemainingTimeSeconds(long);
   }
 
   public final class Trip {
@@ -1062,7 +1047,7 @@
     method public androidx.car.app.navigation.model.Trip.Builder clearStepTravelEstimates();
     method public androidx.car.app.navigation.model.Trip.Builder clearSteps();
     method public androidx.car.app.navigation.model.Trip.Builder setCurrentRoad(CharSequence?);
-    method public androidx.car.app.navigation.model.Trip.Builder setIsLoading(boolean);
+    method public androidx.car.app.navigation.model.Trip.Builder setLoading(boolean);
   }
 
 }
@@ -1079,8 +1064,8 @@
     method public CharSequence? getContentTitle();
     method public android.app.PendingIntent? getDeleteIntent();
     method public int getImportance();
-    method public android.graphics.Bitmap? getLargeIconBitmap();
-    method public int getSmallIconResId();
+    method public android.graphics.Bitmap? getLargeIcon();
+    method @DrawableRes public int getSmallIcon();
     method public boolean isExtended();
     method public static boolean isExtended(android.app.Notification);
   }
@@ -1089,7 +1074,6 @@
     ctor public CarAppExtender.Builder();
     method public androidx.car.app.notification.CarAppExtender.Builder addAction(@DrawableRes int, CharSequence, android.app.PendingIntent);
     method public androidx.car.app.notification.CarAppExtender build();
-    method public androidx.car.app.notification.CarAppExtender.Builder clearActions();
     method public androidx.car.app.notification.CarAppExtender.Builder setContentIntent(android.app.PendingIntent?);
     method public androidx.car.app.notification.CarAppExtender.Builder setContentText(CharSequence?);
     method public androidx.car.app.notification.CarAppExtender.Builder setContentTitle(CharSequence?);
@@ -1120,10 +1104,6 @@
 
 package androidx.car.app.utils {
 
-  public interface Logger {
-    method public void log(String);
-  }
-
   public class ThreadUtils {
     method public static void checkMainThread();
     method public static void runOnMain(Runnable);
@@ -1131,3 +1111,13 @@
 
 }
 
+package androidx.car.app.versioning {
+
+  public class CarAppApiLevels {
+    field public static final int LATEST = 1; // 0x1
+    field public static final int LEVEL_1 = 1; // 0x1
+    field public static final int OLDEST = 1; // 0x1
+  }
+
+}
+
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index 5e853f9..d0811a6 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -31,21 +31,22 @@
     implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
     implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
 
-    androidTestImplementation(ANDROIDX_TEST_CORE)
-    androidTestImplementation(ANDROIDX_TEST_RULES)
-    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
-    androidTestImplementation(ANDROIDX_TEST_RUNNER)
     // TODO(shiufai): We need this for assertThrows. Point back to the AndroidX shared version if
     // it is ever upgraded.
-    androidTestImplementation("junit:junit:4.13")
-    androidTestImplementation(TRUTH)
-    androidTestImplementation(MOCKITO_ANDROID)
+    testImplementation("junit:junit:4.13")
+    testImplementation(ANDROIDX_TEST_CORE)
+    testImplementation(ANDROIDX_TEST_RUNNER)
+    testImplementation(JUNIT)
+    testImplementation(MOCKITO_CORE)
+    testImplementation(ROBOLECTRIC)
+    testImplementation(TRUTH)
 }
 
 android {
     defaultConfig {
         minSdkVersion 21
         multiDexEnabled = true
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
     lintOptions {
         // We have a bunch of builder/inner classes where the outer classes access the private
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/CarAppVersionTest.java b/car/app/app/src/androidTest/java/androidx/car/app/CarAppVersionTest.java
deleted file mode 100644
index b4d28ee..0000000
--- a/car/app/app/src/androidTest/java/androidx/car/app/CarAppVersionTest.java
+++ /dev/null
@@ -1,131 +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.car.app;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import androidx.car.app.CarAppVersion.ReleaseSuffix;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Tests for {@link CarAppVersion}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class CarAppVersionTest {
-
-    @Test
-    public void majorVersion() {
-        CarAppVersion hostVersion = CarAppVersion.create(2, 0, 0);
-        CarAppVersion clientVersion = CarAppVersion.create(1, 0, 0);
-
-        assertThat(hostVersion.isGreaterOrEqualTo(clientVersion)).isTrue();
-        assertThat(clientVersion.isGreaterOrEqualTo(hostVersion)).isFalse();
-    }
-
-    @Test
-    public void minorVersion() {
-        CarAppVersion hostVersion = CarAppVersion.create(2, 1, 0);
-        CarAppVersion clientVersion = CarAppVersion.create(2, 0, 0);
-
-        assertThat(hostVersion.isGreaterOrEqualTo(clientVersion)).isTrue();
-        assertThat(clientVersion.isGreaterOrEqualTo(hostVersion)).isFalse();
-    }
-
-    @Test
-    public void patchVersion() {
-        CarAppVersion hostVersion = CarAppVersion.create(3, 2, 1);
-        CarAppVersion clientVersion = CarAppVersion.create(3, 2, 0);
-
-        assertThat(hostVersion.isGreaterOrEqualTo(clientVersion)).isTrue();
-        assertThat(clientVersion.isGreaterOrEqualTo(hostVersion)).isFalse();
-    }
-
-    @Test
-    public void eapVersion_requiresExactMatch() {
-        CarAppVersion hostVersion = CarAppVersion.create(3, 2, 1, ReleaseSuffix.RELEASE_SUFFIX_EAP,
-                1);
-
-        CarAppVersion mismatchedClientVersion = CarAppVersion.create(4, 3, 2);
-        assertThat(hostVersion.isGreaterOrEqualTo(mismatchedClientVersion)).isFalse();
-        assertThat(mismatchedClientVersion.isGreaterOrEqualTo(hostVersion)).isFalse();
-
-        CarAppVersion matchedClientVersion =
-                CarAppVersion.create(3, 2, 1, ReleaseSuffix.RELEASE_SUFFIX_EAP, 1);
-        assertThat(hostVersion.isGreaterOrEqualTo(matchedClientVersion)).isTrue();
-        assertThat(matchedClientVersion.isGreaterOrEqualTo(hostVersion)).isTrue();
-    }
-
-    @Test
-    public void stableVersion_compatibleWithAllBeta() {
-        CarAppVersion hostVersion = CarAppVersion.create(3, 2, 1);
-
-        CarAppVersion clientVersion1 =
-                CarAppVersion.create(3, 2, 1, ReleaseSuffix.RELEASE_SUFFIX_BETA, 1);
-        assertThat(hostVersion.isGreaterOrEqualTo(clientVersion1)).isTrue();
-        assertThat(clientVersion1.isGreaterOrEqualTo(hostVersion)).isFalse();
-
-        CarAppVersion clientVersion2 =
-                CarAppVersion.create(3, 2, 1, ReleaseSuffix.RELEASE_SUFFIX_BETA, 2);
-        assertThat(hostVersion.isGreaterOrEqualTo(clientVersion2)).isTrue();
-        assertThat(clientVersion2.isGreaterOrEqualTo(hostVersion)).isFalse();
-    }
-
-    @Test
-    public void versionString_malformed_multipleHyphens() {
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3-eap.4-5"));
-    }
-
-    @Test
-    public void versionString_malformed_mainVersionIncorrectNumbers() {
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1"));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1."));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2"));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2."));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3.4"));
-    }
-
-    @Test
-    public void versionString_malformed_invalidNumberFormat() {
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3c-eap.4"));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3-eap.4c"));
-    }
-
-    @Test
-    public void versionString_malformed_incorrectReleaseSuffix() {
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3-"));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3-eap"));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3-eap."));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3-eap.4."));
-        assertThrows(MalformedVersionException.class, () -> CarAppVersion.of("1.2.3-eaP.4"));
-    }
-
-    @Test
-    public void versionString() throws MalformedVersionException {
-        String version1 = "1.2.3";
-        String version2 = "1.2.3-eap.0";
-        String version3 = "1.2.3-beta.1";
-
-        assertThat(CarAppVersion.of(version1).toString()).isEqualTo(version1);
-        assertThat(CarAppVersion.of(version2).toString()).isEqualTo(version2);
-        assertThat(CarAppVersion.of(version3).toString()).isEqualTo(version3);
-    }
-}
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/GridTemplateTest.java b/car/app/app/src/androidTest/java/androidx/car/app/model/GridTemplateTest.java
deleted file mode 100644
index ef3832b..0000000
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/GridTemplateTest.java
+++ /dev/null
@@ -1,373 +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.car.app.model;
-
-import static androidx.car.app.model.CarIcon.ALERT;
-import static androidx.car.app.model.CarIcon.BACK;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import androidx.car.app.TestUtils;
-import androidx.car.app.utils.Logger;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Tests for {@link GridTemplate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class GridTemplateTest {
-    private final Logger mLogger = message -> {
-    };
-
-    @Test
-    public void createInstance_emptyList_notLoading_throws() {
-        assertThrows(
-                IllegalStateException.class,
-                () -> GridTemplate.builder().setTitle("Title").build());
-
-        // Positive case
-        GridTemplate.builder().setTitle("Title").setLoading(true).build();
-    }
-
-    @Test
-    public void createInstance_isLoading_hasList_throws() {
-        assertThrows(
-                IllegalStateException.class,
-                () ->
-                        GridTemplate.builder()
-                                .setTitle("Title")
-                                .setLoading(true)
-                                .setSingleList(TestUtils.getGridItemList(2))
-                                .build());
-    }
-
-    @Test
-    public void createInstance_noHeaderTitleOrAction_throws() {
-        assertThrows(
-                IllegalStateException.class,
-                () -> GridTemplate.builder().setSingleList(TestUtils.getGridItemList(2)).build());
-
-        // Positive cases.
-        GridTemplate.builder().setTitle("Title").setSingleList(
-                TestUtils.getGridItemList(2)).build();
-        GridTemplate.builder()
-                .setHeaderAction(Action.BACK)
-                .setSingleList(TestUtils.getGridItemList(2))
-                .build();
-    }
-
-    @Test
-    public void createInstance_setSingleList() {
-        ItemList list = TestUtils.getGridItemList(2);
-        GridTemplate template = GridTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-        assertThat(template.getSingleList()).isEqualTo(list);
-    }
-
-    @Test
-    public void createInstance_setHeaderAction_invalidActionThrows() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        GridTemplate.builder()
-                                .setHeaderAction(
-                                        Action.builder().setTitle("Action").setOnClickListener(
-                                                () -> {
-                                                }).build()));
-    }
-
-    @Test
-    public void createInstance_setHeaderAction() {
-        GridTemplate template =
-                GridTemplate.builder()
-                        .setSingleList(TestUtils.getGridItemList(2))
-                        .setHeaderAction(Action.BACK)
-                        .build();
-        assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
-    }
-
-    @Test
-    public void createInstance_setActionStrip() {
-        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
-        GridTemplate template =
-                GridTemplate.builder()
-                        .setSingleList(TestUtils.getGridItemList(2))
-                        .setTitle("Title")
-                        .setActionStrip(actionStrip)
-                        .build();
-        assertThat(template.getActionStrip()).isEqualTo(actionStrip);
-    }
-
-    @Test
-    public void createInstance_setBackground() {
-        GridTemplate template =
-                GridTemplate.builder()
-                        .setTitle("Title")
-                        .setLoading(true)
-                        .setBackgroundImage(BACK)
-                        .build();
-        assertThat(template.getBackgroundImage()).isEqualTo(BACK);
-    }
-
-    @Test
-    public void validate_fromLoadingState_isRefresh() {
-        GridItem.Builder gridItem = GridItem.builder().setImage(BACK);
-        ItemList list = ItemList.builder().addItem(gridItem.build()).build();
-        GridTemplate template = GridTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Going from loading state to new content is allowed.
-        assertThat(
-                template.isRefresh(
-                        GridTemplate.builder().setTitle("Title").setLoading(true).build(),
-                        mLogger))
-                .isTrue();
-    }
-
-    @Test
-    public void validate_mutableProperties_isRefresh() {
-        GridItem.Builder gridItem = GridItem.builder().setImage(BACK);
-        ItemList list = ItemList.builder().addItem(gridItem.build()).build();
-        GridTemplate template = GridTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Ensure a template is a refresh of itself.
-        assertThat(template.isRefresh(template, mLogger)).isTrue();
-
-        // Allowed mutable states.
-        assertThat(
-                template.isRefresh(
-                        GridTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder()
-                                                .addItem(gridItem.setOnClickListener(() -> {
-                                                }).setImage(BACK).build())
-                                                .build())
-                                .setHeaderAction(Action.BACK)
-                                .setActionStrip(
-                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
-                                .build(),
-                        mLogger))
-                .isTrue();
-    }
-
-    @Test
-    public void validate_titleUpdate_isNotRefresh() {
-        ItemList list = ItemList.builder().build();
-        GridTemplate template = GridTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Title updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        GridTemplate.builder().setSingleList(list).setTitle("Title2").build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_gridItemImageAndTextUpdates_isNotRefresh() {
-        GridItem.Builder gridItem =
-                GridItem.builder().setImage(BACK).setTitle("Title1").setText("Text1");
-        ItemList list = ItemList.builder().addItem(gridItem.build()).build();
-        GridTemplate template = GridTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Ensure a template is a refresh of itself.
-        assertThat(template.isRefresh(template, mLogger)).isTrue();
-
-        // Image updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        GridTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder().addItem(
-                                                gridItem.setImage(ALERT).build()).build())
-                                .build(),
-                        mLogger))
-                .isFalse();
-
-        // Text updates are disallowed
-        assertThat(
-                template.isRefresh(
-                        GridTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder().addItem(
-                                                gridItem.setTitle("Title2").build()).build())
-                                .build(),
-                        mLogger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        GridTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder().addItem(
-                                                gridItem.setText("Text2").build()).build())
-                                .build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_newGridItem_isNotRefresh() {
-        GridItem.Builder gridItem = GridItem.builder().setImage(BACK);
-        ItemList list = ItemList.builder().addItem(gridItem.build()).build();
-        GridTemplate template = GridTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Additional grid items are disallowed.
-        assertThat(
-                template.isRefresh(
-                        GridTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder()
-                                                .addItem(gridItem.build())
-                                                .addItem(gridItem.build())
-                                                .build())
-                                .build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_toLoadingState_isNotRefresh() {
-        // Going from content to loading state is disallowed.
-        assertThat(
-                GridTemplate.builder()
-                        .setTitle("Title")
-                        .setLoading(true)
-                        .build()
-                        .isRefresh(
-                                GridTemplate.builder()
-                                        .setTitle("Title")
-                                        .setSingleList(ItemList.builder().build())
-                                        .build(),
-                                mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void resetList_clearsSingleList() {
-        GridTemplate.Builder builder =
-                GridTemplate.builder()
-                        .setSingleList(TestUtils.getGridItemList(2))
-                        .setHeaderAction(Action.BACK);
-
-        assertThrows(IllegalStateException.class, () -> builder.clearAllLists().build());
-    }
-
-    @Test
-    public void equals() {
-        ItemList itemList = ItemList.builder().build();
-        String title = "title";
-        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
-
-        GridTemplate template =
-                GridTemplate.builder()
-                        .setSingleList(itemList)
-                        .setHeaderAction(Action.BACK)
-                        .setActionStrip(actionStrip)
-                        .setTitle(title)
-                        .build();
-
-        assertThat(template)
-                .isEqualTo(
-                        GridTemplate.builder()
-                                .setSingleList(itemList)
-                                .setHeaderAction(Action.BACK)
-                                .setActionStrip(actionStrip)
-                                .setTitle(title)
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentItemList() {
-        ItemList itemList = ItemList.builder().build();
-
-        GridTemplate template =
-                GridTemplate.builder().setTitle("Title").setSingleList(itemList).build();
-
-        assertThat(template)
-                .isNotEqualTo(
-                        GridTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder().addItem(
-                                                GridItem.builder().setImage(BACK).build()).build())
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentHeaderAction() {
-        ItemList itemList = ItemList.builder().build();
-
-        GridTemplate template =
-                GridTemplate.builder().setSingleList(itemList).setHeaderAction(Action.BACK).build();
-
-        assertThat(template)
-                .isNotEqualTo(
-                        GridTemplate.builder()
-                                .setSingleList(itemList)
-                                .setHeaderAction(Action.APP_ICON)
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentTitle() {
-        ItemList itemList = ItemList.builder().build();
-        String title = "title";
-
-        GridTemplate template = GridTemplate.builder().setSingleList(itemList).setTitle(
-                title).build();
-
-        assertThat(template)
-                .isNotEqualTo(GridTemplate.builder().setSingleList(itemList).setTitle(
-                        "foo").build());
-    }
-
-    @Test
-    public void notEquals_differentActionStrip() {
-        ItemList itemList = ItemList.builder().build();
-        String title = "title";
-
-        GridTemplate template =
-                GridTemplate.builder()
-                        .setSingleList(itemList)
-                        .setTitle(title)
-                        .setActionStrip(ActionStrip.builder().addAction(Action.BACK).build())
-                        .build();
-
-        assertThat(template)
-                .isNotEqualTo(
-                        GridTemplate.builder()
-                                .setSingleList(itemList)
-                                .setTitle(title)
-                                .setActionStrip(
-                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
-                                .build());
-    }
-}
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ItemListTest.java b/car/app/app/src/androidTest/java/androidx/car/app/model/ItemListTest.java
deleted file mode 100644
index 66c227e..0000000
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ItemListTest.java
+++ /dev/null
@@ -1,609 +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.car.app.model;
-
-import static androidx.car.app.model.CarIcon.ALERT;
-import static androidx.car.app.model.CarIcon.BACK;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.os.RemoteException;
-import android.text.SpannableString;
-
-import androidx.car.app.IOnDoneCallback;
-import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.model.ItemList.OnItemVisibilityChangedListener;
-import androidx.car.app.model.ItemList.OnSelectedListener;
-import androidx.car.app.utils.Logger;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Collections;
-
-/** Tests for {@link ItemListTest}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ItemListTest {
-    @Rule
-    public final MockitoRule mockito = MockitoJUnit.rule();
-
-    @Mock
-    private IOnDoneCallback.Stub mMockOnDoneCallback;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void createEmpty() {
-        ItemList list = ItemList.builder().build();
-        assertThat(list.getItems()).isEqualTo(Collections.emptyList());
-    }
-
-    @Test
-    public void createRows() {
-        Row row1 = Row.builder().setTitle("Row1").build();
-        Row row2 = Row.builder().setTitle("Row2").build();
-        ItemList list = ItemList.builder().addItem(row1).addItem(row2).build();
-
-        assertThat(list.getItems()).hasSize(2);
-        assertThat(list.getItems().get(0)).isEqualTo(row1);
-        assertThat(list.getItems().get(1)).isEqualTo(row2);
-    }
-
-    @Test
-    public void createGridItems() {
-        GridItem gridItem1 = GridItem.builder().setImage(BACK).build();
-        GridItem gridItem2 = GridItem.builder().setImage(BACK).build();
-        ItemList list = ItemList.builder().addItem(gridItem1).addItem(gridItem2).build();
-
-        assertThat(list.getItems()).containsExactly(gridItem1, gridItem2).inOrder();
-    }
-
-    @Test
-    public void clearRows() {
-        Row row1 = Row.builder().setTitle("Row1").build();
-        Row row2 = Row.builder().setTitle("Row2").build();
-        ItemList list = ItemList.builder().addItem(row1).addItem(row2).clearItems().build();
-
-        assertThat(list.getItems()).isEmpty();
-    }
-
-    @Test
-    public void clearGridItems() {
-        GridItem gridItem1 = GridItem.builder().setImage(BACK).build();
-        GridItem gridItem2 = GridItem.builder().setImage(BACK).build();
-        ItemList list = ItemList.builder().addItem(gridItem1).addItem(
-                gridItem2).clearItems().build();
-
-        assertThat(list.getItems()).isEmpty();
-    }
-
-    @Test
-    public void setSelectedable_emptyList_throws() {
-        assertThrows(
-                IllegalStateException.class,
-                () -> ItemList.builder().setSelectable(selectedIndex -> {
-                }).build());
-    }
-
-    @Test
-    public void setSelectedIndex_greaterThanListSize_throws() {
-        Row row1 = Row.builder().setTitle("Row1").build();
-        assertThrows(
-                IllegalStateException.class,
-                () -> ItemList.builder()
-                        .addItem(row1)
-                        .setSelectable(selectedIndex -> {
-                        })
-                        .setSelectedIndex(2)
-                        .build());
-    }
-
-    @Test
-    @UiThreadTest
-    public void setSelectable() throws RemoteException {
-        OnSelectedListener mockListener = mock(OnSelectedListener.class);
-        ItemList itemList =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("title").build())
-                        .setSelectable(mockListener)
-                        .build();
-
-        OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
-
-
-        itemList.getOnSelectedListener().onSelected(0, onDoneCallback);
-        verify(mockListener).onSelected(eq(0));
-        verify(onDoneCallback).onSuccess(null);
-    }
-
-    @Test
-    public void setSelectable_disallowOnClickListenerInRows() {
-        assertThrows(
-                IllegalStateException.class,
-                () -> ItemList.builder()
-                        .addItem(Row.builder().setTitle("foo").setOnClickListener(() -> {
-                        }).build())
-                        .setSelectable((index) -> {
-                        })
-                        .build());
-
-        // Positive test.
-        ItemList.builder()
-                .addItem(Row.builder().setTitle("foo").build())
-                .setSelectable((index) -> {
-                })
-                .build();
-    }
-
-    @Test
-    public void setSelectable_disallowToggleInRow() {
-        assertThrows(
-                IllegalStateException.class,
-                () -> ItemList.builder()
-                        .addItem(Row.builder().setToggle(Toggle.builder(isChecked -> {
-                        }).build()).build())
-                        .setSelectable((index) -> {
-                        })
-                        .build());
-    }
-
-    @Test
-    @UiThreadTest
-    public void setOnItemVisibilityChangeListener_triggerListener() {
-        OnItemVisibilityChangedListener listener = mock(OnItemVisibilityChangedListener.class);
-        ItemList list =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("1").build())
-                        .setOnItemsVisibilityChangeListener(listener)
-                        .build();
-
-        OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
-        list.getOnItemsVisibilityChangeListener().onItemVisibilityChanged(0, 1,
-                onDoneCallback);
-        ArgumentCaptor<Integer> startIndexCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Integer> endIndexCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(listener).onItemVisibilityChanged(startIndexCaptor.capture(),
-                endIndexCaptor.capture());
-        verify(onDoneCallback).onSuccess(null);
-        assertThat(startIndexCaptor.getValue()).isEqualTo(0);
-        assertThat(endIndexCaptor.getValue()).isEqualTo(1);
-    }
-
-    @Test
-    @UiThreadTest
-    public void setOnItemVisibilityChangeListener_triggerListenerWithFailure() {
-        OnItemVisibilityChangedListener listener = mock(OnItemVisibilityChangedListener.class);
-        ItemList list =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("1").build())
-                        .setOnItemsVisibilityChangeListener(listener)
-                        .build();
-
-        String testExceptionMessage = "Test exception";
-        doThrow(new RuntimeException(testExceptionMessage)).when(listener).onItemVisibilityChanged(
-                0, 1);
-
-        OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
-        try {
-            list.getOnItemsVisibilityChangeListener().onItemVisibilityChanged(0, 1,
-                    onDoneCallback);
-        } catch (WrappedRuntimeException e) {
-            assertThat(e.getMessage()).contains(testExceptionMessage);
-        }
-
-        ArgumentCaptor<Integer> startIndexCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Integer> endIndexCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(listener).onItemVisibilityChanged(startIndexCaptor.capture(),
-                endIndexCaptor.capture());
-        verify(onDoneCallback).onFailure(any());
-        assertThat(startIndexCaptor.getValue()).isEqualTo(0);
-        assertThat(endIndexCaptor.getValue()).isEqualTo(1);
-    }
-
-    @Test
-    public void validateRows_isRefresh() {
-        Logger logger = message -> {
-        };
-        Row.Builder row = Row.builder().setTitle("Title1");
-        ItemList listWithRows = ItemList.builder().addItem(row.build()).build();
-
-        // Text updates are disallowed.
-        ItemList listWithDifferentTitle =
-                ItemList.builder().addItem(row.setTitle("Title2").build()).build();
-        ItemList listWithDifferentText =
-                ItemList.builder().addItem(row.addText("Text").build()).build();
-        assertThat(listWithDifferentTitle.isRefresh(listWithRows, logger)).isFalse();
-        assertThat(listWithDifferentText.isRefresh(listWithRows, logger)).isFalse();
-
-        // Additional rows are disallowed.
-        ItemList listWithTwoRows = ItemList.builder().addItem(row.build()).addItem(
-                row.build()).build();
-        assertThat(listWithTwoRows.isRefresh(listWithRows, logger)).isFalse();
-    }
-
-    @Test
-    public void validateGridItems_isRefresh() {
-        Logger logger = message -> {
-        };
-        GridItem.Builder gridItem = GridItem.builder().setImage(BACK).setTitle("Title1");
-        ItemList listWithGridItems = ItemList.builder().addItem(gridItem.build()).build();
-
-        // Text updates are disallowed.
-        ItemList listWithDifferentTitle =
-                ItemList.builder().addItem(gridItem.setTitle("Title2").build()).build();
-        ItemList listWithDifferentText =
-                ItemList.builder().addItem(gridItem.setText("Text").build()).build();
-        assertThat(listWithDifferentTitle.isRefresh(listWithGridItems, logger)).isFalse();
-        assertThat(listWithDifferentText.isRefresh(listWithGridItems, logger)).isFalse();
-
-        // Image updates are disallowed.
-        ItemList listWithDifferentImage =
-                ItemList.builder().addItem(gridItem.setImage(ALERT).build()).build();
-        assertThat(listWithDifferentImage.isRefresh(listWithGridItems, logger)).isFalse();
-
-        // Additional grid items are disallowed.
-        ItemList listWithTwoGridItems =
-                ItemList.builder().addItem(gridItem.build()).addItem(gridItem.build()).build();
-        assertThat(listWithTwoGridItems.isRefresh(listWithGridItems, logger)).isFalse();
-    }
-
-    @Test
-    public void validateRows_isRefresh_differentSpansAreIgnored() {
-        Logger logger = message -> {
-        };
-        SpannableString textWithDistanceSpan = new SpannableString("Text");
-        textWithDistanceSpan.setSpan(
-                DistanceSpan.create(Distance.create(1000, Distance.UNIT_KILOMETERS)),
-                /* start= */ 0,
-                /* end= */ 1,
-                /* flags= */ 0);
-        SpannableString textWithDurationSpan = new SpannableString("Text");
-        textWithDurationSpan.setSpan(DurationSpan.create(1), 0, /* end= */ 1, /* flags= */ 0);
-
-        ItemList list1 =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle(textWithDistanceSpan).addText(
-                                textWithDurationSpan).build())
-                        .build();
-        ItemList list2 =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle(textWithDurationSpan).addText(
-                                textWithDistanceSpan).build())
-                        .build();
-        ItemList list3 =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("Text2").addText("Text2").build())
-                        .build();
-
-        assertThat(list2.isRefresh(list1, logger)).isTrue();
-        assertThat(list3.isRefresh(list1, logger)).isFalse();
-    }
-
-    @Test
-    public void validateRows_isRefresh_differentToggleStatesAllowTextUpdates() {
-        Logger logger = message -> {
-        };
-        Toggle onToggle = Toggle.builder(isChecked -> {
-        }).setChecked(true).build();
-        Toggle offToggle = Toggle.builder(isChecked -> {
-        }).setChecked(false).build();
-
-        ItemList listWithOnToggle =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("Title1").setToggle(onToggle).build())
-                        .build();
-        ItemList listWithOffToggle =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("Title1").setToggle(offToggle).build())
-                        .build();
-        ItemList listWithoutToggle =
-                ItemList.builder().addItem(Row.builder().setTitle("Title2").build()).build();
-        ItemList listWithOffToggleDifferentText =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("Title2").addText("Text").setToggle(
-                                offToggle).build())
-                        .build();
-        ItemList listWithOnToggleDifferentText =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("Title2").setToggle(onToggle).build())
-                        .build();
-
-        // Going from toggle to no toggle is not a refresh.
-        assertThat(listWithOnToggle.isRefresh(listWithoutToggle, logger)).isFalse();
-
-        // Going from on toggle to off toggle, or vice versa, is always a refresh
-        assertThat(listWithOnToggle.isRefresh(listWithOffToggleDifferentText, logger)).isTrue();
-        assertThat(listWithOffToggleDifferentText.isRefresh(listWithOnToggle, logger)).isTrue();
-
-        // If toggle state is the same, then text changes are not considered a refresh.
-        assertThat(listWithOnToggle.isRefresh(listWithOnToggleDifferentText, logger)).isFalse();
-        assertThat(listWithOffToggle.isRefresh(listWithOffToggleDifferentText, logger)).isFalse();
-    }
-
-    @Test
-    public void validateGridItems_isRefresh_differentToggleStatesAllowTextUpdates() {
-        Logger logger = message -> {
-        };
-        Toggle onToggle = Toggle.builder(isChecked -> {
-        }).setChecked(true).build();
-        Toggle offToggle = Toggle.builder(isChecked -> {
-        }).setChecked(false).build();
-
-        ItemList listWithOnToggle =
-                ItemList.builder()
-                        .addItem(
-                                GridItem.builder().setImage(BACK).setTitle("Title1").setToggle(
-                                        onToggle).build())
-                        .build();
-        ItemList listWithOffToggle =
-                ItemList.builder()
-                        .addItem(
-                                GridItem.builder().setImage(BACK).setTitle("Title1").setToggle(
-                                        offToggle).build())
-                        .build();
-        ItemList listWithoutToggle =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title2").build())
-                        .build();
-        ItemList listWithOffToggleDifferentText =
-                ItemList.builder()
-                        .addItem(
-                                GridItem.builder()
-                                        .setImage(BACK)
-                                        .setTitle("Title2")
-                                        .setText("Text")
-                                        .setToggle(offToggle)
-                                        .build())
-                        .build();
-        ItemList listWithOnToggleDifferentText =
-                ItemList.builder()
-                        .addItem(
-                                GridItem.builder().setImage(BACK).setTitle("Title2").setToggle(
-                                        onToggle).build())
-                        .build();
-
-        // Going from toggle to no toggle is not a refresh.
-        assertThat(listWithOnToggle.isRefresh(listWithoutToggle, logger)).isFalse();
-
-        // Going from on toggle to off toggle, or vice versa, is always a refresh
-        assertThat(listWithOnToggle.isRefresh(listWithOffToggleDifferentText, logger)).isTrue();
-        assertThat(listWithOffToggleDifferentText.isRefresh(listWithOnToggle, logger)).isTrue();
-
-        // If toggle state is the same, then text changes are not considered a refresh.
-        assertThat(listWithOnToggle.isRefresh(listWithOnToggleDifferentText, logger)).isFalse();
-        assertThat(listWithOffToggle.isRefresh(listWithOffToggleDifferentText, logger)).isFalse();
-    }
-
-    @Test
-    public void validateGridItems_isRefresh_differentToggleStatesAllowImageUpdates() {
-        Logger logger = message -> {
-        };
-        Toggle onToggle = Toggle.builder(isChecked -> {
-        }).setChecked(true).build();
-        Toggle offToggle = Toggle.builder(isChecked -> {
-        }).setChecked(false).build();
-
-        ItemList listWithOnToggle =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(BACK).setToggle(onToggle).build())
-                        .build();
-        ItemList listWithOffToggle =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(BACK).setToggle(offToggle).build())
-                        .build();
-        ItemList listWithoutToggle =
-                ItemList.builder().addItem(GridItem.builder().setImage(ALERT).build()).build();
-        ItemList listWithOffToggleDifferentImage =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(ALERT).setToggle(offToggle).build())
-                        .build();
-        ItemList listWithOnToggleDifferentImage =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(ALERT).setToggle(onToggle).build())
-                        .build();
-
-        // Going from toggle to no toggle is not a refresh.
-        assertThat(listWithOnToggle.isRefresh(listWithoutToggle, logger)).isFalse();
-
-        // Going from on toggle to off toggle, or vice versa, is always a refresh
-        assertThat(listWithOnToggle.isRefresh(listWithOffToggleDifferentImage, logger)).isTrue();
-        assertThat(listWithOffToggleDifferentImage.isRefresh(listWithOnToggle, logger)).isTrue();
-
-        // If toggle state is the same, then image changes are not considered a refresh.
-        assertThat(listWithOnToggle.isRefresh(listWithOnToggleDifferentImage, logger)).isFalse();
-        assertThat(listWithOffToggle.isRefresh(listWithOffToggleDifferentImage, logger)).isFalse();
-    }
-
-    @Test
-    public void validateGridItems_isRefresh_differentSelectedIndexAllowTextUpdates() {
-        Logger logger = message -> {
-        };
-        OnSelectedListener onSelectedListener = mock(OnSelectedListener.class);
-
-        ItemList listWithItem0Selected =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title11").build())
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title12").build())
-                        .setSelectable(onSelectedListener)
-                        .setSelectedIndex(0)
-                        .build();
-        ItemList listWithItem1Selected =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title21").build())
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title22").build())
-                        .setSelectable(onSelectedListener)
-                        .setSelectedIndex(1)
-                        .build();
-        ItemList listWithItem0SelectedDifferentText =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title21").build())
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title22").build())
-                        .setSelectable(onSelectedListener)
-                        .setSelectedIndex(0)
-                        .build();
-        ItemList listWithoutOnSelectedListener =
-                ItemList.builder()
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title21").build())
-                        .addItem(GridItem.builder().setImage(BACK).setTitle("Title22").build())
-                        .build();
-
-        // Selecting item 1 from item 0, or vice versa, is always a refresh.
-        assertThat(listWithItem0Selected.isRefresh(listWithItem1Selected, logger)).isTrue();
-        assertThat(listWithItem1Selected.isRefresh(listWithItem0Selected, logger)).isTrue();
-
-        // If item selection is the same, it is not considered a refresh
-        assertThat(listWithItem0Selected.isRefresh(listWithItem0SelectedDifferentText, logger))
-                .isFalse();
-
-        // If one of the ItemList doesn't have a selectable state, it is not a refresh.
-        assertThat(
-                listWithItem0Selected.isRefresh(listWithoutOnSelectedListener, logger)).isFalse();
-    }
-
-    @Test
-    public void equals_itemListWithRows() {
-        Row row = Row.builder().setTitle("Title").build();
-        ItemList itemList =
-                ItemList.builder()
-                        .setSelectable((index) -> {
-                        })
-                        .setNoItemsMessage("no items")
-                        .setSelectedIndex(0)
-                        .setOnItemsVisibilityChangeListener((start, end) -> {
-                        })
-                        .addItem(row)
-                        .build();
-        assertThat(itemList)
-                .isEqualTo(
-                        ItemList.builder()
-                                .setSelectable((index) -> {
-                                })
-                                .setNoItemsMessage("no items")
-                                .setSelectedIndex(0)
-                                .setOnItemsVisibilityChangeListener((start, end) -> {
-                                })
-                                .addItem(row)
-                                .build());
-    }
-
-    @Test
-    public void equals_itemListWithGridItems() {
-        GridItem gridItem = GridItem.builder().setImage(BACK).setTitle("Title").build();
-        ItemList itemList =
-                ItemList.builder()
-                        .setSelectable((index) -> {
-                        })
-                        .setNoItemsMessage("no items")
-                        .setSelectedIndex(0)
-                        .setOnItemsVisibilityChangeListener((start, end) -> {
-                        })
-                        .addItem(gridItem)
-                        .build();
-        assertThat(itemList)
-                .isEqualTo(
-                        ItemList.builder()
-                                .setSelectable((index) -> {
-                                })
-                                .setNoItemsMessage("no items")
-                                .setSelectedIndex(0)
-                                .setOnItemsVisibilityChangeListener((start, end) -> {
-                                })
-                                .addItem(gridItem)
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentNoItemsMessage() {
-        ItemList itemList = ItemList.builder().setNoItemsMessage("no items").build();
-        assertThat(itemList).isNotEqualTo(ItemList.builder().setNoItemsMessage("YO").build());
-    }
-
-    @Test
-    public void notEquals_differentSelectedIndex() {
-        Row row = Row.builder().setTitle("Title").build();
-        ItemList itemList =
-                ItemList.builder().setSelectable((index) -> {
-                }).addItem(row).addItem(row).build();
-        assertThat(itemList)
-                .isNotEqualTo(
-                        ItemList.builder()
-                                .setSelectable((index) -> {
-                                })
-                                .setSelectedIndex(1)
-                                .addItem(row)
-                                .addItem(row)
-                                .build());
-    }
-
-    @Test
-    public void notEquals_missingSelectedListener() {
-        Row row = Row.builder().setTitle("Title").build();
-        ItemList itemList =
-                ItemList.builder().setSelectable((index) -> {
-                }).addItem(row).addItem(row).build();
-        assertThat(itemList).isNotEqualTo(ItemList.builder().addItem(row).addItem(row).build());
-    }
-
-    @Test
-    public void notEquals_missingVisibilityChangedListener() {
-        Row row = Row.builder().setTitle("Title").build();
-        ItemList itemList =
-                ItemList.builder()
-                        .setOnItemsVisibilityChangeListener((start, end) -> {
-                        })
-                        .addItem(row)
-                        .addItem(row)
-                        .build();
-        assertThat(itemList).isNotEqualTo(ItemList.builder().addItem(row).addItem(row).build());
-    }
-
-    @Test
-    public void notEquals_differentRows() {
-        Row row = Row.builder().setTitle("Title").build();
-        ItemList itemList = ItemList.builder().addItem(row).addItem(row).build();
-        assertThat(itemList).isNotEqualTo(ItemList.builder().addItem(row).build());
-    }
-
-    @Test
-    public void notEquals_differentGridItems() {
-        GridItem gridItem = GridItem.builder().setImage(BACK).setTitle("Title").build();
-        ItemList itemList = ItemList.builder().addItem(gridItem).addItem(gridItem).build();
-        assertThat(itemList).isNotEqualTo(ItemList.builder().addItem(gridItem).build());
-    }
-}
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ListTemplateTest.java b/car/app/app/src/androidTest/java/androidx/car/app/model/ListTemplateTest.java
deleted file mode 100644
index fd05fa8c..0000000
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ListTemplateTest.java
+++ /dev/null
@@ -1,531 +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.car.app.model;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import android.text.SpannableString;
-
-import androidx.car.app.test.R;
-import androidx.car.app.utils.Logger;
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Tests for {@link ListTemplate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ListTemplateTest {
-    private final Logger mLogger = message -> {
-    };
-
-    @Test
-    public void createInstance_emptyList_notLoading_Throws() {
-        assertThrows(
-                IllegalStateException.class,
-                () -> ListTemplate.builder().setTitle("Title").build());
-
-        // Positive case
-        ListTemplate.builder().setTitle("Title").setLoading(true).build();
-    }
-
-    @Test
-    public void createInstance_isLoading_hasList_Throws() {
-        assertThrows(
-                IllegalStateException.class,
-                () ->
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setLoading(true)
-                                .setSingleList(getList())
-                                .build());
-    }
-
-    @Test
-    public void addEmptyList_throws() {
-        ItemList emptyList = ItemList.builder().build();
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> ListTemplate.builder().setTitle("Title").addList(emptyList,
-                        "header").build());
-    }
-
-    @Test
-    public void addList_emptyHeader_throws() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> ListTemplate.builder().setTitle("Title").addList(getList(), "").build());
-    }
-
-    @Test
-    public void resetList_clearsSingleList() {
-        ListTemplate.Builder builder =
-                ListTemplate.builder().setTitle("Title").setSingleList(getList());
-        assertThrows(IllegalStateException.class, () -> builder.clearAllLists().build());
-    }
-
-    @Test
-    public void resetList_clearsMultipleLists() {
-        ListTemplate.Builder builder =
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .addList(getList(), "header1")
-                        .addList(getList(), "header2");
-        assertThrows(IllegalStateException.class, () -> builder.clearAllLists().build());
-    }
-
-    @Test
-    public void addList_withVisibilityListener_throws() {
-        ItemList list =
-                ItemList.builder()
-                        .addItem(Row.builder().setTitle("Title").build())
-                        .setOnItemsVisibilityChangeListener((start, end) -> {
-                        })
-                        .build();
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> ListTemplate.builder().setTitle("Title").addList(list, "header").build());
-    }
-
-    @Test
-    public void addList_moreThanMaxTexts_throws() {
-        Row rowExceedsMaxTexts =
-                Row.builder().setTitle("Title").addText("text1").addText("text2").addText(
-                        "text3").build();
-        Row rowMeetingMaxTexts =
-                Row.builder().setTitle("Title").addText("text1").addText("text2").build();
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder().addItem(rowExceedsMaxTexts).build())
-                                .build());
-
-        // Positive case.
-        ListTemplate.builder()
-                .setTitle("Title")
-                .setSingleList(ItemList.builder().addItem(rowMeetingMaxTexts).build())
-                .build();
-    }
-
-    @Test
-    public void createInstance_noHeaderTitleOrAction_throws() {
-        assertThrows(
-                IllegalStateException.class,
-                () -> ListTemplate.builder().setSingleList(getList()).build());
-
-        // Positive cases/.
-        ListTemplate.builder().setTitle("Title").setSingleList(getList()).build();
-        ListTemplate.builder().setHeaderAction(Action.BACK).setSingleList(getList()).build();
-    }
-
-    @Test
-    public void createInstance_setSingleList() {
-        ItemList list = getList();
-        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-        assertThat(template.getSingleList()).isEqualTo(list);
-        assertThat(template.getSectionLists()).isEmpty();
-    }
-
-    @Test
-    public void createInstance_addList() {
-        ItemList list1 = getList();
-        ItemList list2 = getList();
-        ListTemplate template =
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .addList(list1, "header1")
-                        .addList(list2, "header2")
-                        .build();
-        assertThat(template.getSingleList()).isNull();
-        assertThat(template.getSectionLists()).hasSize(2);
-        assertThat(template.getSectionLists().get(0).getItemList()).isEqualTo(list1);
-        assertThat(template.getSectionLists().get(0).getHeader().getText()).isEqualTo("header1");
-        assertThat(template.getSectionLists().get(1).getItemList()).isEqualTo(list2);
-        assertThat(template.getSectionLists().get(1).getHeader().getText()).isEqualTo("header2");
-    }
-
-    @Test
-    public void setSingleList_clearLists() {
-        ItemList list1 = getList();
-        ItemList list2 = getList();
-        ItemList list3 = getList();
-        ListTemplate template =
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .addList(list1, "header1")
-                        .addList(list2, "header2")
-                        .setSingleList(list3)
-                        .build();
-        assertThat(template.getSingleList()).isEqualTo(list3);
-        assertThat(template.getSectionLists()).isEmpty();
-    }
-
-    @Test
-    public void addList_clearSingleList() {
-        ItemList list1 = getList();
-        ItemList list2 = getList();
-        ItemList list3 = getList();
-        ListTemplate template =
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .setSingleList(list1)
-                        .addList(list2, "header1")
-                        .addList(list3, "header2")
-                        .build();
-        assertThat(template.getSingleList()).isNull();
-        assertThat(template.getSectionLists()).hasSize(2);
-    }
-
-    @Test
-    public void createInstance_setHeaderAction_invalidActionThrows() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () ->
-                        ListTemplate.builder()
-                                .setHeaderAction(
-                                        Action.builder().setTitle("Action").setOnClickListener(
-                                                () -> {
-                                                }).build()));
-    }
-
-    @Test
-    public void createInstance_setHeaderAction() {
-        ListTemplate template =
-                ListTemplate.builder().setSingleList(getList()).setHeaderAction(
-                        Action.BACK).build();
-        assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
-    }
-
-    @Test
-    public void createInstance_setActionStrip() {
-        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
-        ListTemplate template =
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .setSingleList(getList())
-                        .setActionStrip(actionStrip)
-                        .build();
-        assertThat(template.getActionStrip()).isEqualTo(actionStrip);
-    }
-
-    @Test
-    public void validate_fromLoadingState_isRefresh() {
-        Row.Builder row = Row.builder().setTitle("Row1");
-        ItemList list = ItemList.builder().addItem(row.build()).build();
-        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Going from loading state to new content is allowed.
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder().setTitle("Title").setLoading(true).build(),
-                        mLogger))
-                .isTrue();
-    }
-
-    @Test
-    public void validate_mutableProperties_isRefresh() {
-        Row.Builder row = Row.builder().setTitle("Row1");
-        ItemList list = ItemList.builder().addItem(row.build()).build();
-        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Ensure a template is a refresh of itself.
-        assertThat(template.isRefresh(template, mLogger)).isTrue();
-
-        // Allowed mutable states.
-        SpannableString stringWithSpan = new SpannableString("Row1");
-        stringWithSpan.setSpan(DurationSpan.create(1), 0, /* end= */ 1, /* flags= */ 0);
-        IconCompat icon = IconCompat.createWithResource(
-                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1);
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder()
-                                                .addItem(
-                                                        row.setOnClickListener(() -> {
-                                                        })
-                                                                .setBrowsable(true)
-                                                                .setTitle(stringWithSpan)
-                                                                .setImage(CarIcon.of(icon))
-                                                                .build())
-                                                .build())
-                                .setHeaderAction(Action.BACK)
-                                .setActionStrip(
-                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
-                                .build(),
-                        mLogger))
-                .isTrue();
-    }
-
-    @Test
-    public void validate_multipleListMutableProperties_isRefresh() {
-        Row row = Row.builder().setTitle("Row1").build();
-        Row refreshRow =
-                Row.builder()
-                        .setTitle("Row1")
-                        .setOnClickListener(() -> {
-                        })
-                        .setBrowsable(true)
-                        .setImage(
-                                CarIcon.of(
-                                        IconCompat.createWithResource(
-                                                ApplicationProvider.getApplicationContext(),
-                                                R.drawable.ic_test_1)))
-                        .build();
-        ItemList list = ItemList.builder().addItem(row).build();
-        ListTemplate template =
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .addList(list, "header1")
-                        .addList(list, "header2")
-                        .build();
-
-        // Sublist refreshes are allowed as long as headers and  number of sections remain the same.
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .addList(ItemList.builder().addItem(refreshRow).build(), "header1")
-                                .addList(ItemList.builder().addItem(refreshRow).build(), "header2")
-                                .build(),
-                        mLogger))
-                .isTrue();
-    }
-
-    @Test
-    public void validate_titleUpdate_isNotRefresh() {
-        ItemList list = ItemList.builder().build();
-        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Title updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder().setSingleList(list).setTitle("Title2").build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_rowTextUpdate_isNotRefresh() {
-        Row.Builder row = Row.builder().setTitle("Row1");
-        ItemList list = ItemList.builder().addItem(row.build()).build();
-        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Text updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(ItemList.builder().addItem(
-                                        row.setTitle("Row2").build()).build())
-                                .build(),
-                        mLogger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(ItemList.builder().addItem(
-                                        row.addText("Text").build()).build())
-                                .build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_newRow_isNotRefresh() {
-        Row.Builder row = Row.builder().setTitle("Row1");
-        ItemList list = ItemList.builder().addItem(row.build()).build();
-        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Additional rows are disallowed.
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder().addItem(row.build()).addItem(
-                                                row.build()).build())
-                                .build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_multipleList_headerChange_isNotRefresh() {
-        Row.Builder row = Row.builder().setTitle("Row1");
-        ItemList list = ItemList.builder().addItem(row.build()).build();
-        ListTemplate template =
-                ListTemplate.builder().setTitle("Title").addList(list, "header1").build();
-
-        // Addition of lists are disallowed.
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder().setTitle("Title").addList(list, "header2").build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_newList_isNotRefresh() {
-        Row.Builder row = Row.builder().setTitle("Row1");
-        ItemList list = ItemList.builder().addItem(row.build()).build();
-        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
-                list).build();
-
-        // Addition of lists are disallowed.
-        assertThat(
-                template.isRefresh(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .addList(list, "header1")
-                                .addList(list, "header2")
-                                .build(),
-                        mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void validate_toLoadingState_isNotRefresh() {
-        // Going from content to loading state is disallowed.
-        assertThat(
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .setLoading(true)
-                        .build()
-                        .isRefresh(
-                                ListTemplate.builder()
-                                        .setTitle("Title")
-                                        .setSingleList(ItemList.builder().build())
-                                        .build(),
-                                mLogger))
-                .isFalse();
-    }
-
-    @Test
-    public void equals() {
-        ItemList itemList = ItemList.builder().build();
-        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
-        String title = "title";
-
-        ListTemplate template =
-                ListTemplate.builder()
-                        .setSingleList(itemList)
-                        .setActionStrip(actionStrip)
-                        .setHeaderAction(Action.BACK)
-                        .setTitle(title)
-                        .build();
-
-        assertThat(template)
-                .isEqualTo(
-                        ListTemplate.builder()
-                                .setSingleList(itemList)
-                                .setActionStrip(actionStrip)
-                                .setHeaderAction(Action.BACK)
-                                .setTitle(title)
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentItemList() {
-        ItemList itemList = ItemList.builder().build();
-
-        ListTemplate template =
-                ListTemplate.builder().setTitle("Title").setSingleList(itemList).build();
-
-        assertThat(template)
-                .isNotEqualTo(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(
-                                        ItemList.builder().addItem(
-                                                Row.builder().setTitle("Title").build()).build())
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentHeaderAction() {
-        ItemList itemList = ItemList.builder().build();
-
-        ListTemplate template =
-                ListTemplate.builder().setSingleList(itemList).setHeaderAction(Action.BACK).build();
-
-        assertThat(template)
-                .isNotEqualTo(
-                        ListTemplate.builder()
-                                .setSingleList(itemList)
-                                .setHeaderAction(Action.APP_ICON)
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentActionStrip() {
-        ItemList itemList = ItemList.builder().build();
-        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
-
-        ListTemplate template =
-                ListTemplate.builder()
-                        .setTitle("Title")
-                        .setSingleList(itemList)
-                        .setActionStrip(actionStrip)
-                        .build();
-
-        assertThat(template)
-                .isNotEqualTo(
-                        ListTemplate.builder()
-                                .setTitle("Title")
-                                .setSingleList(itemList)
-                                .setActionStrip(
-                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
-                                .build());
-    }
-
-    @Test
-    public void notEquals_differentTitle() {
-        ItemList itemList = ItemList.builder().build();
-        String title = "title";
-
-        ListTemplate template = ListTemplate.builder().setSingleList(itemList).setTitle(
-                title).build();
-
-        assertThat(template)
-                .isNotEqualTo(ListTemplate.builder().setSingleList(itemList).setTitle(
-                        "yo").build());
-    }
-
-    private static ItemList getList() {
-        Row row1 = Row.builder().setTitle("Bananas").build();
-        Row row2 = Row.builder().setTitle("Oranges").build();
-        return ItemList.builder().addItem(row1).addItem(row2).build();
-    }
-}
diff --git a/car/app/app/src/main/aidl/androidx/car/app/ICarApp.aidl b/car/app/app/src/main/aidl/androidx/car/app/ICarApp.aidl
index e8f3995..b7e4f99 100644
--- a/car/app/app/src/main/aidl/androidx/car/app/ICarApp.aidl
+++ b/car/app/app/src/main/aidl/androidx/car/app/ICarApp.aidl
@@ -61,12 +61,12 @@
   void getManager(in String type, IOnDoneCallback callback) = 8;
 
   /**
-   * Requests the version string of the library used for building the app.
+   * Requests information of the application (min API level, target API level, etc.).
    */
-  void getCarAppVersion(IOnDoneCallback callback) = 9;
+  void getAppInfo(IOnDoneCallback callback) = 9;
 
   /**
-   * Sends host information to the app.
+   * Sends host information and negotiated API level to the app.
    */
   void onHandshakeCompleted(in Bundleable handshakeInfo, IOnDoneCallback callback) = 10;
 }
diff --git a/car/app/app/src/main/aidl/androidx/car/app/navigation/INavigationManager.aidl b/car/app/app/src/main/aidl/androidx/car/app/navigation/INavigationManager.aidl
index 4e16e7e..7a3a01d 100644
--- a/car/app/app/src/main/aidl/androidx/car/app/navigation/INavigationManager.aidl
+++ b/car/app/app/src/main/aidl/androidx/car/app/navigation/INavigationManager.aidl
@@ -28,5 +28,5 @@
   * <p>The app should stop any audio guidance, routing notifications tagged for
   * the car, and metadata state updates.
   */
-  void stopNavigation(IOnDoneCallback callback) = 1;
+  void onStopNavigation(IOnDoneCallback callback) = 1;
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/AppInfo.java b/car/app/app/src/main/java/androidx/car/app/AppInfo.java
new file mode 100644
index 0000000..4c52330
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/AppInfo.java
@@ -0,0 +1,143 @@
+/*
+ * 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.car.app;
+
+import static androidx.car.app.utils.CommonUtils.TAG;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
+import androidx.car.app.versioning.CarAppApiLevel;
+import androidx.car.app.versioning.CarAppApiLevels;
+
+/**
+ * Container class for information about the app the host is connected to.
+ * <p>
+ * Hosts will use this information to provide the right level of compatibility, based on the
+ * application's minimum and maximum API level and its own set of supported API levels.
+ * <p>
+ * The application minimum API level is defined in the application's manifest using the
+ * following declaration.
+ * <pre>{@code
+ * <manifest ...>
+ *   <application ...>
+ *     <meta-data
+ *         android:name="androidx.car.app.min-api-level"
+ *         android:value="2" />
+ *     ...
+ *   </application>
+ * </manifest>
+ * }</pre>
+ * <p>
+ * @see CarContext#getCarAppApiLevel()
+ */
+public final class AppInfo {
+    // TODO(b/174803562): Automatically update the this version using Gradle
+    private static final String LIBRARY_VERSION = "1.0.0-alpha01";
+
+    /** @hide */
+    @RestrictTo(Scope.LIBRARY)
+    @VisibleForTesting
+    public static final String MIN_API_LEVEL_MANIFEST_KEY = "androidx.car.app.min-api-level";
+
+    @Nullable
+    private final String mLibraryVersion;
+    @CarAppApiLevel
+    private final int mMinCarAppApiLevel;
+    @CarAppApiLevel
+    private final int mLatestCarAppApiLevel;
+
+    /** @hide */
+    @RestrictTo(Scope.LIBRARY)
+    @NonNull
+    public static AppInfo create(@NonNull Context context) {
+        @CarAppApiLevel
+        int minApiLevel = retrieveMinCarAppApiLevel(context);
+        if (minApiLevel < CarAppApiLevels.OLDEST || minApiLevel > CarAppApiLevels.LATEST) {
+            throw new IllegalArgumentException("Min API level (" + MIN_API_LEVEL_MANIFEST_KEY
+                    + "=" + minApiLevel + ") is out of range (" + CarAppApiLevels.OLDEST + "-"
+                    + CarAppApiLevels.LATEST + ")");
+        }
+        return new AppInfo(minApiLevel, CarAppApiLevels.LATEST, LIBRARY_VERSION);
+    }
+
+    // Used for serialization
+    /** @hide */
+    @RestrictTo(Scope.LIBRARY)
+    public AppInfo() {
+        mMinCarAppApiLevel = 0;
+        mLibraryVersion = null;
+        mLatestCarAppApiLevel = 0;
+    }
+
+    // Used for testing
+    /** @hide */
+    @RestrictTo(Scope.LIBRARY)
+    @VisibleForTesting
+    public AppInfo(@CarAppApiLevel int minCarAppApiLevel, @CarAppApiLevel int latestCarAppApiLevel,
+            @NonNull String libraryVersion) {
+        mMinCarAppApiLevel = minCarAppApiLevel;
+        mLibraryVersion = libraryVersion;
+        mLatestCarAppApiLevel = latestCarAppApiLevel;
+    }
+
+    /** @hide */
+    @RestrictTo(Scope.LIBRARY)
+    @VisibleForTesting
+    @CarAppApiLevel
+    public static int retrieveMinCarAppApiLevel(@NonNull Context context) {
+        try {
+            ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(
+                    context.getPackageName(),
+                    PackageManager.GET_META_DATA);
+            if (applicationInfo.metaData == null) {
+                Log.i(TAG, "Min API level not found (" + MIN_API_LEVEL_MANIFEST_KEY + "). "
+                        + "Assuming min API level = " + CarAppApiLevels.LATEST);
+                return CarAppApiLevels.LATEST;
+            }
+            return applicationInfo.metaData.getInt(MIN_API_LEVEL_MANIFEST_KEY);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Unable to read min API level from manifest. Assuming "
+                            + CarAppApiLevels.LATEST, e);
+            return CarAppApiLevels.LATEST;
+        }
+    }
+
+    @NonNull
+    public String getLibraryVersion() {
+        return requireNonNull(mLibraryVersion);
+    }
+
+    @CarAppApiLevel
+    public int getMinCarAppApiLevel() {
+        return mMinCarAppApiLevel;
+    }
+
+    @CarAppApiLevel
+    public int getLatestCarAppApiLevel() {
+        return mLatestCarAppApiLevel;
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/AppManager.java b/car/app/app/src/main/java/androidx/car/app/AppManager.java
index 314ba5e..1196b19 100644
--- a/car/app/app/src/main/java/androidx/car/app/AppManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/AppManager.java
@@ -71,7 +71,7 @@
 
     /**
      * Requests the current template to be invalidated, which eventually triggers a call to {@link
-     * Screen#getTemplate} to get the new template to display.
+     * Screen#onGetTemplate} to get the new template to display.
      *
      * @throws HostException if the remote call fails.
      */
diff --git a/car/app/app/src/main/java/androidx/car/app/CarAppService.java b/car/app/app/src/main/java/androidx/car/app/CarAppService.java
index 00ce152..4964e699 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarAppService.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarAppService.java
@@ -48,6 +48,23 @@
 /**
  * The base class for implementing a car app that runs in the car.
  *
+ * <h4>Service Declaration</h4>
+ *
+ * The app must extend the {@link CarAppService} to be bound by the car host. The service must also
+ * respond to {@link Intent} actions coming from the host, by adding an
+ * <code>intent-filter</code> to the service in the <code>AndroidManifest.xml</code> that handles
+ * the {@link #SERVICE_INTERFACE} action. For example:
+ *
+ * <pre>{@code
+ * <service
+ *   android:name=".YourAppService"
+ *   android:exported="true">
+ *   <intent-filter>
+ *     <action android:name="androidx.car.app.CarAppService" />
+ *   </intent-filter>
+ * </service>
+ * }</pre>
+ *
  * <h4>Accessing Location</h4>
  *
  * When the app is running in the car display, the system will not consider it as being in the
@@ -65,6 +82,11 @@
 // actually cleaning any held resources in that method.
 @SuppressWarnings("NotCloseable")
 public abstract class CarAppService extends Service implements LifecycleOwner {
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService";
+
     private static final String TAG = "CarAppService";
     private static final String AUTO_DRIVE = "AUTO_DRIVE";
 
@@ -75,7 +97,15 @@
     private final CarContext mCarContext = CarContext.create(mRegistry);
 
     @Nullable
-    HostInfo mHostInfo;
+    private HostInfo mHostInfo;
+    @Nullable
+    private AppInfo mAppInfo;
+
+    @Override
+    @CallSuper
+    public void onCreate() {
+        mAppInfo = AppInfo.create(mCarContext);
+    }
 
     /**
      * Handles the host binding to this car app.
@@ -88,7 +118,7 @@
     @Override
     @CallSuper
     @Nullable
-    public IBinder onBind(@NonNull Intent intent) {
+    public final IBinder onBind(@NonNull Intent intent) {
         return mBinder;
     }
 
@@ -107,7 +137,7 @@
             mRegistry.handleLifecycleEvent(Event.ON_STOP);
 
             // Stop any active navigation
-            mCarContext.getCarService(NavigationManager.class).stopNavigation();
+            mCarContext.getCarService(NavigationManager.class).onStopNavigation();
 
             // Destroy all screens in the stack
             mCarContext.getCarService(ScreenManager.class).destroyAndClearScreenStack();
@@ -174,7 +204,7 @@
      * <p>At some point after this call {@link #onCarAppFinished} will be called, and eventually the
      * system will destroy this {@link CarAppService}.
      */
-    public void finish() {
+    public final void finish() {
         mCarContext.finishCarApp();
     }
 
@@ -196,7 +226,7 @@
      *
      * <p>This method is invoked when this car app is first opened by the user.
      *
-     * <p>Once the method returns, {@link Screen#getTemplate} will be called on the {@link Screen}
+     * <p>Once the method returns, {@link Screen#onGetTemplate} will be called on the {@link Screen}
      * returned, and the app will be displayed on the car screen.
      *
      * <p>To pre-seed a back stack, you can push {@link Screen}s onto the stack, via {@link
@@ -219,7 +249,7 @@
     /**
      * Notifies that the car app has received a new {@link Intent}.
      *
-     * <p>Once the method returns, {@link Screen#getTemplate} will be called on the {@link Screen}
+     * <p>Once the method returns, {@link Screen#onGetTemplate} will be called on the {@link Screen}
      * that is on top of the {@link Screen} stack managed by the {@link ScreenManager}, and the app
      * will be displayed on the car screen.
      *
@@ -301,18 +331,19 @@
      */
     @NonNull
     @Override
-    public Lifecycle getLifecycle() {
+    public final Lifecycle getLifecycle() {
         return mRegistry;
     }
 
     @Override
     @CallSuper
-    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+    public final void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
             @Nullable String[] args) {
         super.dump(fd, writer, args);
 
         for (String arg : args) {
             if (AUTO_DRIVE.equals(arg)) {
+                Log.d(TAG, "Executing onAutoDriveEnabled");
                 runOnMain(mCarContext.getCarService(NavigationManager.class)::onAutoDriveEnabled);
             }
         }
@@ -324,7 +355,7 @@
      * @see HostInfo
      */
     @Nullable
-    public HostInfo getHostInfo() {
+    public final HostInfo getHostInfo() {
         return mHostInfo;
     }
 
@@ -432,7 +463,7 @@
                             RemoteUtils.sendSuccessResponse(
                                     callback,
                                     "getManager",
-                                    mCarContext.getCarService(
+                                    getCarContext().getCarService(
                                             NavigationManager.class).getIInterface());
                             return;
                         default:
@@ -444,9 +475,9 @@
                 }
 
                 @Override
-                public void getCarAppVersion(IOnDoneCallback callback) {
+                public void getAppInfo(IOnDoneCallback callback) {
                     RemoteUtils.sendSuccessResponse(
-                            callback, "getCarAppVersion", CarAppVersion.INSTANCE.toString());
+                            callback, "getAppInfo", mAppInfo);
                 }
 
                 @Override
@@ -458,10 +489,12 @@
                         String packageName = deserializedHandshakeInfo.getHostPackageName();
                         int uid = Binder.getCallingUid();
                         mHostInfo = new HostInfo(packageName, uid);
-                    } catch (BundlerException e) {
+                        mCarContext.onHandshakeComplete(deserializedHandshakeInfo);
+                        RemoteUtils.sendSuccessResponse(callback, "onHandshakeCompleted", null);
+                    } catch (BundlerException | IllegalArgumentException e) {
                         mHostInfo = null;
+                        RemoteUtils.sendFailureResponse(callback, "onHandshakeCompleted", e);
                     }
-                    RemoteUtils.sendSuccessResponse(callback, "onHandshakeCompleted", null);
                 }
 
                 // call to onNewIntent(android.content.Intent) not allowed on the given receiver.
diff --git a/car/app/app/src/main/java/androidx/car/app/CarAppVersion.java b/car/app/app/src/main/java/androidx/car/app/CarAppVersion.java
deleted file mode 100644
index d23f7e1..0000000
--- a/car/app/app/src/main/java/androidx/car/app/CarAppVersion.java
+++ /dev/null
@@ -1,270 +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.car.app;
-
-import static androidx.car.app.CarAppVersion.ReleaseSuffix.RELEASE_SUFFIX_BETA;
-import static androidx.car.app.CarAppVersion.ReleaseSuffix.RELEASE_SUFFIX_EAP;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.Locale;
-
-/**
- * A versioning class used for compatibility checks between the host and client.
- *
- * <p>The version scheme follows semantic versioning and is defined as:
- *
- * <pre>major.minor.patch[-releasesSuffix.build]<pre>
- *
- * where:
- *
- * <ul>
- *   <li>major version differences denote binary incompatibility (e.g. API removal)
- *   <li>minor version differences denote compatible API changes (e.g. API additional/deprecation)
- *   <li>patch version differences denote non-API altering internal changes (e.g. bug fixes)
- *   <li>releaseSuffix is{@code null} for stable versions but otherwise reserved for special-purpose
- *       EAP builds and one-off public betas. For cases where a release suffix is provided, the
- *       appended build number is used to differentiate versions within the same suffix category.
- *       (e.g. 1.0.0-eap.1 vs 1.0.0-eap.2).
- * </ul>
- */
-public class CarAppVersion {
-    private static final String MAIN_VERSION_FORMAT = "%d.%d.%d";
-    private static final String SUFFIX_VERSION_FORMAT = "-%s.%d";
-
-    public static final CarAppVersion INSTANCE =
-            CarAppVersion.create(1, 0, 0, RELEASE_SUFFIX_BETA, 1);
-
-    /** Min version of the SDK which supports handshake completed binder call. */
-    public static final CarAppVersion HANDSHAKE_MIN_VERSION =
-            CarAppVersion.create(1, 0, 0, RELEASE_SUFFIX_BETA, 1);
-
-    /** Different types of supported version suffixes. */
-    public enum ReleaseSuffix {
-        RELEASE_SUFFIX_EAP("eap"),
-        RELEASE_SUFFIX_BETA("beta");
-
-        private final String mValue;
-
-        ReleaseSuffix(String value) {
-            this.mValue = value;
-        }
-
-        /** Creates a {@link ReleaseSuffix} from the string standard representation as described
-         * in the {@link #toString()} method */
-        @NonNull
-        public static ReleaseSuffix fromString(@NonNull String value) {
-            switch (value) {
-                case "eap":
-                    return RELEASE_SUFFIX_EAP;
-                case "beta":
-                    return RELEASE_SUFFIX_BETA;
-                default:
-                    throw new IllegalArgumentException(value + " is not a valid release suffix");
-            }
-        }
-
-        @NonNull
-        @Override
-        public String toString() {
-            return mValue;
-        }
-    }
-
-    private final int mMajor;
-    private final int mMinor;
-    private final int mPatch;
-    @Nullable
-    private final ReleaseSuffix mReleaseSuffix;
-    private final int mBuild;
-
-    /** Creates a {@link CarAppVersion} without a release suffix. (e.g. 1.0.0) */
-    @NonNull
-    static CarAppVersion create(int major, int minor, int patch) {
-        return new CarAppVersion(major, minor, patch, null, 0);
-    }
-
-    /**
-     * Creates a {@link CarAppVersion} with a release suffix and build number. (e.g. 1.0.0-eap.0)
-     *
-     * <p>Note that if {@code releaseSuffix} is {@code null}, then {@code build} is ignored.
-     */
-    @NonNull
-    static CarAppVersion create(
-            int major, int minor, int patch, ReleaseSuffix releaseSuffix, int build) {
-        return new CarAppVersion(major, minor, patch, releaseSuffix, build);
-    }
-
-    /**
-     * Creates a {@link CarAppVersion} instance based on its string representation.
-     *
-     * <p>The string should be of the format major.minor.patch(-releaseSuffix.build). If the
-     * string is malformed or {@code null}, {@code null} will be returned.
-     */
-    @Nullable
-    public static CarAppVersion of(@NonNull String versionString) throws MalformedVersionException {
-        return parseVersionString(versionString);
-    }
-
-    private static CarAppVersion parseVersionString(String versionString)
-            throws MalformedVersionException {
-        String[] versionSplit = versionString.split("-", -1);
-        if (versionSplit.length > 2) {
-            throw new MalformedVersionException(
-                    "Malformed version string (more than 1 \"-\" detected): " + versionString);
-        }
-
-        String mainVersion = versionSplit[0];
-        String[] mainVersionSplit = mainVersion.split("\\.", -1);
-
-        // Main version should be formatted as major.minor.patch
-        if (mainVersionSplit.length != 3) {
-            throw new MalformedVersionException(
-                    "Malformed version string (invalid main version format): " + versionString);
-        }
-
-        int major;
-        int minor;
-        int patch;
-        ReleaseSuffix releaseSuffix = null;
-        int build = 0;
-
-        try {
-            major = Integer.parseInt(mainVersionSplit[0]);
-            minor = Integer.parseInt(mainVersionSplit[1]);
-            patch = Integer.parseInt(mainVersionSplit[2]);
-
-            String suffixVersion = versionSplit.length > 1 ? versionSplit[1] : null;
-            if (suffixVersion != null) {
-                String[] suffixVersionSplit = suffixVersion.split("\\.", -1);
-
-                // Release suffix should be formatted as releaseSuffix.build
-                if (suffixVersionSplit.length != 2) {
-                    throw new MalformedVersionException(
-                            "Malformed version string (invalid suffix version format): "
-                                    + versionString);
-                }
-
-                try {
-                    releaseSuffix = ReleaseSuffix.fromString(suffixVersionSplit[0]);
-                } catch (IllegalArgumentException e) {
-                    throw new MalformedVersionException(
-                            "Malformed version string (unsupported suffix): " + versionString, e);
-                }
-                build = Integer.parseInt(suffixVersionSplit[1]);
-            }
-
-            return new CarAppVersion(major, minor, patch, releaseSuffix, build);
-        } catch (NumberFormatException exception) {
-            throw new MalformedVersionException(
-                    "Malformed version string (unsupported characters): " + versionString,
-                    exception);
-        }
-    }
-
-    private CarAppVersion(
-            int major, int minor, int patch, @Nullable ReleaseSuffix releaseSuffix, int build) {
-        this.mMajor = major;
-        this.mMinor = minor;
-        this.mPatch = patch;
-        this.mReleaseSuffix = releaseSuffix;
-        this.mBuild = build;
-    }
-
-    /** Returns the human-readable format of this version. */
-    @NonNull
-    @Override
-    public String toString() {
-        String versionString = String.format(Locale.US, MAIN_VERSION_FORMAT, mMajor, mMinor,
-                mPatch);
-        if (mReleaseSuffix != null) {
-            versionString += String.format(Locale.US, SUFFIX_VERSION_FORMAT, mReleaseSuffix,
-                    mBuild);
-        }
-
-        return versionString;
-    }
-
-    /**
-     * Checks whether this {@link CarAppVersion} is greater than or equal to {@code other}, which is
-     * used to determine compatibility. Returns true if so, false otherwise.
-     *
-     * <p>The rules of comparison are as follow:
-     *
-     * <ul>
-     *   <li>If either versions are suffixed with {@link ReleaseSuffix#RELEASE_SUFFIX_EAP}, the
-     *   version strings have to be exact match to be considered compatible.
-     *   <li>The major version has to be greater or equal to be considered compatible.
-     *   <li>If major versions are equal, the minor version has to be greater or equal to be
-     *   considered compatible.
-     *   <li>If major.minor versions are equal, the patch version has to be greater or equal to
-     *   be considered compatible.
-     *   <li>If the major.minor.patch versions are equal, for stable versions release suffix
-     *   equals {@code null}, the instance is considered compatible with all
-     *   {@link ReleaseSuffix#RELEASE_SUFFIX_BETA} versions; for
-     *   {@link ReleaseSuffix#RELEASE_SUFFIX_BETA}, the instance is considered compatible iff the
-     *   other is a {@link ReleaseSuffix#RELEASE_SUFFIX_BETA} version and that the build is
-     *   greater or equal to the other version.
-     * </ul>
-     */
-    public boolean isGreaterOrEqualTo(@NonNull CarAppVersion other) {
-        // For EAP versions, we require an exact match.
-        if (mReleaseSuffix == RELEASE_SUFFIX_EAP || other.mReleaseSuffix == RELEASE_SUFFIX_EAP) {
-            return mMajor == other.mMajor
-                    && mMinor == other.mMinor
-                    && mPatch == other.mPatch
-                    && mReleaseSuffix == other.mReleaseSuffix
-                    && mBuild == other.mBuild;
-        }
-
-        int result = Integer.compare(mMajor, other.mMajor);
-        // Major version differs, return.
-        if (result != 0) {
-            return result == 1;
-        }
-
-        // Minor version differs, return.
-        result = Integer.compare(mMinor, other.mMinor);
-        if (result != 0) {
-            return result == 1;
-        }
-
-        // Patch version differs, return.
-        result = Integer.compare(mPatch, other.mPatch);
-        if (result != 0) {
-            return result == 1;
-        }
-
-        if (mReleaseSuffix == null) {
-            // A stable version (is considered compatible to any beta versions, the suffix
-            // version is
-            // ignored in those cases.
-            return true;
-        } else if (mReleaseSuffix == RELEASE_SUFFIX_BETA) {
-            // Compatible if beta build is greater than other's beta build.
-            if (other.mReleaseSuffix == RELEASE_SUFFIX_BETA) {
-                return mBuild >= other.mBuild;
-            } else {
-                // Beta version is incompatible with stable version.
-                return false;
-            }
-        } else {
-            throw new IllegalStateException("Invalid release suffix: " + mReleaseSuffix);
-        }
-    }
-}
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 8c44fe5..1258fcc 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
@@ -40,9 +40,12 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.StringDef;
+import androidx.car.app.annotations.RequiresCarApi;
 import androidx.car.app.navigation.NavigationManager;
 import androidx.car.app.utils.RemoteUtils;
 import androidx.car.app.utils.ThreadUtils;
+import androidx.car.app.versioning.CarAppApiLevel;
+import androidx.car.app.versioning.CarAppApiLevels;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
 
@@ -54,7 +57,7 @@
  * The CarContext class is a {@link ContextWrapper} subclass accessible to your {@link
  * CarAppService} and {@link Screen} instances, which provides access to car services such as the
  * {@link ScreenManager} for managing the screen stack, the {@link AppManager} for general
- * app-related functionality such as accessing a surface for drawing your navigation app’s map, and
+ * app-related functionality such as accessing a surface for drawing your navigation app's map, and
  * the {@link NavigationManager} used by turn-by-turn navigation apps to communicate navigation
  * metadata and other navigation-related events with the host. See Access the navigation templates
  * for a comprehensive list of library functionality available to navigation apps.
@@ -81,22 +84,22 @@
      *
      * @hide
      */
-    @StringDef({APP_SERVICE, CAR_SERVICE, NAVIGATION_SERVICE, SCREEN_MANAGER_SERVICE})
+    @StringDef({APP_SERVICE, CAR_SERVICE, NAVIGATION_SERVICE, SCREEN_SERVICE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface CarServiceType {
     }
 
     /** Manages all app events such as invalidating the UI, showing a toast, etc. */
-    public static final String APP_SERVICE = "app_manager";
+    public static final String APP_SERVICE = "app";
 
     /**
      * Manages all navigation events such as starting navigation when focus is granted, abandoning
      * navigation when focus is lost, etc.
      */
-    public static final String NAVIGATION_SERVICE = "navigation_manager";
+    public static final String NAVIGATION_SERVICE = "navigation";
 
     /** Manages the screens of the app, including the screen stack. */
-    public static final String SCREEN_MANAGER_SERVICE = "screen_manager";
+    public static final String SCREEN_SERVICE = "screen";
 
     /**
      * Internal usage only. Top level binder to host.
@@ -107,7 +110,8 @@
      * Key for including a IStartCarApp in the notification {@link Intent}, for starting the app
      * if it has not been opened yet.
      */
-    public static final String START_CAR_APP_BINDER_KEY = "StartCarAppBinderKey";
+    public static final String EXTRA_START_CAR_APP_BINDER_KEY = "androidx.car.app.extra"
+            + ".START_CAR_APP_BINDER_KEY";
 
     /**
      * Standard action for navigating to a location.
@@ -121,9 +125,12 @@
     private final NavigationManager mNavigationManager;
     private final ScreenManager mScreenManager;
     private final OnBackPressedDispatcher mOnBackPressedDispatcher;
-
     private final HostDispatcher mHostDispatcher;
 
+    /** API level, updated once host connection handshake is completed. */
+    @CarAppApiLevel
+    private int mCarAppApiLevel = CarAppApiLevels.UNKNOWN;
+
     /** @hide */
     @NonNull
     @RestrictTo(LIBRARY)
@@ -143,13 +150,13 @@
      *   <dd>An {@link AppManager} for communication between the app and the host.
      *   <dt>{@link #NAVIGATION_SERVICE}
      *   <dd>A {@link NavigationManager} for management of navigation updates.
-     *   <dt>{@link #SCREEN_MANAGER_SERVICE}
+     *   <dt>{@link #SCREEN_SERVICE}
      *   <dd>A {@link ScreenManager} for management of {@link Screen}s.
      * </dl>
      *
      * @param name The name of the car service requested. This should be one of
      *             {@link #APP_SERVICE},
-     *             {@link #NAVIGATION_SERVICE} or {@link #SCREEN_MANAGER_SERVICE}.
+     *             {@link #NAVIGATION_SERVICE} or {@link #SCREEN_SERVICE}.
      * @return The car service instance.
      * @throws IllegalArgumentException if {@code name} does not refer to a valid car service.
      * @throws NullPointerException     if {@code name} is {@code null}.
@@ -162,7 +169,7 @@
                 return mAppManager;
             case NAVIGATION_SERVICE:
                 return mNavigationManager;
-            case SCREEN_MANAGER_SERVICE:
+            case SCREEN_SERVICE:
                 return mScreenManager;
             default: // fall out
         }
@@ -206,7 +213,7 @@
         } else if (serviceClass.isInstance(mNavigationManager)) {
             return NAVIGATION_SERVICE;
         } else if (serviceClass.isInstance(mScreenManager)) {
-            return SCREEN_MANAGER_SERVICE;
+            return SCREEN_SERVICE;
         }
 
         throw new IllegalArgumentException("The class does not correspond to a car service.");
@@ -282,7 +289,7 @@
         IBinder binder = null;
         Bundle extras = notificationIntent.getExtras();
         if (extras != null) {
-            binder = extras.getBinder(START_CAR_APP_BINDER_KEY);
+            binder = extras.getBinder(EXTRA_START_CAR_APP_BINDER_KEY);
         }
         if (binder == null) {
             throw new IllegalArgumentException("Notification intent missing expected extra");
@@ -374,6 +381,22 @@
     }
 
     /**
+     * Updates context information based on the information provided during connection handshake
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    @MainThread
+    void onHandshakeComplete(HandshakeInfo handshakeInfo) {
+        int carAppApiLevel = handshakeInfo.getHostCarAppApiLevel();
+        if (!CarAppApiLevels.isValid(carAppApiLevel)) {
+            throw new IllegalArgumentException("Invalid Car App API level received: "
+                    + carAppApiLevel);
+        }
+        mCarAppApiLevel = carAppApiLevel;
+    }
+
+    /**
      * Attaches the base {@link Context} for this {@link CarContext} by creating a new display
      * context using {@link #createDisplayContext} with a {@link VirtualDisplay} created using
      * the metrics from the provided {@link Configuration}, and then also calling {@link
@@ -431,6 +454,39 @@
         mHostDispatcher.resetHosts();
     }
 
+    /**
+     * Retrieves the API level negotiated with the host.
+     * <p>
+     * API levels are used during client and host connection handshake to negotiate a common set of
+     * elements that both processes can understand. Different devices might have different host
+     * versions. Each of these hosts will support a
+     * range of API levels, as a way to provide backwards compatibility.
+     * <p>
+     * Applications can also provide forward compatibility, by declaring support for a
+     * {@link AppInfo#getMinCarAppApiLevel()} lower than {@link AppInfo#getLatestCarAppApiLevel()}.
+     * See {@link AppInfo#getMinCarAppApiLevel()} for more details.
+     * <p>
+     * Clients must ensure no elements annotated with a {@link RequiresCarApi} value higher
+     * than {@link #getCarAppApiLevel()} is used at runtime.
+     * <p>
+     * Please refer to {@link RequiresCarApi} description for more details on how to
+     * implement forward compatibility.
+     *
+     * @return a value between {@link AppInfo#getMinCarAppApiLevel()} and
+     * {@link AppInfo#getLatestCarAppApiLevel()}. In case of incompatibility, the host will
+     * disconnect from the service before completing the handshake.
+     *
+     * @throws IllegalStateException if invoked before the connection handshake with the host has
+     * been completed (for example, before {@link CarAppService#onCreateScreen(Intent)}).
+     */
+    @CarAppApiLevel
+    public int getCarAppApiLevel() {
+        if (mCarAppApiLevel == CarAppApiLevels.UNKNOWN) {
+            throw new IllegalStateException("Car App API level hasn't been established yet");
+        }
+        return mCarAppApiLevel;
+    }
+
     /** @hide */
     @RestrictTo(LIBRARY_GROUP) // Restrict to testing library
     @SuppressWarnings({
@@ -442,7 +498,7 @@
 
         this.mHostDispatcher = hostDispatcher;
         mAppManager = AppManager.create(this, hostDispatcher);
-        mNavigationManager = NavigationManager.create(hostDispatcher);
+        mNavigationManager = NavigationManager.create(this, hostDispatcher);
         mScreenManager = ScreenManager.create(this, lifecycle);
         mOnBackPressedDispatcher =
                 new OnBackPressedDispatcher(() -> getCarService(ScreenManager.class).pop());
diff --git a/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java b/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java
index 8d3f137..8b92f28 100644
--- a/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java
@@ -33,18 +33,25 @@
 public class HandshakeInfo {
     @Nullable
     private final String mHostPackageName;
+    private final int mHostCarAppApiLevel;
 
-    public HandshakeInfo(@NonNull String hostPackageName) {
-        this.mHostPackageName = hostPackageName;
+    public HandshakeInfo(@NonNull String hostPackageName, int hostCarAppApiLevel) {
+        mHostPackageName = hostPackageName;
+        mHostCarAppApiLevel = hostCarAppApiLevel;
     }
 
     // Used for serialization
     public HandshakeInfo() {
         mHostPackageName = null;
+        mHostCarAppApiLevel = 0;
     }
 
     @NonNull
     public String getHostPackageName() {
         return requireNonNull(mHostPackageName);
     }
+
+    public int getHostCarAppApiLevel() {
+        return mHostCarAppApiLevel;
+    }
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/HostInfo.java b/car/app/app/src/main/java/androidx/car/app/HostInfo.java
index 5b1dad2..7174e18 100644
--- a/car/app/app/src/main/java/androidx/car/app/HostInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/HostInfo.java
@@ -19,9 +19,10 @@
 import static java.util.Objects.requireNonNull;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
 
 /**
- * Container class for information about the host the service is connected to.
+ * Container class for information about the host the app is connected to.
  *
  * <p>Apps can use this information to determine how they will respond to the host. For example, a
  * host which is not recognized could receive a message screen while an authorized host could
@@ -29,16 +30,24 @@
  *
  * <p>The package name and uid can used to query the system package manager for a signature or to
  * determine if the host has a system signature.
+ *
+ * <p>The host API level can be used to adjust the models exchanged with the host to those valid
+ * for the specific host version the app is connected to.
  */
 public class HostInfo {
     @NonNull
     private final String mPackageName;
     private final int mUid;
 
-    /** Constructs an instance of the HostInfo from the required package name and uid. */
+    /**
+     * Constructs an instance of the HostInfo from the required package name, uid and API level.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public HostInfo(@NonNull String packageName, int uid) {
-        this.mPackageName = requireNonNull(packageName);
-        this.mUid = uid;
+        mPackageName = requireNonNull(packageName);
+        mUid = uid;
     }
 
     /** Retrieves the package name of the host. */
diff --git a/car/app/app/src/main/java/androidx/car/app/host/OnCheckedChangeListenerWrapper.java b/car/app/app/src/main/java/androidx/car/app/OnCheckedChangeListenerWrapper.java
similarity index 97%
rename from car/app/app/src/main/java/androidx/car/app/host/OnCheckedChangeListenerWrapper.java
rename to car/app/app/src/main/java/androidx/car/app/OnCheckedChangeListenerWrapper.java
index c6f7ba1..4aaca10 100644
--- a/car/app/app/src/main/java/androidx/car/app/host/OnCheckedChangeListenerWrapper.java
+++ b/car/app/app/src/main/java/androidx/car/app/OnCheckedChangeListenerWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host;
+package androidx.car.app;
 
 import androidx.annotation.NonNull;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/host/OnDoneCallback.java b/car/app/app/src/main/java/androidx/car/app/OnDoneCallback.java
similarity index 85%
rename from car/app/app/src/main/java/androidx/car/app/host/OnDoneCallback.java
rename to car/app/app/src/main/java/androidx/car/app/OnDoneCallback.java
index bc6db49..8ace8ef 100644
--- a/car/app/app/src/main/java/androidx/car/app/host/OnDoneCallback.java
+++ b/car/app/app/src/main/java/androidx/car/app/OnDoneCallback.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host;
+package androidx.car.app;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -27,11 +27,15 @@
     /**
      * Notifies that the request has been successfully processed the request and provides a
      * response.
+     *
+     * @param response the {@link Bundleable} containing the success response.
      */
     void onSuccess(@Nullable Bundleable response);
 
     /**
      * Notifies that the request was not fulfilled successfully.
+     *
+     * @param response the {@link Bundleable} containing the failure response.
      */
     void onFailure(@NonNull Bundleable response);
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/host/OnItemVisibilityChangedListenerWrapper.java b/car/app/app/src/main/java/androidx/car/app/OnItemVisibilityChangedListenerWrapper.java
similarity index 97%
rename from car/app/app/src/main/java/androidx/car/app/host/OnItemVisibilityChangedListenerWrapper.java
rename to car/app/app/src/main/java/androidx/car/app/OnItemVisibilityChangedListenerWrapper.java
index e536430..5d7175f6 100644
--- a/car/app/app/src/main/java/androidx/car/app/host/OnItemVisibilityChangedListenerWrapper.java
+++ b/car/app/app/src/main/java/androidx/car/app/OnItemVisibilityChangedListenerWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host;
+package androidx.car.app;
 
 import androidx.annotation.NonNull;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/host/OnSelectedListenerWrapper.java b/car/app/app/src/main/java/androidx/car/app/OnSelectedListenerWrapper.java
similarity index 81%
rename from car/app/app/src/main/java/androidx/car/app/host/OnSelectedListenerWrapper.java
rename to car/app/app/src/main/java/androidx/car/app/OnSelectedListenerWrapper.java
index 3bef05c..812d211 100644
--- a/car/app/app/src/main/java/androidx/car/app/host/OnSelectedListenerWrapper.java
+++ b/car/app/app/src/main/java/androidx/car/app/OnSelectedListenerWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host;
+package androidx.car.app;
 
 import androidx.annotation.NonNull;
 
@@ -27,6 +27,10 @@
      *
      * <p>This event is called even if the selection did not change, for example, if the user
      * selected an already selected item.
+     *
+     * @param selectedIndex the index of the selected item.
+     * @param callback      the {@link OnDoneCallback} to trigger when the client finishes handling
+     *                      the event.
      */
     void onSelected(int selectedIndex, @NonNull OnDoneCallback callback);
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/Screen.java b/car/app/app/src/main/java/androidx/car/app/Screen.java
index 62158f0..f36b455 100644
--- a/car/app/app/src/main/java/androidx/car/app/Screen.java
+++ b/car/app/app/src/main/java/androidx/car/app/Screen.java
@@ -40,7 +40,7 @@
  * A Screen has a {@link Lifecycle} and provides the mechanism for the app to send {@link Template}s
  * to display when the Screen is visible. Screen instances can also be pushed and popped to and from
  * a Screen stack, which ensures they adhere to the template flow restrictions (see {@link
- * #getTemplate} for more details on template flow).
+ * #onGetTemplate} for more details on template flow).
  *
  * <p>The Screen class can be used to manage individual units of business logic within a car app. A
  * Screen is closely tied to the {@link CarAppService} it is a part of, and cannot be used without
@@ -84,7 +84,7 @@
     /**
      * Whether to set the ID of the last template in the next template to be returned.
      *
-     * @see #getTemplate
+     * @see #onGetTemplate
      */
     private boolean mUseLastTemplateId;
 
@@ -94,15 +94,15 @@
 
     /**
      * Requests the current template to be invalidated, which eventually triggers a call to {@link
-     * #getTemplate} to get the new template to display.
+     * #onGetTemplate} to get the new template to display.
      *
      * <p>If the current {@link State} of this screen is not at least {@link State#STARTED}, then a
      * call to this method will have no effect.
      *
      * <p>After the call to invalidate is made, subsequent calls have no effect until the new
-     * template is returned by {@link #getTemplate}.
+     * template is returned by {@link #onGetTemplate}.
      *
-     * <p>To avoid race conditions with calls to {@link #getTemplate} you should call this method
+     * <p>To avoid race conditions with calls to {@link #onGetTemplate} you should call this method
      * with the main thread.
      *
      * @throws HostException if the remote call fails.
@@ -184,7 +184,7 @@
      *   <dt>{@link Event#ON_CREATE}
      *   <dd>The screen is in the process of being pushed to the screen stack, it is valid, but
      *       contents from it are not yet visible in the car screen. You should get a callback to
-     *       {@link #getTemplate} at a point after this call.
+     *       {@link #onGetTemplate} at a point after this call.
      *   <dt>{@link Event#ON_START}
      *   <dd>The template returned from this screen is visible in the car screen.
      *   <dt>{@link Event#ON_RESUME}
@@ -291,7 +291,7 @@
      * Certain {@link Template} classes have special semantics that signify the end of a task. For
      * example, the {@link androidx.car.app.navigation.model.NavigationTemplate} is a template
      * that is expected to stay on the screen and be refreshed with new turn-by-turn instructions
-     * for the user’s consumption. Upon reaching one of these templates, the host will reset the
+     * for the user's consumption. Upon reaching one of these templates, the host will reset the
      * template quota, treating that template as if it is the first step of a new task, thus
      * allowing the app to begin a new task. See the documentation of individual {@link Template}
      * classes to see which ones trigger a reset on the host.
@@ -304,7 +304,7 @@
      * <p>See {@code androidx.car.app.notification.CarAppExtender} for details on notifications.
      */
     @NonNull
-    public abstract Template getTemplate();
+    public abstract Template onGetTemplate();
 
     /** Sets a {@link OnScreenResultCallback} for this {@link Screen}. */
     void setOnResultCallback(OnScreenResultCallback onScreenResultCallback) {
@@ -330,7 +330,7 @@
     }
 
     /**
-     * Calls {@link #getTemplate} to get the next {@link Template} for the screen and returns it
+     * Calls {@link #onGetTemplate} to get the next {@link Template} for the screen and returns it
      * wrapped in a {@link TemplateWrapper}.
      *
      * <p>The {@link TemplateWrapper} attaches a unique ID to the wrapped template, which is used
@@ -345,7 +345,7 @@
      */
     @NonNull
     TemplateWrapper getTemplateWrapper() {
-        Template template = getTemplate();
+        Template template = onGetTemplate();
 
         TemplateWrapper wrapper;
         if (mUseLastTemplateId) {
@@ -368,15 +368,15 @@
      * Returns the information for the template that was last returned by this screen.
      *
      * <p>If no templates have been returned from this screen yet, this will call
-     * {@link #getTemplate} to retrieve the {@link Template} and generate an info for it. This is
-     * used in the case where multiple screens are added before a {@link #getTemplate} method is
+     * {@link #onGetTemplate} to retrieve the {@link Template} and generate an info for it. This is
+     * used in the case where multiple screens are added before a {@link #onGetTemplate} method is
      * dispatched to the top screen, allowing to notify the host of the current stack of template
      * ids known to the client.
      */
     @NonNull
     TemplateInfo getLastTemplateInfo() {
         if (mTemplateWrapper == null) {
-            mTemplateWrapper = TemplateWrapper.wrap(getTemplate());
+            mTemplateWrapper = TemplateWrapper.wrap(onGetTemplate());
         }
         return new TemplateInfo(mTemplateWrapper.getTemplate(), mTemplateWrapper.getId());
     }
@@ -387,8 +387,8 @@
     }
 
     /**
-     * Denotes whether the next {@link Template} retrieved via {@link #getTemplate} should reuse the
-     * ID of the last {@link Template}.
+     * Denotes whether the next {@link Template} retrieved via {@link #onGetTemplate} should reuse
+     * the ID of the last {@link Template}.
      *
      * <p>When this is set to {@code true}, the host will considered the next template sent to be a
      * back operation, and will attempt to find the previous template that shares the same ID and
diff --git a/car/app/app/src/main/java/androidx/car/app/host/SearchListenerWrapper.java b/car/app/app/src/main/java/androidx/car/app/SearchListenerWrapper.java
similarity index 97%
rename from car/app/app/src/main/java/androidx/car/app/host/SearchListenerWrapper.java
rename to car/app/app/src/main/java/androidx/car/app/SearchListenerWrapper.java
index 75036fa..050ac89 100644
--- a/car/app/app/src/main/java/androidx/car/app/host/SearchListenerWrapper.java
+++ b/car/app/app/src/main/java/androidx/car/app/SearchListenerWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host;
+package androidx.car.app;
 
 import androidx.annotation.NonNull;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/annotations/RequiresCarApi.java b/car/app/app/src/main/java/androidx/car/app/annotations/RequiresCarApi.java
new file mode 100644
index 0000000..4201a81
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/annotations/RequiresCarApi.java
@@ -0,0 +1,59 @@
+/*
+ * 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.car.app.annotations;
+
+import androidx.car.app.CarContext;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Defines the minimum API level required to be able to use this model, field or method.
+ * <p>
+ * Before using any of this elements, application must check that
+ * {@link CarContext#getCarAppApiLevel()} is equal or greater than the value of this annotation.
+ * <p>
+ * For example, if an application wants to use a newer template "Foo" marked with
+ * <code>@RequiresHostApiLevel(2)</code> while maintain backwards compatibility with older hosts
+ * by using an older template "Bar" (<code>@RequiresHostApiLevel(1)</code>), they can do:
+ *
+ * <pre>
+ * if (getCarContext().getCarApiLevel() >= 2) {
+ *     // Use new feature
+ *     return Foo.Builder()....;
+ * } else {
+ *     // Use supported fallback
+ *     return Bar.Builder()....;
+ * }
+ * </pre>
+ *
+ * If a certain model or method has no {@link RequiresCarApi} annotation, it is assumed to
+ * be available in all car API levels.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
+public @interface RequiresCarApi {
+
+    /**
+     * The minimum API level required to be able to use this model, field or method. Applications
+     * shouldn't use any elements annotated with a {@link RequiresCarApi} greater than
+     * {@link CarContext#getCarAppApiLevel()}
+     */
+    int value();
+}
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 551e78f..34cb78d 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,8 +34,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.CarContext;
-import androidx.car.app.host.model.OnClickListenerWrapper;
-import androidx.car.app.host.model.OnClickListenerWrapperImpl;
 import androidx.car.app.model.constraints.CarIconConstraints;
 import androidx.lifecycle.LifecycleOwner;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java b/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java
index ee98a8c..ddd6ef2 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java
@@ -140,17 +140,6 @@
         }
 
         /**
-         * Clears any actions that may have been added with {@link #addAction(Action)} up to this
-         * point.
-         */
-        @NonNull
-        public Builder clearActions() {
-            mActions.clear();
-            mAddedActionTypes.clear();
-            return this;
-        }
-
-        /**
          * Constructs the {@link ActionStrip} defined by this builder.
          *
          * @throws IllegalStateException if the action strip is empty.
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java b/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
index 5ae0e97..5cf23ac 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
@@ -112,7 +112,6 @@
                     TYPE_ALERT,
                     TYPE_APP,
                     TYPE_ERROR,
-                    TYPE_WILLIAM_ALERT,
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CarIconType {
@@ -157,13 +156,6 @@
     public static final int TYPE_ERROR = 6;
 
     /**
-     * An alerting William.
-     *
-     * @see #WILLIAM_ALERT
-     */
-    public static final int TYPE_WILLIAM_ALERT = 7;
-
-    /**
      * Represents the app's icon, as defined in the app's manifest by the {@code android:icon}
      * attribute of the {@code application} element.
      */
@@ -179,10 +171,6 @@
     @NonNull
     public static final CarIcon ERROR = CarIcon.forStandardType(TYPE_ERROR);
 
-    @NonNull
-    public static final CarIcon WILLIAM_ALERT =
-            CarIcon.forStandardType(TYPE_WILLIAM_ALERT, /* tint= */ null);
-
     @Keep
     @CarIconType
     private final int mType;
@@ -345,8 +333,6 @@
                 return "APP";
             case TYPE_ERROR:
                 return "ERROR";
-            case TYPE_WILLIAM_ALERT:
-                return "WILLIAM_ALERT";
             case TYPE_BACK:
                 return "BACK";
             case TYPE_CUSTOM:
diff --git a/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java b/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java
index 9fe03a8..3ae6a5f 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java
@@ -38,7 +38,7 @@
  * the string before the interpunct:
  *
  * <pre>{@code
- * String interpunct = "\u00b7";
+ * String interpunct = "\\u00b7";
  * SpannableString string = new SpannableString("  " + interpunct + " Point-of-Interest 1");
  * string.setSpan(
  *   DistanceSpan.create(
@@ -52,7 +52,7 @@
  * apply styling to the text, such as changing colors:
  *
  * <pre>{@code
- * String interpunct = "\u00b7";
+ * String interpunct = "\\u00b7";
  * SpannableString string = new SpannableString("  " + interpunct + " Point-of-Interest 1");
  * string.setSpan(
  *   DistanceSpan.create(
diff --git a/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java b/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java
index 05e58a9..ac05662 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java
@@ -36,7 +36,7 @@
  * the string before the interpunct:
  *
  * <pre>{@code
- * String interpunct = "\u00b7";
+ * String interpunct = "\\u00b7";
  * SpannableString string = new SpannableString("  " + interpunct + " Point-of-Interest 1");
  * string.setSpan(DurationSpan.create(300), 0, 1, SPAN_INCLUSIVE_INCLUSIVE);
  * }</pre>
@@ -48,7 +48,7 @@
  * apply styling to the text, such as changing colors:
  *
  * <pre>{@code
- * String interpunct = "\u00b7";
+ * String interpunct = "\\u00b7";
  * SpannableString string = new SpannableString("  " + interpunct + " Point-of-Interest 1");
  * string.setSpan(DurationSpan.create(300), 0, 1, SPAN_INCLUSIVE_INCLUSIVE);
  * string.setSpan(ForegroundCarColorSpan.create(CarColor.BLUE), 0, 1, SPAN_EXCLUSIVE_EXCLUSIVE);
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 95fa7cf..32e9482 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
@@ -28,8 +28,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.car.app.host.model.OnClickListenerWrapper;
-import androidx.car.app.host.model.OnClickListenerWrapperImpl;
 import androidx.car.app.model.constraints.CarIconConstraints;
 
 import java.lang.annotation.Retention;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
index a3a4ddf..199d19d 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
@@ -25,7 +25,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.car.app.model.constraints.CarIconConstraints;
-import androidx.car.app.utils.Logger;
 
 import java.util.Collections;
 import java.util.Objects;
@@ -36,7 +35,7 @@
  * <h4>Template Restrictions</h4>
  *
  * In regards to template refreshes, as described in
- * {@link androidx.car.app.Screen#getTemplate()}, this template is considered a refresh of a
+ * {@link androidx.car.app.Screen#onGetTemplate()}, this template is considered a refresh of a
  * previous one if:
  *
  * <ul>
@@ -102,35 +101,6 @@
         return mBackgroundImage;
     }
 
-    @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        requireNonNull(oldTemplate);
-
-        if (oldTemplate.getClass() != this.getClass()) {
-            return false;
-        }
-
-        GridTemplate old = (GridTemplate) oldTemplate;
-
-        if (!Objects.equals(old.getTitle(), getTitle())) {
-            return false;
-        }
-
-        if (old.mIsLoading) {
-            // Transition from a previous loading state is allowed.
-            return true;
-        } else if (mIsLoading) {
-            // Transition to a loading state is disallowed.
-            return false;
-        }
-
-        if (mSingleList != null && old.mSingleList != null) {
-            return mSingleList.isRefresh(old.mSingleList, logger);
-        }
-
-        return true;
-    }
-
     @NonNull
     @Override
     public String toString() {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
index 2a1dcd0..9d054bd 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
@@ -28,14 +28,11 @@
 import androidx.car.app.IOnDoneCallback;
 import androidx.car.app.IOnItemVisibilityChangedListener;
 import androidx.car.app.IOnSelectedListener;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.OnItemVisibilityChangedListenerWrapper;
+import androidx.car.app.OnSelectedListenerWrapper;
 import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.host.OnItemVisibilityChangedListenerWrapper;
-import androidx.car.app.host.OnSelectedListenerWrapper;
-import androidx.car.app.host.model.OnClickListenerWrapper;
-import androidx.car.app.utils.Logger;
 import androidx.car.app.utils.RemoteUtils;
-import androidx.car.app.utils.ValidationUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -51,7 +48,7 @@
     /**
      * A listener for handling selection events for lists with selectable items.
      *
-     * @see Builder#setSelectable(OnSelectedListener)
+     * @see Builder#setOnSelectedListener(OnSelectedListener)
      */
     public interface OnSelectedListener {
         /**
@@ -126,7 +123,7 @@
      * items in the list changes.
      */
     @Nullable
-    public OnItemVisibilityChangedListenerWrapper getOnItemsVisibilityChangeListener() {
+    public OnItemVisibilityChangedListenerWrapper getOnItemsVisibilityChangedListener() {
         return mOnItemVisibilityChangedListener;
     }
 
@@ -136,30 +133,6 @@
         return mItems;
     }
 
-    /**
-     * Returns {@code true} if this {@link ItemList} instance is determined to be a refresh of the
-     * given list, or {@code false} otherwise.
-     *
-     * <p>A list is considered a refresh if:
-     *
-     * <ul>
-     *   <li>The other list is in a loading state, or
-     *   <li>The item size and string contents of the two lists are the same. For rows that
-     *   contain a
-     *       {@link Toggle}, the string contents can be updated if the toggle state has changed
-     *       between the previous and new rows. For grid items that contain a {@link Toggle}, string
-     *       contents and images can be updated if the toggle state has changed.
-     * </ul>
-     */
-    public boolean isRefresh(@Nullable ItemList other, @NonNull Logger logger) {
-        if (other == null) {
-            return false;
-        }
-
-        return ValidationUtils.itemsHaveSameContent(
-                other.getItems(), other.getSelectedIndex(), getItems(), getSelectedIndex(), logger);
-    }
-
     @Override
     @NonNull
     public String toString() {
@@ -238,7 +211,7 @@
          */
         @NonNull
         @SuppressLint("ExecutorRegistration")
-        public Builder setOnItemsVisibilityChangeListener(
+        public Builder setOnItemsVisibilityChangedListener(
                 @Nullable OnItemVisibilityChangedListener itemVisibilityChangedListener) {
             this.mOnItemVisibilityChangedListener =
                     itemVisibilityChangedListener == null
@@ -248,28 +221,24 @@
         }
 
         /**
-         * Marks the list as selectable and sets the {@link OnSelectedListener} to call when an
-         * item is selected by the user. Set to {@code null} to mark the list as non-selectable.
+         * Marks the list as selectable by setting the {@link OnSelectedListener} to call when an
+         * item is selected by the user, or set to {@code null} to mark the list as non-selectable.
          *
          * <p>Selectable lists, where allowed by the template they are added to, automatically
-         * display
-         * an item in a selected state when selected by the user.
+         * display an item in a selected state when selected by the user.
          *
          * <p>The items in the list define a mutually exclusive selection scope: only a single
-         * item will
-         * be selected at any given time.
+         * item will be selected at any given time.
          *
          * <p>The specific way in which the selection will be visualized depends on the template
-         * and the
-         * host implementation. For example, some templates may display the list as a radio button
-         * group, while others may highlight the selected item's background.
+         * and the host implementation. For example, some templates may display the list as a
+         * radio button group, while others may highlight the selected item's background.
          *
          * @see #setSelectedIndex(int)
          */
         @NonNull
-        // TODO(rampara): Review if API should be updated to match getter.
-        @SuppressLint({"MissingGetterMatchingBuilder", "ExecutorRegistration"})
-        public Builder setSelectable(@Nullable OnSelectedListener onSelectedListener) {
+        @SuppressLint("ExecutorRegistration")
+        public Builder setOnSelectedListener(@Nullable OnSelectedListener onSelectedListener) {
             this.mOnSelectedListener =
                     onSelectedListener == null ? null : createOnSelectedListener(
                             onSelectedListener);
@@ -281,8 +250,8 @@
          *
          * <p>By default and unless explicitly set with this method, the first item is selected.
          *
-         * <p>If the list is not a selectable list set with {@link #setSelectable}, this value is
-         * ignored.
+         * <p>If the list is not a selectable list set with {@link #setOnSelectedListener}, this
+         * value is ignored.
          */
         @NonNull
         public Builder setSelectedIndex(int selectedIndex) {
@@ -298,8 +267,7 @@
          * Sets the text to display if the list is empty.
          *
          * <p>If the list is empty and the app does not explicitly set the message with this
-         * method, the
-         * host will show a default message.
+         * method, the host will show a default message.
          */
         @NonNull
         public Builder setNoItemsMessage(@Nullable CharSequence noItemsMessage) {
@@ -330,8 +298,7 @@
          *
          * @throws IllegalStateException if the list is selectable but does not have any items.
          * @throws IllegalStateException if the selected index is greater or equal to the size of
-         *                               the
-         *                               list.
+         *                               the list.
          * @throws IllegalStateException if the list is selectable and any items have either one of
          *                               their {@link OnClickListener} or {@link Toggle} set.
          */
@@ -355,8 +322,8 @@
                     if (getOnClickListener(item) != null) {
                         throw new IllegalStateException(
                                 "Items that belong to selectable lists can't have an "
-                                        + "onClickListener. Use the"
-                                        + " OnSelectedListener of the list instead");
+                                        + "onClickListener. Use the OnSelectedListener of the list "
+                                        + "instead");
                     }
 
                     if (getToggle(item) != null) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
index d3da538..dda936c 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
@@ -30,7 +30,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.Screen;
-import androidx.car.app.utils.Logger;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -42,7 +41,7 @@
  *
  * <h4>Template Restrictions</h4>
  *
- * In regards to template refreshes, as described in {@link Screen#getTemplate()}, this
+ * In regards to template refreshes, as described in {@link Screen#onGetTemplate()}, this
  * template is considered a refresh of a previous one if:
  *
  * <ul>
@@ -109,49 +108,6 @@
         return mActionStrip;
     }
 
-    @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        requireNonNull(oldTemplate);
-
-        if (oldTemplate.getClass() != this.getClass()) {
-            return false;
-        }
-
-        ListTemplate old = (ListTemplate) oldTemplate;
-
-        if (!Objects.equals(old.getTitle(), getTitle())) {
-            return false;
-        }
-
-        if (old.mIsLoading) {
-            // Transition from a previous loading state is allowed.
-            return true;
-        } else if (mIsLoading) {
-            // Transition to a loading state is disallowed.
-            return false;
-        }
-
-        if (mSingleList != null && old.mSingleList != null) {
-            return mSingleList.isRefresh(old.mSingleList, logger);
-        } else {
-            if (mSectionLists.size() != old.mSectionLists.size()) {
-                return false;
-            }
-
-            for (int i = 0; i < mSectionLists.size(); i++) {
-                SectionedItemList section = mSectionLists.get(i);
-                SectionedItemList oldSection = old.mSectionLists.get(i);
-
-                if (!section.getHeader().equals(oldSection.getHeader())
-                        || !section.getItemList().isRefresh(oldSection.getItemList(), logger)) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
     @NonNull
     @Override
     public String toString() {
@@ -266,15 +222,6 @@
             return this;
         }
 
-        /** Resets any list(s) that were added via {@link #setSingleList} or {@link #addList}. */
-        @NonNull
-        public Builder clearAllLists() {
-            mSingleList = null;
-            mSectionLists.clear();
-            mHasSelectableList = false;
-            return this;
-        }
-
         /**
          * Sets a single {@link ItemList} to show in the template.
          *
@@ -335,7 +282,7 @@
                 throw new IllegalArgumentException("List cannot be empty");
             }
 
-            if (list.getOnItemsVisibilityChangeListener() != null) {
+            if (list.getOnItemsVisibilityChangedListener() != null) {
                 throw new IllegalArgumentException(
                         "OnItemVisibilityChangedListener in the list is disallowed");
             }
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 90beda5..baa90dc 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
@@ -27,7 +27,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.car.app.model.constraints.CarIconConstraints;
-import androidx.car.app.utils.Logger;
 
 import java.util.Collections;
 import java.util.List;
@@ -39,7 +38,7 @@
  * <h4>Template Restrictions</h4>
  *
  * In regards to template refreshes, as described in
- * {@link androidx.car.app.Screen#getTemplate()}, this template is
+ * {@link androidx.car.app.Screen#onGetTemplate()}, this template is
  * considered a refresh of a previous one if the title and messages have not changed.
  */
 public final class MessageTemplate implements Template {
@@ -94,24 +93,10 @@
     }
 
     @Nullable
-    public ActionList getActionList() {
+    public ActionList getActions() {
         return mActionList;
     }
 
-    @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        requireNonNull(oldTemplate);
-
-        if (oldTemplate.getClass() != this.getClass()) {
-            return false;
-        }
-
-        MessageTemplate old = (MessageTemplate) oldTemplate;
-        return Objects.equals(old.getTitle(), getTitle())
-                && Objects.equals(old.getDebugMessage(), getDebugMessage())
-                && Objects.equals(old.getMessage(), getMessage());
-    }
-
     @NonNull
     @Override
     public String toString() {
@@ -288,8 +273,6 @@
          * @throws NullPointerException if {@code actions} is {@code null}.
          */
         @NonNull
-        // TODO(shiufai): consider rename to match getter's name (e.g. setActionList or getActions).
-        @SuppressLint("MissingGetterMatchingBuilder")
         public Builder setActions(@NonNull List<Action> actions) {
             mActionList = ActionList.create(requireNonNull(actions));
             return this;
diff --git a/car/app/app/src/main/java/androidx/car/app/host/model/OnClickListenerWrapper.java b/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapper.java
similarity index 92%
rename from car/app/app/src/main/java/androidx/car/app/host/model/OnClickListenerWrapper.java
rename to car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapper.java
index 64f2691c..fd9d619 100644
--- a/car/app/app/src/main/java/androidx/car/app/host/model/OnClickListenerWrapper.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapper.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host.model;
+package androidx.car.app.model;
 
 import androidx.annotation.NonNull;
-import androidx.car.app.host.OnDoneCallback;
+import androidx.car.app.OnDoneCallback;
 
 /**
  * A host-side interface for reporting click to clients.
diff --git a/car/app/app/src/main/java/androidx/car/app/host/model/OnClickListenerWrapperImpl.java b/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapperImpl.java
similarity index 92%
rename from car/app/app/src/main/java/androidx/car/app/host/model/OnClickListenerWrapperImpl.java
rename to car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapperImpl.java
index fa695a8..be60b6f 100644
--- a/car/app/app/src/main/java/androidx/car/app/host/model/OnClickListenerWrapperImpl.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapperImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host.model;
+package androidx.car.app.model;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
@@ -26,11 +26,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.IOnDoneCallback;
+import androidx.car.app.OnDoneCallback;
 import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.model.IOnClickListener;
-import androidx.car.app.model.OnClickListener;
-import androidx.car.app.model.ParkedOnlyOnClickListener;
 import androidx.car.app.utils.RemoteUtils;
 
 /**
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Pane.java b/car/app/app/src/main/java/androidx/car/app/model/Pane.java
index 0670466..08e486b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Pane.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Pane.java
@@ -18,13 +18,9 @@
 
 import static java.util.Objects.requireNonNull;
 
-import android.annotation.SuppressLint;
-
 import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.car.app.utils.Logger;
-import androidx.car.app.utils.ValidationUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -54,7 +50,7 @@
      * Returns the list of {@link Action}s displayed alongside the {@link Row}s in this pane.
      */
     @Nullable
-    public ActionList getActionList() {
+    public ActionList getActions() {
         return mActionList;
     }
 
@@ -73,29 +69,6 @@
         return mIsLoading;
     }
 
-    /**
-     * Returns {@code true} if this {@link Pane} instance is determined to be a refresh of the given
-     * pane, or {@code false} otherwise.
-     *
-     * <p>A pane is considered a refresh if:
-     *
-     * <ul>
-     *   <li>The other pane is in a loading state, or
-     *   <li>The row size and string contents of the two panes are the same.
-     * </ul>
-     */
-    public boolean isRefresh(@Nullable Pane other, @NonNull Logger logger) {
-        if (other == null) {
-            return false;
-        } else if (other.isLoading()) {
-            return true;
-        } else if (isLoading()) {
-            return false;
-        }
-
-        return ValidationUtils.itemsHaveSameContent(other.getRows(), getRows(), logger);
-    }
-
     @Override
     @NonNull
     public String toString() {
@@ -174,13 +147,6 @@
             return this;
         }
 
-        /** Clears any rows that may have been added with {@link #addRow(Row)} up to this point. */
-        @NonNull
-        public Builder clearRows() {
-            mRows.clear();
-            return this;
-        }
-
         /**
          * Sets multiple {@link Action}s to display alongside the rows in the pane.
          *
@@ -189,8 +155,6 @@
          * @throws NullPointerException if {@code actions} is {@code null}.
          */
         @NonNull
-        // TODO(shiufai): consider rename to match getter's name (e.g. setActionList or getActions).
-        @SuppressLint("MissingGetterMatchingBuilder")
         public Builder setActions(@NonNull List<Action> actions) {
             mActionList = ActionList.create(requireNonNull(actions));
             return this;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
index 37597b6..c2e8a4b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
@@ -27,7 +27,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.car.app.utils.Logger;
 
 import java.util.Collections;
 import java.util.Objects;
@@ -38,7 +37,7 @@
  * <h4>Template Restrictions</h4>
  *
  * In regards to template refreshes, as described in
- * {@link androidx.car.app.Screen#getTemplate()}, this template is considered a refresh of a
+ * {@link androidx.car.app.Screen#onGetTemplate()}, this template is considered a refresh of a
  * previous one if:
  *
  * <ul>
@@ -92,17 +91,6 @@
         return mActionStrip;
     }
 
-    @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        if (oldTemplate.getClass() != this.getClass()) {
-            return false;
-        }
-
-        PaneTemplate old = (PaneTemplate) oldTemplate;
-        return Objects.equals(old.getTitle(), getTitle()) && getPane().isRefresh(old.getPane(),
-                logger);
-    }
-
     @NonNull
     @Override
     public String toString() {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java
index 1cec76f..187c3c8 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java
@@ -21,8 +21,6 @@
 import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_SIMPLE;
 import static androidx.car.app.model.constraints.RowListConstraints.ROW_LIST_CONSTRAINTS_SIMPLE;
 
-import static java.util.Objects.requireNonNull;
-
 import android.Manifest.permission;
 import android.content.Context;
 
@@ -31,7 +29,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.CarAppPermission;
-import androidx.car.app.utils.Logger;
 
 import java.util.Collections;
 import java.util.List;
@@ -46,7 +43,7 @@
  * <h4>Template Restrictions</h4>
  *
  * In regards to template refreshes, as described in
- * {@link androidx.car.app.Screen#getTemplate()}, this template is considered a refresh of a
+ * {@link androidx.car.app.Screen#onGetTemplate()}, this template is considered a refresh of a
  * previous one if:
  *
  * <ul>
@@ -117,29 +114,6 @@
     }
 
     @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        requireNonNull(oldTemplate);
-        if (oldTemplate.getClass() != this.getClass()) {
-            return false;
-        }
-
-        PlaceListMapTemplate old = (PlaceListMapTemplate) oldTemplate;
-        if (!Objects.equals(old.getTitle(), getTitle())) {
-            return false;
-        }
-
-        if (old.mIsLoading) {
-            // Transition from a previous loading state is allowed.
-            return true;
-        } else if (mIsLoading) {
-            // Transition to a loading state is disallowed.
-            return false;
-        }
-
-        return requireNonNull(mItemList).isRefresh(old.getItemList(), logger);
-    }
-
-    @Override
     public void checkPermissions(@NonNull Context context) {
         if (isCurrentLocationEnabled()) {
             CarAppPermission.checkHasPermission(context, permission.ACCESS_FINE_LOCATION);
@@ -246,8 +220,7 @@
 
         /**
          * Sets the {@link Action} that will be displayed in the header of the template, or
-         * {@code null}
-         * to not display an action.
+         * {@code null} to not display an action.
          *
          * <h4>Requirements</h4>
          *
@@ -268,8 +241,7 @@
 
         /**
          * Sets the {@link CharSequence} to show as the template's title, or {@code null} to not
-         * display
-         * a title.
+         * display a title.
          */
         @NonNull
         public Builder setTitle(@Nullable CharSequence title) {
@@ -279,32 +251,27 @@
 
         /**
          * Sets an {@link ItemList} to show in a list view along with the map, or {@code null} to
-         * not
-         * display a list.
+         * not display a list.
          *
          * <p>To show a marker corresponding to a point of interest represented by a row, set the
-         * {@link
-         * Place} instance via {@link Row.Builder#setMetadata}. The host will display the {@link
-         * PlaceMarker} in both the map and the list view as the row becomes visible.
+         * {@link Place} instance via {@link Row.Builder#setMetadata}. The host will display the
+         * {@link PlaceMarker} in both the map and the list view as the row becomes visible.
          *
          * <h4>Requirements</h4>
          *
          * This template allows up to 6 {@link Row}s in the {@link ItemList}. The host will
-         * ignore any
-         * items over that limit. The list itself cannot be selectable as set via {@link
-         * ItemList.Builder#setSelectable}. Each {@link Row} can add up to 2 lines of texts via
-         * {@link
-         * Row.Builder#addText} and cannot contain a {@link Toggle}.
+         * ignore any items over that limit. The list itself cannot be selectable as set via {@link
+         * ItemList.Builder#setOnSelectedListener}. Each {@link Row} can add up to 2 lines of texts
+         * via {@link Row.Builder#addText} and cannot contain a {@link Toggle}.
          *
          * <p>Images of type {@link Row#IMAGE_TYPE_LARGE} are not allowed in this template.
          *
          * <p>Rows are not allowed to have both and an image and a place marker.
          *
          * <p>All non-browsable rows must have a {@link DistanceSpan} attached to either its
-         * title or
-         * texts to indicate the distance of the point of interest from the current location. A
-         * row is
-         * browsable when it's configured like so with {@link Row.Builder#setBrowsable(boolean)}.
+         * title or texts to indicate the distance of the point of interest from the current
+         * location. A row is browsable when it's configured like so with
+         * {@link Row.Builder#setBrowsable(boolean)}.
          *
          * @throws IllegalArgumentException if {@code itemList} does not meet the template's
          *                                  requirements.
@@ -339,8 +306,7 @@
          *
          * 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.
+         * {@link Action.Builder#setTitle}. Otherwise, only {@link Action}s with icons are allowed.
          *
          * @throws IllegalArgumentException if {@code actionStrip} does not meet the template's
          *                                  requirements.
@@ -359,13 +325,11 @@
          * <p>The anchor marker is displayed differently from other markers by the host.
          *
          * <p>If not {@code null}, an anchor marker will be shown at the specified {@link LatLng}
-         * on the
-         * map. The camera will adapt to always have the anchor marker visible within its viewport,
-         * along with other places' markers from {@link Row} that are currently visible in the
-         * {@link
-         * Pane}. This can be used to provide a reference point on the map (e.g. the center of a
-         * search
-         * region) as the user pages through the {@link Pane}'s markers, for example.
+         * on the map. The camera will adapt to always have the anchor marker visible within its
+         * viewport, along with other places' markers from {@link Row} that are currently visible
+         * in the {@link Pane}. This can be used to provide a reference point on the map (e.g.
+         * the center of a search region) as the user pages through the {@link Pane}'s markers,
+         * for example.
          */
         @NonNull
         public Builder setAnchor(@Nullable Place anchor) {
@@ -381,8 +345,7 @@
          * Either a header {@link Action} or title must be set on the template.
          *
          * @throws IllegalArgumentException if the template is in a loading state but the list is
-         *                                  set,
-         *                                  or vice versa.
+         *                                  set, or vice versa.
          * @throws IllegalStateException    if the template does not have either a title or header
          *                                  {@link Action} set.
          */
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 8e0f6a9..1099af0 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,8 +29,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.car.app.host.model.OnClickListenerWrapper;
-import androidx.car.app.host.model.OnClickListenerWrapperImpl;
 import androidx.car.app.model.constraints.CarIconConstraints;
 
 import java.lang.annotation.Retention;
@@ -45,20 +43,6 @@
  */
 public class Row implements Item {
     /**
-     * Represents flags that control some attributes of the row.
-     *
-     * @hide
-     */
-    // TODO(shiufai): investigate how to expose IntDefs if needed.
-    @RestrictTo(LIBRARY)
-    @IntDef(
-            value = {ROW_FLAG_NONE, ROW_FLAG_SHOW_DIVIDERS, ROW_FLAG_SECTION_HEADER},
-            flag = true)
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RowFlags {
-    }
-
-    /**
      * The type of images supported within rows.
      *
      * @hide
@@ -71,26 +55,6 @@
     }
 
     /**
-     * No flags applied to the row.
-     */
-    public static final int ROW_FLAG_NONE = (1 << 0);
-
-    /**
-     * Whether to show dividers around the row.
-     */
-    public static final int ROW_FLAG_SHOW_DIVIDERS = (1 << 1);
-
-    /**
-     * Whether the row is a section header.
-     *
-     * <p>Sections are used to group rows in the UI, for example, by showing them all within a block
-     * of the same background color.
-     *
-     * <p>A section header is a string of text above the section with a title for it.
-     */
-    public static final int ROW_FLAG_SECTION_HEADER = (1 << 2);
-
-    /**
      * Represents a small image to be displayed in the row.
      *
      * <p>If necessary, small images will be scaled down to fit within a 36 x 36 dp bounding box,
@@ -136,9 +100,6 @@
     @Keep
     private final Metadata mMetadata;
     @Keep
-    @RowFlags
-    private final int mFlags;
-    @Keep
     private final boolean mIsBrowsable;
     @Keep
     @RowImageType
@@ -211,14 +172,6 @@
     }
 
     /**
-     * Returns the flags for the row.
-     */
-    @RowFlags
-    public int getFlags() {
-        return mFlags;
-    }
-
-    /**
      * Rows your boat.
      *
      * <p>Example usage:
@@ -259,7 +212,6 @@
                 mToggle,
                 mOnClickListener == null,
                 mMetadata,
-                mFlags,
                 mIsBrowsable,
                 mRowImageType);
     }
@@ -281,7 +233,6 @@
                 && Objects.equals(mToggle, otherRow.mToggle)
                 && Objects.equals(mOnClickListener == null, otherRow.mOnClickListener == null)
                 && Objects.equals(mMetadata, otherRow.mMetadata)
-                && mFlags == otherRow.mFlags
                 && mIsBrowsable == otherRow.mIsBrowsable
                 && mRowImageType == otherRow.mRowImageType;
     }
@@ -294,7 +245,6 @@
         mOnClickListener = builder.mOnClickListener;
         mMetadata = builder.mMetadata;
         mIsBrowsable = builder.mIsBrowsable;
-        mFlags = builder.mFlags;
         mRowImageType = builder.mRowImageType;
     }
 
@@ -307,7 +257,6 @@
         mOnClickListener = null;
         mMetadata = EMPTY_METADATA;
         mIsBrowsable = false;
-        mFlags = ROW_FLAG_NONE;
         mRowImageType = IMAGE_TYPE_SMALL;
     }
 
@@ -324,8 +273,6 @@
         private OnClickListenerWrapper mOnClickListener;
         private Metadata mMetadata = EMPTY_METADATA;
         private boolean mIsBrowsable;
-        @RowFlags
-        private int mFlags = ROW_FLAG_NONE;
         @RowImageType
         private int mRowImageType = IMAGE_TYPE_SMALL;
 
@@ -346,18 +293,6 @@
         }
 
         /**
-         * Sets the title of the row, or {@code null} to not show a title.
-         *
-         * @hide
-         */
-        @RestrictTo(LIBRARY)
-        @NonNull
-        public Builder setTitle(@Nullable CarText title) {
-            this.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.
@@ -428,29 +363,6 @@
         }
 
         /**
-         * Clears any rows that may have been added with {@link #addText(CharSequence)} up to this
-         * point.
-         */
-        @NonNull
-        public Builder clearText() {
-            mTexts.clear();
-            return this;
-        }
-
-        /**
-         * Adds a line text of the row below the title.
-         *
-         * @throws NullPointerException if {@code text} is {@code null}.
-         * @hide
-         */
-        @RestrictTo(LIBRARY)
-        @NonNull
-        public Builder addText(@NonNull CarText text) {
-            this.mTexts.add(requireNonNull(text));
-            return this;
-        }
-
-        /**
          * Sets an image to show in the row with the default size {@link #IMAGE_TYPE_SMALL}, or
          * {@code null} to not display an image in the row.
          *
@@ -546,15 +458,6 @@
         }
 
         /**
-         * Sets flags for the row.
-         */
-        @NonNull
-        public Builder setFlags(@RowFlags int flags) {
-            this.mFlags = flags;
-            return this;
-        }
-
-        /**
          * Constructs the {@link Row} defined by this builder.
          *
          * @throws IllegalStateException if the row's title is not set.
diff --git a/car/app/app/src/main/java/androidx/car/app/model/SearchTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/SearchTemplate.java
index 5cca4cd..18976ce 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/SearchTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/SearchTemplate.java
@@ -29,12 +29,11 @@
 import androidx.annotation.Nullable;
 import androidx.car.app.IOnDoneCallback;
 import androidx.car.app.ISearchListener;
+import androidx.car.app.OnDoneCallback;
 import androidx.car.app.Screen;
 import androidx.car.app.SearchListener;
+import androidx.car.app.SearchListenerWrapper;
 import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.host.SearchListenerWrapper;
-import androidx.car.app.utils.Logger;
 import androidx.car.app.utils.RemoteUtils;
 
 import java.util.Collections;
@@ -45,7 +44,7 @@
  *
  * <h4>Template Restrictions</h4>
  *
- * In regards to template refreshes, as described in {@link Screen#getTemplate()}, this template
+ * In regards to template refreshes, as described in {@link Screen#onGetTemplate()}, this template
  * supports any content changes as refreshes. This allows apps to interactively update the search
  * results as the user types without the templates being counted against the quota.
  */
@@ -151,13 +150,6 @@
         return mShowKeyboardByDefault;
     }
 
-    @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        // Always allow updating on search templates. Search results needs to be updated on the fly
-        // as user searches.
-        return oldTemplate.getClass() == this.getClass();
-    }
-
     @NonNull
     @Override
     public String toString() {
@@ -347,8 +339,8 @@
          *
          * This template allows up to 6 {@link Row}s in the {@link ItemList}. The host will
          * ignore any items over that limit. The list itself cannot be selectable as set via {@link
-         * ItemList.Builder#setSelectable}. Each {@link Row} can add up to 2 lines of texts via
-         * {@link Row.Builder#addText} and cannot contain a {@link Toggle}.
+         * ItemList.Builder#setOnSelectedListener}. Each {@link Row} can add up to 2 lines of texts
+         * via {@link Row.Builder#addText} and cannot contain a {@link Toggle}.
          *
          * @throws IllegalArgumentException if {@code itemList} does not meet the template's
          *                                  requirements.
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Template.java b/car/app/app/src/main/java/androidx/car/app/model/Template.java
index 46eb37a..20a8881 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Template.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Template.java
@@ -19,19 +19,9 @@
 import android.content.Context;
 
 import androidx.annotation.NonNull;
-import androidx.car.app.utils.Logger;
 
 /** An interface used to denote a model that can act as a root for a tree of other models. */
 public interface Template {
-
-    /**
-     * Returns {@code true} if this {@link Template} instance is determined to be a refresh compared
-     * to the input template.
-     */
-    default boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        return false;
-    }
-
     /**
      * Checks that the application has the required permissions for this template.
      *
diff --git a/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java b/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java
index a26e151..21031ab 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java
@@ -50,8 +50,7 @@
 
     /**
      * Whether the template wrapper is a refresh of the current template. For internal, host-side
-     * use
-     * only.
+     * use only.
      */
     private boolean mIsRefresh;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Toggle.java b/car/app/app/src/main/java/androidx/car/app/model/Toggle.java
index a58eb9d..f704b7a 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Toggle.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Toggle.java
@@ -27,9 +27,9 @@
 import androidx.annotation.Nullable;
 import androidx.car.app.IOnCheckedChangeListener;
 import androidx.car.app.IOnDoneCallback;
+import androidx.car.app.OnCheckedChangeListenerWrapper;
+import androidx.car.app.OnDoneCallback;
 import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnCheckedChangeListenerWrapper;
-import androidx.car.app.host.OnDoneCallback;
 import androidx.car.app.utils.RemoteUtils;
 
 /** Represents a toggle that can have either a checked or unchecked state. */
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
index 28b8051..ca81759 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
@@ -16,12 +16,9 @@
 
 package androidx.car.app.model.constraints;
 
-import static androidx.car.app.model.Row.ROW_FLAG_SHOW_DIVIDERS;
-
 import androidx.annotation.NonNull;
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.Row;
-import androidx.car.app.model.Row.RowFlags;
 
 /**
  * Encapsulates the constraints to apply when rendering a {@link
@@ -57,7 +54,6 @@
     @NonNull
     public static final RowConstraints ROW_CONSTRAINTS_SIMPLE =
             RowConstraints.builder()
-                    .setFlagOverrides(ROW_FLAG_SHOW_DIVIDERS)
                     .setMaxActionsExclusive(0)
                     .setImageAllowed(true)
                     .setMaxTextLinesPerRow(2)
@@ -75,8 +71,6 @@
     private final boolean mIsImageAllowed;
     private final boolean mIsToggleAllowed;
     private final boolean mIsOnClickListenerAllowed;
-    @RowFlags
-    private final int mFlagOverrides;
     private final CarIconConstraints mCarIconConstraints;
 
     /**
@@ -120,15 +114,6 @@
         return mIsImageAllowed;
     }
 
-    /**
-     * The flags that will be forced on each row, on top of whatever flags come from the client
-     * side.
-     */
-    @RowFlags
-    public int getFlagOverrides() {
-        return mFlagOverrides;
-    }
-
     /** Returns the {@link CarIconConstraints} enforced for the row images. */
     @NonNull
     public CarIconConstraints getCarIconConstraints() {
@@ -173,7 +158,6 @@
         mMaxActionsExclusive = builder.mMaxActionsExclusive;
         mIsToggleAllowed = builder.mIsToggleAllowed;
         mIsImageAllowed = builder.mIsImageAllowed;
-        mFlagOverrides = builder.mFlagOverrides;
         mCarIconConstraints = builder.mCarIconConstraints;
     }
 
@@ -184,8 +168,6 @@
         private int mMaxTextLines = Integer.MAX_VALUE;
         private int mMaxActionsExclusive = Integer.MAX_VALUE;
         private boolean mIsImageAllowed = true;
-        @RowFlags
-        private int mFlagOverrides;
         private CarIconConstraints mCarIconConstraints = CarIconConstraints.UNCONSTRAINED;
 
         /** Sets whether the row can have a click listener associated with it. */
@@ -223,16 +205,6 @@
             return this;
         }
 
-        /**
-         * Sets the flags that will be forced on each row, on top of whatever flags come from
-         * the client side.
-         */
-        @NonNull
-        public Builder setFlagOverrides(@RowFlags int flagOverrides) {
-            this.mFlagOverrides = flagOverrides;
-            return this;
-        }
-
         /** Sets the {@link CarIconConstraints} enforced for the row images. */
         @NonNull
         public Builder setCarIconConstraints(@NonNull CarIconConstraints carIconConstraints) {
@@ -257,7 +229,6 @@
             mMaxActionsExclusive = constraints.mMaxActionsExclusive;
             mIsToggleAllowed = constraints.mIsToggleAllowed;
             mIsImageAllowed = constraints.mIsImageAllowed;
-            mFlagOverrides = constraints.mFlagOverrides;
             mCarIconConstraints = constraints.mCarIconConstraints;
         }
     }
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
index 5d20a94..393e555 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
@@ -189,7 +189,7 @@
      * @throws IllegalArgumentException if the constraints are not met.
      */
     public void validateOrThrow(@NonNull Pane pane) {
-        ActionList actions = pane.getActionList();
+        ActionList actions = pane.getActions();
         if (actions != null && actions.getList().size() > mMaxActions) {
             throw new IllegalArgumentException(
                     "The number of actions on the pane exceeded the supported max of "
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
index fd22326..356c4e5 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
@@ -23,6 +23,8 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.SuppressLint;
+import android.os.Looper;
+import android.util.Log;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
@@ -37,6 +39,9 @@
 import androidx.car.app.serialization.Bundleable;
 import androidx.car.app.serialization.BundlerException;
 import androidx.car.app.utils.RemoteUtils;
+import androidx.core.content.ContextCompat;
+
+import java.util.concurrent.Executor;
 
 /**
  * Manager for communicating navigation related events with the host.
@@ -50,15 +55,20 @@
  * called.
  *
  * <p>Navigation apps must also register a {@link NavigationManagerListener} to handle callbacks to
- * {@link NavigationManagerListener#stopNavigation()} issued by the host.
+ * {@link NavigationManagerListener#onStopNavigation()} issued by the host.
  */
 public class NavigationManager {
-    private final INavigationManager.Stub mNavigationmanager;
+    private static final String TAG = "NavigationManager";
+
+    private final CarContext mCarContext;
+    private final INavigationManager.Stub mNavigationManager;
     private final HostDispatcher mHostDispatcher;
 
     // Guarded by main thread access.
     @Nullable
-    private NavigationManagerListener mListener;
+    private NavigationManagerListener mNavigationManagerListener;
+    @Nullable
+    private Executor mNavigationManagerListenerExecutor;
     private boolean mIsNavigating;
     private boolean mIsAutoDriveEnabled;
 
@@ -71,7 +81,7 @@
      * <p>This method should only be invoked once the navigation app has called {@link
      * #navigationStarted()}, or else the updates will be dropped by the host. Once the app has
      * called {@link #navigationEnded()} or received
-     * {@link NavigationManagerListener#stopNavigation()} it should stop sending updates.
+     * {@link NavigationManagerListener#onStopNavigation()} it should stop sending updates.
      *
      * <p>As the location changes, and in accordance with speed and rounded distance changes, the
      * {@link TravelEstimate}s in the provided {@link Trip} should be rebuilt and this method called
@@ -83,13 +93,15 @@
      * androidx.car.app.navigation.model.Maneuver}s of unknown type may be skipped while on other
      * displays the associated icon may be shown.
      *
+     * @param trip destination, steps, and trip estimates to be sent to the host
+     *
      * @throws HostException            if the call is invoked by an app that is not declared as
-     *                                  a navigation app in the manifest.
+     *                                  a navigation app in the manifest
      * @throws IllegalStateException    if the call occurs when navigation is not started. See
-     *                                  {@link #navigationStarted()} for more info.
+     *                                  {@link #navigationStarted()} for more info
      * @throws IllegalArgumentException if any of the destinations, steps, or trip position is
-     *                                  not well formed.
-     * @throws IllegalStateException    if the current thread is not the main thread.
+     *                                  not well formed
+     * @throws IllegalStateException    if the current thread is not the main thread
      */
     @MainThread
     public void updateTrip(@NonNull Trip trip) {
@@ -115,25 +127,59 @@
     }
 
     /**
-     * Sets a listener to start receiving navigation manager events, or {@code null} to clear the
-     * listener.
+     * Sets a listener to start receiving navigation manager events.
      *
-     * @throws IllegalStateException if {@code null} is passed in while navigation is started. See
+     * Note that the listener will be executed on the main thread using
+     * {@link Looper#getMainLooper()}. To specify the execution thread, use
+     * {@link #setNavigationManagerListener(Executor, NavigationManagerListener)}.
+     *
+     * @param listener the {@link NavigationManagerListener} to use
+     *
+     * @throws IllegalStateException if the current thread is not the main thread
+     */
+    @SuppressLint("ExecutorRegistration")
+    @MainThread
+    public void setNavigationManagerListener(@NonNull NavigationManagerListener listener) {
+        checkMainThread();
+        Executor executor = ContextCompat.getMainExecutor(mCarContext);
+        setNavigationManagerListener(executor, listener);
+    }
+
+    /**
+     * Sets a listener to start receiving navigation manager events.
+     *
+     * @param executor the executor which will be used for invoking the listener
+     * @param listener the {@link NavigationManagerListener} to use
+     *
+     * @throws IllegalStateException if the current thread is not the main thread.
+     */
+    @MainThread
+    public void setNavigationManagerListener(@NonNull /* @CallbackExecutor */ Executor executor,
+            @NonNull NavigationManagerListener listener) {
+        checkMainThread();
+
+        mNavigationManagerListenerExecutor = executor;
+        mNavigationManagerListener = listener;
+        if (mIsAutoDriveEnabled) {
+            onAutoDriveEnabled();
+        }
+    }
+
+    /**
+     * Clears the listener for receiving navigation manager events.
+     *
+     * @throws IllegalStateException if navigation is started. See
      *                               {@link #navigationStarted()} for more info.
      * @throws IllegalStateException if the current thread is not the main thread.
      */
-    // TODO(rampara): Add Executor parameter.
-    @SuppressLint("ExecutorRegistration")
     @MainThread
-    public void setListener(@Nullable NavigationManagerListener listener) {
+    public void clearNavigationManagerListener() {
         checkMainThread();
-        if (mIsNavigating && listener == null) {
+        if (mIsNavigating) {
             throw new IllegalStateException("Removing listener while navigating");
         }
-        this.mListener = listener;
-        if (mIsAutoDriveEnabled && listener != null) {
-            listener.onAutoDriveEnabled();
-        }
+        mNavigationManagerListenerExecutor = null;
+        mNavigationManagerListener = null;
     }
 
     /**
@@ -143,10 +189,11 @@
      * the host. The app must call this method to inform the system that it has started
      * navigation in response to user action.
      *
-     * <p>This function can only called if {@link #setListener(NavigationManagerListener)} has been
+     * <p>This function can only called if
+     * {@link #setNavigationManagerListener(NavigationManagerListener)} has been
      * called with a non-{@code null} value. The listener is required so that a signal to stop
      * navigation from the host can be handled using
-     * {@link NavigationManagerListener#stopNavigation()}.
+     * {@link NavigationManagerListener#onStopNavigation()}.
      *
      * <p>This method is idempotent.
      *
@@ -159,7 +206,7 @@
         if (mIsNavigating) {
             return;
         }
-        if (mListener == null) {
+        if (mNavigationManagerListener == null) {
             throw new IllegalStateException("No listener has been set");
         }
         mIsNavigating = true;
@@ -206,8 +253,9 @@
      */
     @RestrictTo(LIBRARY)
     @NonNull
-    public static NavigationManager create(@NonNull HostDispatcher hostDispatcher) {
-        return new NavigationManager(hostDispatcher);
+    public static NavigationManager create(@NonNull CarContext carContext,
+            @NonNull HostDispatcher hostDispatcher) {
+        return new NavigationManager(carContext, hostDispatcher);
     }
 
     /**
@@ -218,7 +266,7 @@
     @RestrictTo(LIBRARY)
     @NonNull
     public INavigationManager.Stub getIInterface() {
-        return mNavigationmanager;
+        return mNavigationManager;
     }
 
     /**
@@ -228,13 +276,15 @@
      */
     @RestrictTo(LIBRARY)
     @MainThread
-    public void stopNavigation() {
+    public void onStopNavigation() {
         checkMainThread();
         if (!mIsNavigating) {
             return;
         }
         mIsNavigating = false;
-        requireNonNull(mListener).stopNavigation();
+        requireNonNull(mNavigationManagerListenerExecutor).execute(() -> {
+            requireNonNull(mNavigationManagerListener).onStopNavigation();
+        });
     }
 
     /**
@@ -252,22 +302,30 @@
     public void onAutoDriveEnabled() {
         checkMainThread();
         mIsAutoDriveEnabled = true;
-        if (mListener != null) {
-            mListener.onAutoDriveEnabled();
+        if (mNavigationManagerListener != null) {
+            Log.d(TAG, "Executing onAutoDriveEnabled");
+            requireNonNull(mNavigationManagerListenerExecutor).execute(() -> {
+                mNavigationManagerListener.onAutoDriveEnabled();
+            });
+        } else {
+            Log.w(TAG, "NavigationManagerListener not set, skipping onAutoDriveEnabled");
         }
     }
 
     /** @hide */
     @RestrictTo(LIBRARY_GROUP) // Restrict to testing library
     @SuppressWarnings({"methodref.receiver.bound.invalid"})
-    protected NavigationManager(@NonNull HostDispatcher hostDispatcher) {
-        this.mHostDispatcher = requireNonNull(hostDispatcher);
-        mNavigationmanager =
+    protected NavigationManager(@NonNull CarContext carContext,
+            @NonNull HostDispatcher hostDispatcher) {
+        mCarContext = carContext;
+        mHostDispatcher = requireNonNull(hostDispatcher);
+        mNavigationManager =
                 new INavigationManager.Stub() {
                     @Override
-                    public void stopNavigation(IOnDoneCallback callback) {
+                    public void onStopNavigation(IOnDoneCallback callback) {
                         RemoteUtils.dispatchHostCall(
-                                NavigationManager.this::stopNavigation, callback, "stopNavigation");
+                                NavigationManager.this::onStopNavigation, callback,
+                                "onStopNavigation");
                     }
                 };
     }
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManagerListener.java b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManagerListener.java
index ee4c29f..8f0cd2a 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManagerListener.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManagerListener.java
@@ -16,12 +16,10 @@
 
 package androidx.car.app.navigation;
 
-import android.annotation.SuppressLint;
-
 import androidx.car.app.navigation.model.Trip;
 
 /**
- * Listener of events from the {@link NavigationManager}.
+ * Listener for events from the {@link NavigationManager}.
  *
  * @see NavigationManager
  */
@@ -34,11 +32,7 @@
      * guidance, routing-related notifications, and updating trip information via {@link
      * NavigationManager#updateTrip(Trip)}.
      */
-
-    // TODO(rampara): Listener method names must follow the on<Something> style. Consider
-    //  onShouldStopNavigation.
-    @SuppressLint("CallbackMethodName")
-    void stopNavigation();
+    void onStopNavigation();
 
     /**
      * Notifies the app that, from this point onwards, when the user chooses to navigate to a
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
index 8f1e1f0..d937e9c 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
@@ -31,7 +31,7 @@
 import java.util.Objects;
 
 /** Information about a maneuver that the driver will be required to perform. */
-// TODO: Update when Embedded updates or a scheme for auto sync is established.
+// TODO(rasekh): Update when host(s) updates or a scheme for auto sync is established.
 public final class Maneuver {
     /**
      * Possible maneuver types.
@@ -81,14 +81,21 @@
             TYPE_DESTINATION,
             TYPE_DESTINATION_STRAIGHT,
             TYPE_DESTINATION_LEFT,
-            TYPE_DESTINATION_RIGHT
+            TYPE_DESTINATION_RIGHT,
+            TYPE_ROUNDABOUT_ENTER_CW,
+            TYPE_ROUNDABOUT_EXIT_CW,
+            TYPE_ROUNDABOUT_ENTER_CCW,
+            TYPE_ROUNDABOUT_EXIT_CCW,
+            TYPE_FERRY_BOAT_LEFT,
+            TYPE_FERRY_BOAT_RIGHT,
+            TYPE_FERRY_TRAIN_LEFT,
+            TYPE_FERRY_TRAIN_RIGHT,
     })
     @Retention(RetentionPolicy.SOURCE)
     @RestrictTo(LIBRARY)
     public @interface Type {
     }
 
-    // LINT.IfChange(enums)
     /**
      * Maneuver type is unknown, no maneuver information should be displayed.
      *
@@ -286,7 +293,11 @@
      * Roundabout entrance on which the current road ends.
      *
      * <p>For example, this is used to indicate "Enter the roundabout".
+     *
+     * @deprecated Use {@link #TYPE_ROUNDABOUT_ENTER_CW} or {@link #TYPE_ROUNDABOUT_ENTER_CCW}
+     * instead.
      */
+    @Deprecated
     @Type
     public static final int TYPE_ROUNDABOUT_ENTER = 30;
 
@@ -294,7 +305,11 @@
      * Used when leaving a roundabout when the step starts in it.
      *
      * <p>For example, this is used to indicate "Exit the roundabout".
+     *
+     * @deprecated Use {@link #TYPE_ROUNDABOUT_EXIT_CW} or {@link #TYPE_ROUNDABOUT_EXIT_CCW}
+     * instead.
      */
+    @Deprecated
     @Type
     public static final int TYPE_ROUNDABOUT_EXIT = 31;
 
@@ -344,14 +359,18 @@
     public static final int TYPE_STRAIGHT = 36;
 
     /**
-     * Drive towards a boat ferry for vehicles.
+     * Drive towards a boat ferry for vehicles, where the entrance is straight ahead or in an
+     * unknown direction.
      *
      * <p>For example, this is used to indicate "Take the ferry".
      */
     @Type
     public static final int TYPE_FERRY_BOAT = 37;
 
-    /** Drive towards a train ferry for vehicles (e.g. "Take the train"). */
+    /**
+     * Drive towards a train ferry for vehicles (e.g. "Take the train"), where the entrance is
+     * straight ahead or in an unknown direction.
+     */
     @Type
     public static final int TYPE_FERRY_TRAIN = 38;
 
@@ -370,7 +389,70 @@
     /** Arrival to a destination located to the right side of the road. */
     @Type
     public static final int TYPE_DESTINATION_RIGHT = 42;
-    // LINT.ThenChange(:enumTypeChecks)
+
+    /**
+     * Entrance to a clockwise roundabout on which the current road ends.
+     *
+     * <p>For example, this is used to indicate "Enter the roundabout".
+     */
+    @Type
+    public static final int TYPE_ROUNDABOUT_ENTER_CW = 43;
+
+    /**
+     * Used when leaving a clockwise roundabout when the step starts in it.
+     *
+     * <p>For example, this is used to indicate "Exit the roundabout".
+     */
+    @Type
+    public static final int TYPE_ROUNDABOUT_EXIT_CW = 44;
+
+    /**
+     * Entrance to a counter-clockwise roundabout on which the current road ends.
+     *
+     * <p>For example, this is used to indicate "Enter the roundabout".
+     */
+    @Type
+    public static final int TYPE_ROUNDABOUT_ENTER_CCW = 45;
+
+    /**
+     * Used when leaving a counter-clockwise roundabout when the step starts in it.
+     *
+     * <p>For example, this is used to indicate "Exit the roundabout".
+     */
+    @Type
+    public static final int TYPE_ROUNDABOUT_EXIT_CCW = 46;
+
+    /**
+     * Drive towards a boat ferry for vehicles, where the entrance is to the left.
+     *
+     * <p>For example, this is used to indicate "Take the ferry".
+     */
+    @Type
+    public static final int TYPE_FERRY_BOAT_LEFT = 47;
+
+    /**
+     * Drive towards a boat ferry for vehicles, where the entrance is to the right.
+     *
+     * <p>For example, this is used to indicate "Take the ferry".
+     */
+    @Type
+    public static final int TYPE_FERRY_BOAT_RIGHT = 48;
+
+    /**
+     * Drive towards a train ferry for vehicles (e.g. "Take the train"), where the entrance is to
+     * the
+     * left.
+     */
+    @Type
+    public static final int TYPE_FERRY_TRAIN_LEFT = 49;
+
+    /**
+     * Drive towards a train ferry for vehicles (e.g. "Take the train"), where the entrance is to
+     * the
+     * right.
+     */
+    @Type
+    public static final int TYPE_FERRY_TRAIN_RIGHT = 50;
 
     @Keep
     @Type
@@ -388,7 +470,7 @@
      *
      * <p>The type should be chosen to reflect the closest semantic meaning of the maneuver. In some
      * cases, an exact type match is not possible, but choosing a similar or slightly more general
-     * type is preferred. Using {@link #TYPE_UNKNOWN} is allowed, but some headunits will not
+     * type is preferred. Using {@link #TYPE_UNKNOWN} is allowed, but some head units will not
      * display any information in that case.
      *
      * @param type one of the {@code TYPE_*} static constants defined in this class.
@@ -513,7 +595,7 @@
     }
 
     private static boolean isValidType(@Type int type) {
-        return (type >= TYPE_UNKNOWN && type <= TYPE_DESTINATION_RIGHT);
+        return (type >= TYPE_UNKNOWN && type <= TYPE_FERRY_TRAIN_RIGHT);
     }
 
     private static boolean isValidTypeWithExitNumber(@Type int type) {
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
index 22e6df5..b463033 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
@@ -33,7 +33,6 @@
 import androidx.car.app.model.ActionStrip;
 import androidx.car.app.model.CarColor;
 import androidx.car.app.model.Template;
-import androidx.car.app.utils.Logger;
 
 import java.util.Objects;
 
@@ -60,7 +59,7 @@
  *
  * <h4>Template Restrictions</h4>
  *
- * In regard to template refreshes, as described in {@link Screen#getTemplate()}, this template
+ * In regard to template refreshes, as described in {@link Screen#onGetTemplate()}, this template
  * supports any content changes as refreshes. This allows apps to interactively update the
  * turn-by-turn instructions without the templates being counted against the template quota.
  *
@@ -118,14 +117,6 @@
         return requireNonNull(mActionStrip);
     }
 
-    @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        requireNonNull(oldTemplate);
-
-        // Always allow updating on navigation templates.
-        return oldTemplate.getClass() == this.getClass();
-    }
-
     @NonNull
     @Override
     public String toString() {
@@ -217,10 +208,19 @@
         /**
          * Sets the {@link TravelEstimate} to the final destination, or {@code null} to not show any
          * travel estimate information.
+         *
+         * @throws IllegalArgumentException if the {@link TravelEstimate}'s remaining time is
+         *                                  less than zero.
          */
         @NonNull
         public Builder setDestinationTravelEstimate(
                 @Nullable TravelEstimate destinationTravelEstimate) {
+            if (destinationTravelEstimate != null
+                    && destinationTravelEstimate.getRemainingTimeSeconds() < 0) {
+                throw new IllegalArgumentException(
+                        "The destination travel estimate's remaining time must be greater or "
+                                + "equal to zero");
+            }
             this.mDestinationTravelEstimate = destinationTravelEstimate;
             return this;
         }
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
index 51a304d..3a267185 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
@@ -20,8 +20,6 @@
 import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_SIMPLE;
 import static androidx.car.app.model.constraints.RowListConstraints.ROW_LIST_CONSTRAINTS_SIMPLE;
 
-import static java.util.Objects.requireNonNull;
-
 import android.content.Context;
 
 import androidx.annotation.Keep;
@@ -42,7 +40,6 @@
 import androidx.car.app.model.Row;
 import androidx.car.app.model.Template;
 import androidx.car.app.model.Toggle;
-import androidx.car.app.utils.Logger;
 
 import java.util.Collections;
 import java.util.List;
@@ -56,12 +53,12 @@
  *
  * <h4>Template Restrictions</h4>
  *
- * In regards to template refreshes, as described in {@link Screen#getTemplate()}, this template is
- * considered a refresh of a previous one if:
+ * In regards to template refreshes, as described in {@link Screen#onGetTemplate()}, this template
+ * is considered a refresh of a previous one if:
  *
  * <ul>
  *   <li>The template title has not changed, and
- *   <li>The previous template is in a loading state (see {@link Builder#setIsLoading}, or the
+ *   <li>The previous template is in a loading state (see {@link Builder#setLoading}, or the
  *       number of rows and the string contents (title, texts, not counting spans) of each row
  *       between the previous and new {@link ItemList}s have not changed.
  * </ul>
@@ -116,29 +113,6 @@
     }
 
     @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        requireNonNull(oldTemplate);
-        if (oldTemplate.getClass() != this.getClass()) {
-            return false;
-        }
-
-        PlaceListNavigationTemplate old = (PlaceListNavigationTemplate) oldTemplate;
-        if (!Objects.equals(old.getTitle(), getTitle())) {
-            return false;
-        }
-
-        if (old.mIsLoading) {
-            // Transition from a previous loading state is allowed.
-            return true;
-        } else if (mIsLoading) {
-            // Transition to a loading state is disallowed.
-            return false;
-        }
-
-        return requireNonNull(mItemList).isRefresh(old.getItemList(), logger);
-    }
-
-    @Override
     public void checkPermissions(@NonNull Context context) {
         CarAppPermission.checkHasLibraryPermission(context, CarAppPermission.NAVIGATION_TEMPLATES);
     }
@@ -217,10 +191,8 @@
          * once the data is ready. If set to {@code false}, the UI shows the {@link ItemList}
          * contents added via {@link #setItemList}.
          */
-        // TODO(rampara): Consider renaming to setLoading()
-        @SuppressWarnings("MissingGetterMatchingBuilder")
         @NonNull
-        public Builder setIsLoading(boolean isLoading) {
+        public Builder setLoading(boolean isLoading) {
             this.mIsLoading = isLoading;
             return this;
         }
@@ -259,8 +231,8 @@
          *
          * This template allows up to 6 {@link Row}s in the {@link ItemList}. The host will
          * ignore any items over that limit. The list itself cannot be selectable as set via {@link
-         * ItemList.Builder#setSelectable}. Each {@link Row} can add up to 2 lines of texts via
-         * {@link Row.Builder#addText} and cannot contain a {@link Toggle}.
+         * ItemList.Builder#setOnSelectedListener}. Each {@link Row} can add up to 2 lines of texts
+         * via {@link Row.Builder#addText} and cannot contain a {@link Toggle}.
          *
          * <p>Images of type {@link Row#IMAGE_TYPE_LARGE} are not allowed in this template.
          *
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
index aae7e95..9c0160b 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
@@ -40,7 +40,6 @@
 import androidx.car.app.model.Row;
 import androidx.car.app.model.Template;
 import androidx.car.app.model.Toggle;
-import androidx.car.app.utils.Logger;
 
 import java.util.Collections;
 import java.util.Objects;
@@ -49,7 +48,7 @@
  * A template that supports showing a list of routes alongside a custom drawn map.
  *
  * <p>The list must have its {@link
- * androidx.car.app.model.ItemList.OnSelectedListener} set, and the template
+ * ItemList.OnSelectedListener} set, and the template
  * must have its navigate action set (see {@link Builder#setNavigateAction}). These are used in
  * conjunction to inform the app that:
  *
@@ -64,12 +63,12 @@
  *
  * <h4>Template Restrictions</h4>
  *
- * In regards to template refreshes, as described in {@link Screen#getTemplate()}, this template is
- * considered a refresh of a previous one if:
+ * In regards to template refreshes, as described in {@link Screen#onGetTemplate()}, this template
+ * is considered a refresh of a previous one if:
  *
  * <ul>
  *   <li>The template title has not changed, and
- *   <li>The previous template is in a loading state (see {@link Builder#setIsLoading}, or the
+ *   <li>The previous template is in a loading state (see {@link Builder#setLoading}, or the
  *       number of rows and the string contents (title, texts, not counting spans) of each row
  *       between the previous and new {@link ItemList}s have not changed.
  * </ul>
@@ -135,28 +134,6 @@
     }
 
     @Override
-    public boolean isRefresh(@NonNull Template oldTemplate, @NonNull Logger logger) {
-        if (oldTemplate.getClass() != this.getClass()) {
-            return false;
-        }
-
-        RoutePreviewNavigationTemplate old = (RoutePreviewNavigationTemplate) oldTemplate;
-        if (!Objects.equals(old.getTitle(), getTitle())) {
-            return false;
-        }
-
-        if (old.mIsLoading) {
-            // Transition from a previous loading state is allowed.
-            return true;
-        } else if (mIsLoading) {
-            // Transition to a loading state is disallowed.
-            return false;
-        }
-
-        return requireNonNull(mItemList).isRefresh(old.getItemList(), logger);
-    }
-
-    @Override
     public void checkPermissions(@NonNull Context context) {
         CarAppPermission.checkHasLibraryPermission(context, CarAppPermission.NAVIGATION_TEMPLATES);
     }
@@ -241,10 +218,8 @@
          * once the data is ready. If set to {@code false}, the UI shows the {@link ItemList}
          * contents added via {@link #setItemList}.
          */
-        // TODO(rampara): Consider renaming to setLoading()
-        @SuppressWarnings("MissingGetterMatchingBuilder")
         @NonNull
-        public Builder setIsLoading(boolean isLoading) {
+        public Builder setLoading(boolean isLoading) {
             this.mIsLoading = isLoading;
             return this;
         }
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java
index 1de529f..c5c2e67 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java
@@ -214,10 +214,8 @@
          *
          * @see #build
          */
-        // TODO(rampara): Consider renaming to setLoading()
-        @SuppressWarnings("MissingGetterMatchingBuilder")
         @NonNull
-        public Builder setIsLoading(boolean isLoading) {
+        public Builder setLoading(boolean isLoading) {
             this.mIsLoading = isLoading;
             return this;
         }
@@ -228,7 +226,7 @@
          * <h4>Requirements</h4>
          *
          * The {@link RoutingInfo} can be in a loading state by passing {@code true} to {@link
-         * #setIsLoading(boolean)}, in which case no other fields may be set. Otherwise, the current
+         * #setLoading(boolean)}, in which case no other fields may be set. Otherwise, the current
          * step and distance must be set. If the lane information is set with {@link
          * Step.Builder#addLane(Lane)}, then the lane image must also be set with {@link
          * Step.Builder#setLanesImage(CarIcon)}.
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
index cda7135..8efd3c4 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
@@ -39,6 +39,9 @@
  */
 @SuppressWarnings("MissingSummary")
 public final class TravelEstimate {
+    /** A value used to represent an unknown remaining amount of time. */
+    public static final long REMAINING_TIME_UNKNOWN = -1L;
+
     @Keep
     @Nullable
     private final Distance mRemainingDistance;
@@ -59,7 +62,7 @@
      * @param remainingDistance        The estimated remaining {@link Distance} until arriving at
      *                                 the destination.
      * @param remainingTimeSeconds     The estimated time remaining until arriving at the
-     *                                 destination, in seconds.
+     *                                 destination, in seconds, or {@link #REMAINING_TIME_UNKNOWN}.
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided
      *                                 for the destination.
      * @throws IllegalArgumentException if {@code remainingTimeSeconds} is a negative value.
@@ -71,7 +74,8 @@
             @NonNull Distance remainingDistance,
             long remainingTimeSeconds,
             @NonNull DateTimeWithZone arrivalTimeAtDestination) {
-        return builder(remainingDistance, remainingTimeSeconds, arrivalTimeAtDestination).build();
+        return builder(remainingDistance, arrivalTimeAtDestination).setRemainingTimeSeconds(
+                remainingTimeSeconds).build();
     }
 
     /**
@@ -81,7 +85,8 @@
      * @param remainingDistance        The estimated remaining {@link Distance} until arriving at
      *                                 the destination.
      * @param remainingTime            The estimated time remaining until arriving at the
-     *                                 destination.
+     *                                 destination, or {@code Duration.ofSeconds
+     *                                 (REMAINING_TIME_UNKNOWN)}.
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided for
      *                                 the destination.
      * @throws IllegalArgumentException if {@code remainingTime} contains a negative duration.
@@ -96,7 +101,8 @@
             @NonNull Distance remainingDistance,
             @NonNull Duration remainingTime,
             @NonNull ZonedDateTime arrivalTimeAtDestination) {
-        return builder(remainingDistance, remainingTime, arrivalTimeAtDestination).build();
+        return builder(remainingDistance, arrivalTimeAtDestination).setRemainingTime(
+                remainingTime).build();
     }
 
     /**
@@ -104,22 +110,17 @@
      *
      * @param remainingDistance        The estimated remaining {@link Distance} until arriving at
      *                                 the destination.
-     * @param remainingTimeSeconds     The estimated time remaining until arriving at the
-     *                                 destination, in seconds.
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided
      *                                 for the destination.
-     * @throws IllegalArgumentException if {@code remainingTimeSeconds} is a negative value.
-     * @throws NullPointerException     if {@code remainingDistance} is {@code null}
-     * @throws NullPointerException     if {@code arrivalTimeAtDestination} is {@code null}
+     * @throws NullPointerException if {@code remainingDistance} is {@code null}
+     * @throws NullPointerException if {@code arrivalTimeAtDestination} is {@code null}
      */
     @NonNull
     public static Builder builder(
             @NonNull Distance remainingDistance,
-            long remainingTimeSeconds,
             @NonNull DateTimeWithZone arrivalTimeAtDestination) {
         return new Builder(
                 requireNonNull(remainingDistance),
-                remainingTimeSeconds,
                 requireNonNull(arrivalTimeAtDestination));
     }
 
@@ -128,25 +129,19 @@
      *
      * @param remainingDistance        The estimated remaining {@link Distance} until arriving at
      *                                 the destination.
-     * @param remainingTime            The estimated time remaining until arriving at the
-     *                                 destination.
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided for
      *                                 the destination.
-     * @throws IllegalArgumentException if {@code remainingTime} contains a negative duration.
-     * @throws NullPointerException     if {@code remainingDistance} is {@code null}
-     * @throws NullPointerException     if {@code remainingTime} is {@code null}
-     * @throws NullPointerException     if {@code arrivalTimeAtDestination} is {@code null}
+     * @throws NullPointerException if {@code remainingDistance} is {@code null}
+     * @throws NullPointerException if {@code arrivalTimeAtDestination} is {@code null}
      */
     @NonNull
     @RequiresApi(26)
     @SuppressWarnings("AndroidJdkLibsChecker")
     public static Builder builder(
             @NonNull Distance remainingDistance,
-            @NonNull Duration remainingTime,
             @NonNull ZonedDateTime arrivalTimeAtDestination) {
         return new Builder(
                 requireNonNull(remainingDistance),
-                requireNonNull(remainingTime),
                 requireNonNull(arrivalTimeAtDestination));
     }
 
@@ -158,7 +153,7 @@
     // TODO(rampara): Returned time values must be in milliseconds
     @SuppressWarnings("MethodNameUnits")
     public long getRemainingTimeSeconds() {
-        return mRemainingTimeSeconds;
+        return mRemainingTimeSeconds >= 0 ? mRemainingTimeSeconds : REMAINING_TIME_UNKNOWN;
     }
 
     @Nullable
@@ -239,17 +234,15 @@
     /** A builder of {@link TravelEstimate}. */
     public static final class Builder {
         private final Distance mRemainingDistance;
-        private final long mRemainingTimeSeconds;
+        private long mRemainingTimeSeconds = REMAINING_TIME_UNKNOWN;
         private final DateTimeWithZone mArrivalTimeAtDestination;
         private CarColor mRemainingTimeColor = CarColor.DEFAULT;
         private CarColor mRemainingDistanceColor = CarColor.DEFAULT;
 
         private Builder(
                 Distance remainingDistance,
-                long remainingTimeSeconds,
                 DateTimeWithZone arrivalTimeAtDestination) {
             this.mRemainingDistance = requireNonNull(remainingDistance);
-            this.mRemainingTimeSeconds = validateRemainingTime(remainingTimeSeconds);
             this.mArrivalTimeAtDestination = requireNonNull(arrivalTimeAtDestination);
         }
 
@@ -259,14 +252,46 @@
         @SuppressWarnings("AndroidJdkLibsChecker")
         private Builder(
                 Distance remainingDistance,
-                Duration remainingTime,
                 ZonedDateTime arrivalTimeAtDestination) {
             this.mRemainingDistance = remainingDistance;
-            this.mRemainingTimeSeconds = validateRemainingTime(remainingTime.getSeconds());
             this.mArrivalTimeAtDestination = DateTimeWithZone.create(arrivalTimeAtDestination);
         }
 
         /**
+         * Sets the estimated time remaining until arriving at the destination, in seconds.
+         *
+         * <p>If not set, {@link #REMAINING_TIME_UNKNOWN} will be used.
+         *
+         * @throws IllegalArgumentException if {@code remainingTimeSeconds} is a negative value
+         *                                  but not {@link #REMAINING_TIME_UNKNOWN}.
+         */
+        @NonNull
+        public Builder setRemainingTimeSeconds(long remainingTimeSeconds) {
+            this.mRemainingTimeSeconds = validateRemainingTime(remainingTimeSeconds);
+            return this;
+        }
+
+        /**
+         * Sets the estimated time remaining until arriving at the destination.
+         *
+         * <p>If not set, {@link #REMAINING_TIME_UNKNOWN} will be used.
+         *
+         * @throws IllegalArgumentException if {@code remainingTime} is a negative duration
+         *                                  but not {@link #REMAINING_TIME_UNKNOWN}.
+         * @throws NullPointerException     if {@code remainingTime} is {@code null}
+         */
+        @SuppressLint({"MissingGetterMatchingBuilder", "UnsafeNewApiCall"})
+        // TODO(rampara): Move API 26 calls into separate class.
+        @RequiresApi(26)
+        @SuppressWarnings("AndroidJdkLibsChecker")
+        @NonNull
+        public Builder setRemainingTime(@NonNull Duration remainingTime) {
+            requireNonNull(remainingTime);
+            this.mRemainingTimeSeconds = validateRemainingTime(remainingTime.getSeconds());
+            return this;
+        }
+
+        /**
          * Sets the color of the remaining time text.
          *
          * <p>The host may ignore this color depending on the capabilities of the target screen.
@@ -312,9 +337,10 @@
         }
 
         private static long validateRemainingTime(long remainingTimeSeconds) {
-            if (remainingTimeSeconds < 0) {
+            if (remainingTimeSeconds < 0 && remainingTimeSeconds != REMAINING_TIME_UNKNOWN) {
                 throw new IllegalArgumentException(
-                        "Remaining time must be a larger than or equal to zero");
+                        "Remaining time must be a larger than or equal to zero, or set to"
+                                + " REMAINING_TIME_UNKNOWN");
             }
             return remainingTimeSeconds;
         }
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java
index d3f7b5d..15615f2 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java
@@ -267,10 +267,8 @@
          * <p>If set to {@code true}, the UI may show a loading indicator, and adding any steps
          * or step travel estimates will throw an {@link IllegalArgumentException}.
          */
-        // TODO(rampara): Consider renaming to setLoading()
-        @SuppressWarnings("MissingGetterMatchingBuilder")
         @NonNull
-        public Builder setIsLoading(boolean isLoading) {
+        public Builder setLoading(boolean isLoading) {
             this.mIsLoading = isLoading;
             return this;
         }
diff --git a/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java b/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
index 05fc5a1..dc259cc 100644
--- a/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
+++ b/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
@@ -278,7 +278,8 @@
      *
      * @see Builder#setSmallIcon(int)
      */
-    public int getSmallIconResId() {
+    @DrawableRes
+    public int getSmallIcon() {
         return mSmallIconResId;
     }
 
@@ -288,7 +289,7 @@
      * @see Builder#setLargeIcon(Bitmap)
      */
     @Nullable
-    public Bitmap getLargeIconBitmap() {
+    public Bitmap getLargeIcon() {
         return mLargeIconBitmap;
     }
 
@@ -391,8 +392,6 @@
          * <p>This method is equivalent to {@link NotificationCompat.Builder#setSmallIcon(int)} for
          * the car screen.
          */
-        // TODO(rampara): Revisit small icon getter API
-        @SuppressWarnings("MissingGetterMatchingBuilder")
         @NonNull
         public Builder setSmallIcon(int iconResId) {
             this.mSmallIconResId = iconResId;
@@ -412,8 +411,6 @@
          * <p>The large icon will be shown in the notification badge. If the large icon is not
          * set in the {@link CarAppExtender} or the notification, the small icon will show instead.
          */
-        // TODO(rampara): Revisit small icon getter API
-        @SuppressWarnings("MissingGetterMatchingBuilder")
         @NonNull
         public Builder setLargeIcon(@Nullable Bitmap bitmap) {
             this.mLargeIconBitmap = bitmap;
@@ -494,13 +491,6 @@
             return this;
         }
 
-        /** Clears any actions that may have been added with {@link #addAction} up to this point. */
-        @NonNull
-        public Builder clearActions() {
-            this.mActions.clear();
-            return this;
-        }
-
         /**
          * Sets the importance of the notification in the car screen.
          *
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java b/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java
index ba59fcc..09d87e2 100644
--- a/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java
+++ b/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java
@@ -31,10 +31,10 @@
 import androidx.car.app.HostException;
 import androidx.car.app.IOnDoneCallback;
 import androidx.car.app.ISurfaceListener;
+import androidx.car.app.OnDoneCallback;
 import androidx.car.app.SurfaceContainer;
 import androidx.car.app.SurfaceListener;
 import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnDoneCallback;
 import androidx.car.app.serialization.Bundleable;
 import androidx.car.app.serialization.BundlerException;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/ValidationUtils.java b/car/app/app/src/main/java/androidx/car/app/utils/ValidationUtils.java
deleted file mode 100644
index 3e4a283..0000000
--- a/car/app/app/src/main/java/androidx/car/app/utils/ValidationUtils.java
+++ /dev/null
@@ -1,251 +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.car.app.utils;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.car.app.model.CarText;
-import androidx.car.app.model.GridItem;
-import androidx.car.app.model.Row;
-import androidx.car.app.model.Toggle;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Shared util methods for handling different distraction validation logic.
- *
- * @hide
- */
-@RestrictTo(LIBRARY)
-public class ValidationUtils {
-    private static final int INVALID_INDEX = -1;
-
-    /**
-     * Returns {@code true} if the sizes and string contents of the two lists of items are equal,
-     * {@code false} otherwise.
-     */
-    public static boolean itemsHaveSameContent(
-            @NonNull List<Object> itemList1, @NonNull List<Object> itemList2,
-            @NonNull Logger logger) {
-        return itemsHaveSameContent(itemList1, INVALID_INDEX, itemList2, INVALID_INDEX, logger);
-    }
-
-    /**
-     * Returns {@code true} if the sizes and string contents of the two lists of items are equal,
-     * {@code false} otherwise.
-     */
-    public static boolean itemsHaveSameContent(
-            @NonNull List<Object> itemList1,
-            int itemList1SelectedIndex,
-            @NonNull List<Object> itemList2,
-            int itemList2SelectedIndex,
-            @NonNull Logger logger) {
-        if (itemList1.size() != itemList2.size()) {
-            logger.log(
-                    "Different item list sizes. Old: " + itemList1.size() + ". New: "
-                            + itemList2.size());
-            return false;
-        }
-
-        for (int i = 0; i < itemList1.size(); i++) {
-            Object itemObj1 = itemList1.get(i);
-            Object itemObj2 = itemList2.get(i);
-
-            if (itemObj1.getClass() != itemObj2.getClass()) {
-                logger.log(
-                        "Different item types at index "
-                                + i
-                                + ". Old: "
-                                + itemObj1.getClass()
-                                + ". New: "
-                                + itemObj2.getClass());
-                return false;
-            }
-
-            if (itemObj1 instanceof Row) {
-                if (!rowsHaveSameContent((Row) itemObj1, (Row) itemObj2, i, logger)) {
-                    return false;
-                }
-            } else if (itemObj1 instanceof GridItem) {
-                if (!gridItemsHaveSameContent(
-                        (GridItem) itemObj1,
-                        itemList1SelectedIndex == i,
-                        (GridItem) itemObj2,
-                        itemList2SelectedIndex == i,
-                        i,
-                        logger)) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns {@code true} if the string contents of the two rows are equal, {@code false}
-     * otherwise.
-     */
-    private static boolean rowsHaveSameContent(Row row1, Row row2, int index, Logger logger) {
-        // Special case for rows with toggles - if the toggle state has changed, then text updates
-        // are allowed.
-        if (rowToggleStateHasChanged(row1, row2)) {
-            return true;
-        }
-
-        if (!carTextsHasSameString(row1.getTitle(), row2.getTitle())) {
-            logger.log(
-                    "Different row titles at index "
-                            + index
-                            + ". Old: "
-                            + row1.getTitle()
-                            + ". New: "
-                            + row2.getTitle());
-            return false;
-        }
-
-        List<CarText> row1Texts = row1.getTexts();
-        List<CarText> row2Texts = row2.getTexts();
-        if (row1Texts.size() != row2Texts.size()) {
-            logger.log(
-                    "Different text list size at row index "
-                            + index
-                            + ". Old: "
-                            + row1Texts.size()
-                            + ". New: "
-                            + row2Texts.size());
-            return false;
-        }
-
-        for (int j = 0; j < row1Texts.size(); j++) {
-            if (!carTextsHasSameString(row1Texts.get(j), row2Texts.get(j))) {
-                logger.log(
-                        "Different texts at row index "
-                                + index
-                                + ". Old row: "
-                                + row1Texts.get(j)
-                                + ". New row: "
-                                + row2Texts.get(j));
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns {@code true} if string contents and images of the two grid items are equal, {@code
-     * false} otherwise.
-     */
-    private static boolean gridItemsHaveSameContent(
-            GridItem gridItem1,
-            boolean isGridItem1Selected,
-            GridItem gridItem2,
-            boolean isGridItem2Selected,
-            int index,
-            Logger logger) {
-        // Special case for grid items with toggles - if the toggle state has changed, then text
-        // and image updates are allowed.
-        if (gridItemToggleStateHasChanged(gridItem1, gridItem2)) {
-            return true;
-        }
-
-        // Special case for grid items that are selectable - if the selected state has changed,
-        // then text and image updates are allowed.
-        if (isGridItem1Selected != isGridItem2Selected) {
-            return true;
-        }
-
-        if (!carTextsHasSameString(gridItem1.getTitle(), gridItem2.getTitle())) {
-            logger.log(
-                    "Different grid item titles at index "
-                            + index
-                            + ". Old: "
-                            + gridItem1.getTitle()
-                            + ". New: "
-                            + gridItem2.getTitle());
-            return false;
-        }
-
-        if (!carTextsHasSameString(gridItem1.getText(), gridItem2.getText())) {
-            logger.log(
-                    "Different grid item texts at index "
-                            + index
-                            + ". Old: "
-                            + gridItem1.getText()
-                            + ". New: "
-                            + gridItem2.getText());
-            return false;
-        }
-
-        if (!Objects.equals(gridItem1.getImage(), gridItem2.getImage())) {
-            logger.log("Different grid item images at index " + index);
-            return false;
-        }
-
-        if (gridItem1.getImageType() != gridItem2.getImageType()) {
-            logger.log(
-                    "Different grid item image types at index "
-                            + index
-                            + ". Old: "
-                            + gridItem1.getImageType()
-                            + ". New: "
-                            + gridItem2.getImageType());
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns {@code true} if the strings of the two {@link CarText}s are the same, {@code false}
-     * otherwise.
-     *
-     * <p>Spans that are attached to the strings are ignored from the comparison.
-     */
-    private static boolean carTextsHasSameString(
-            @Nullable CarText carText1, @Nullable CarText carText2) {
-        // If both carText1 and carText2 are null, return true. If only one of them is null,
-        // return false.
-        if (carText1 == null || carText2 == null) {
-            return carText1 == null && carText2 == null;
-        }
-
-        return Objects.equals(carText1.getText(), carText2.getText());
-    }
-
-    private static boolean rowToggleStateHasChanged(Row row1, Row row2) {
-        Toggle toggle1 = row1.getToggle();
-        Toggle toggle2 = row2.getToggle();
-
-        return toggle1 != null && toggle2 != null && toggle1.isChecked() != toggle2.isChecked();
-    }
-
-    private static boolean gridItemToggleStateHasChanged(GridItem gridItem1, GridItem gridItem2) {
-        Toggle toggle1 = gridItem1.getToggle();
-        Toggle toggle2 = gridItem2.getToggle();
-
-        return toggle1 != null && toggle2 != null && toggle1.isChecked() != toggle2.isChecked();
-    }
-
-    private ValidationUtils() {
-    }
-}
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/Logger.java b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevel.java
similarity index 63%
copy from car/app/app/src/main/java/androidx/car/app/utils/Logger.java
copy to car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevel.java
index a402482..0da9df6 100644
--- a/car/app/app/src/main/java/androidx/car/app/utils/Logger.java
+++ b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevel.java
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-package androidx.car.app.utils;
+package androidx.car.app.versioning;
 
-import androidx.annotation.NonNull;
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
 
-/**
- * Logger interface to allow the host to log while using the client library.
- */
-// TODO: Allow setting logging severity and including throwables
-public interface Logger {
-    void log(@NonNull String message);
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@IntDef(value = {CarAppApiLevels.LEVEL_1})
+@Retention(RetentionPolicy.SOURCE)
+public @interface CarAppApiLevel {
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
new file mode 100644
index 0000000..21c453b
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
@@ -0,0 +1,73 @@
+/*
+ * 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.car.app.versioning;
+
+import androidx.annotation.RestrictTo;
+import androidx.car.app.CarContext;
+
+/**
+ * API levels supported by this library.
+ * <p>
+ * Each level denotes a set of elements (classes, fields and methods) known to both clients and
+ * hosts.
+ *
+ * @see CarContext#getCarAppApiLevel()
+ */
+public class CarAppApiLevels {
+
+    /**
+     * Initial API level.
+     * <p>
+     * Includes core API services and managers, and templates for parking,
+     * charging, and navigation apps.
+     */
+    @CarAppApiLevel
+    public static final int LEVEL_1 = 1;
+
+    /**
+     * Lowest API level implement to this library
+     */
+    @CarAppApiLevel
+    public static final int OLDEST = LEVEL_1;
+
+    /**
+     * Highest API level implemented by this library.
+     */
+    @CarAppApiLevel
+    public static final int LATEST = LEVEL_1;
+
+    /**
+     * Unknown API level. Used when the API level hasn't been established yet
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @CarAppApiLevel
+    public static final int UNKNOWN = 0;
+
+    /**
+     * @return true if the given integer is a valid {@link CarAppApiLevel}
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static boolean isValid(int carApiLevel) {
+        return carApiLevel >= OLDEST && carApiLevel <= LATEST;
+    }
+
+    private CarAppApiLevels() {}
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/AppInfoTest.java b/car/app/app/src/test/java/androidx/car/app/AppInfoTest.java
new file mode 100644
index 0000000..8b6f797
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/AppInfoTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.car.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import androidx.car.app.versioning.CarAppApiLevels;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class AppInfoTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getApplicationInfo(isNull(), anyInt()))
+                .thenReturn(mApplicationInfo);
+    }
+
+    @Test
+    public void create_minApiLevel_defaultsToCurrent() {
+        mApplicationInfo.metaData = null;
+        AppInfo appInfo = AppInfo.create(mContext);
+        assertThat(appInfo.getMinCarAppApiLevel()).isEqualTo(CarAppApiLevels.LATEST);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void create_minApiLevel_cannotBeLowerThanOldest() {
+        int minApiLevel = CarAppApiLevels.OLDEST - 1;
+        mApplicationInfo.metaData = new Bundle();
+        mApplicationInfo.metaData.putInt(AppInfo.MIN_API_LEVEL_MANIFEST_KEY, minApiLevel);
+        AppInfo.create(mContext);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void create_minApiLevel_cannotBeHigherThanLatest() {
+        int minApiLevel = CarAppApiLevels.LATEST + 1;
+        mApplicationInfo.metaData = new Bundle();
+        mApplicationInfo.metaData.putInt(AppInfo.MIN_API_LEVEL_MANIFEST_KEY, minApiLevel);
+        AppInfo.create(mContext);
+    }
+
+    @Test
+    public void retrieveMinApiLevel_isReadFromManifest() {
+        int minApiLevel = 123;
+        mApplicationInfo.metaData = new Bundle();
+        mApplicationInfo.metaData.putInt(AppInfo.MIN_API_LEVEL_MANIFEST_KEY, minApiLevel);
+        assertThat(AppInfo.retrieveMinCarAppApiLevel(mContext)).isEqualTo(minApiLevel);
+    }
+}
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/AppManagerTest.java b/car/app/app/src/test/java/androidx/car/app/AppManagerTest.java
similarity index 94%
rename from car/app/app/src/androidTest/java/androidx/car/app/AppManagerTest.java
rename to car/app/app/src/test/java/androidx/car/app/AppManagerTest.java
index 7b1f8bc..57e4c35 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/AppManagerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/AppManagerTest.java
@@ -30,20 +30,19 @@
 import androidx.annotation.Nullable;
 import androidx.car.app.model.Template;
 import androidx.car.app.testing.TestCarContext;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link AppManager}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public final class AppManagerTest {
     @Mock
     private ICarHost mMockCarHost;
@@ -58,7 +57,6 @@
     private AppManager mAppManager;
 
     @Before
-    @UiThreadTest
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
 
@@ -92,7 +90,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void getTemplate_serializationFails_throwsIllegalStateException()
             throws RemoteException {
         mTestCarContext
@@ -100,7 +97,7 @@
                 .push(new Screen(mTestCarContext) {
                     @NonNull
                     @Override
-                    public Template getTemplate() {
+                    public Template onGetTemplate() {
                         return new Template() {
                         };
                     }
@@ -113,7 +110,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void invalidate_forwardsRequestToHost() throws RemoteException {
         mAppManager.invalidate();
 
@@ -121,7 +117,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void invalidate_hostThrowsRemoteException_throwsHostException() throws
             RemoteException {
         doThrow(new RemoteException()).when(mMockAppHost).invalidate();
@@ -130,7 +125,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void invalidate_hostThrowsRuntimeException_throwsHostException() throws
             RemoteException {
         doThrow(new IllegalStateException()).when(mMockAppHost).invalidate();
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/CarAppServiceTest.java b/car/app/app/src/test/java/androidx/car/app/CarAppServiceTest.java
similarity index 75%
rename from car/app/app/src/androidTest/java/androidx/car/app/CarAppServiceTest.java
rename to car/app/app/src/test/java/androidx/car/app/CarAppServiceTest.java
index 98ee04a..c644b17 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/CarAppServiceTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/CarAppServiceTest.java
@@ -35,25 +35,27 @@
 import androidx.car.app.serialization.BundlerException;
 import androidx.car.app.testing.CarAppServiceController;
 import androidx.car.app.testing.TestCarContext;
+import androidx.car.app.versioning.CarAppApiLevels;
 import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.Lifecycle;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.Deque;
 import java.util.Locale;
 
 /** Tests for {@link CarAppService}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public final class CarAppServiceTest {
     @Mock
     ICarHost mMockCarHost;
@@ -68,12 +70,13 @@
                     .build();
 
     private CarAppService mCarAppService;
-
+    private CarAppServiceController mCarAppServiceController;
     private Intent mIntentSet;
     private boolean mHasCarAppFinished;
+    @Captor
+    ArgumentCaptor<Bundleable> mBundleableArgumentCaptor;
 
     @Before
-    @UiThreadTest
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
@@ -89,7 +92,7 @@
                         return new Screen(getCarContext()) {
                             @Override
                             @NonNull
-                            public Template getTemplate() {
+                            public Template onGetTemplate() {
                                 return mTemplate;
                             }
                         };
@@ -106,12 +109,11 @@
                     }
                 };
 
-        CarAppServiceController.of(mCarContext, mCarAppService);
+        mCarAppServiceController = CarAppServiceController.of(mCarContext, mCarAppService);
         mCarAppService.onCreate();
     }
 
     @Test
-    @UiThreadTest
     public void onAppCreate_createsFirstScreen() throws RemoteException {
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
         carApp.onAppCreate(mMockCarHost, null, new Configuration(), mock(IOnDoneCallback.class));
@@ -126,7 +128,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onAppCreate_withIntent_callsWithOnCreateScreenWithIntent() throws
             RemoteException {
         IOnDoneCallback callback = mock(IOnDoneCallback.class);
@@ -139,7 +140,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onAppCreate_alreadyPreviouslyCreated_callsOnNewIntent() throws RemoteException {
         IOnDoneCallback callback = mock(IOnDoneCallback.class);
 
@@ -157,7 +157,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onAppCreate_updatesTheConfiguration() throws RemoteException {
         Configuration configuration = new Configuration();
         configuration.setToDefaults();
@@ -171,7 +170,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onNewIntent_callsOnNewIntentWithIntent() throws RemoteException {
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
         Intent intent = new Intent("Foo");
@@ -193,7 +191,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onConfigurationChanged_updatesTheConfiguration() throws RemoteException {
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
         carApp.onAppCreate(mMockCarHost, null, new Configuration(), mock(IOnDoneCallback.class));
@@ -209,17 +206,77 @@
     }
 
     @Test
+    public void getAppInfo() throws RemoteException, BundlerException {
+        AppInfo appInfo = new AppInfo(3, 4, "foo");
+        mCarAppServiceController.setAppInfo(appInfo);
+        ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
+        IOnDoneCallback callback = mock(IOnDoneCallback.class);
+
+        carApp.getAppInfo(callback);
+
+        verify(callback).onSuccess(mBundleableArgumentCaptor.capture());
+        AppInfo receivedAppInfo = (AppInfo) mBundleableArgumentCaptor.getValue().get();
+        assertThat(receivedAppInfo.getMinCarAppApiLevel())
+                .isEqualTo(appInfo.getMinCarAppApiLevel());
+        assertThat(receivedAppInfo.getLatestCarAppApiLevel())
+                .isEqualTo(appInfo.getLatestCarAppApiLevel());
+        assertThat(receivedAppInfo.getLibraryVersion()).isEqualTo(appInfo.getLibraryVersion());
+    }
+
+    @Test
     public void onHandshakeCompleted_updatesHostInfo() throws RemoteException, BundlerException {
         String hostPackageName = "com.google.projection.gearhead";
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
-        HandshakeInfo handshakeInfo = new HandshakeInfo(hostPackageName);
-        carApp.onHandshakeCompleted(Bundleable.create(handshakeInfo), mock(IOnDoneCallback
-                .class));
+        HandshakeInfo handshakeInfo = new HandshakeInfo(hostPackageName, CarAppApiLevels.LEVEL_1);
+
+        carApp.onHandshakeCompleted(Bundleable.create(handshakeInfo), mock(IOnDoneCallback.class));
+
         assertThat(mCarAppService.getHostInfo().getPackageName()).isEqualTo(hostPackageName);
     }
 
     @Test
-    @UiThreadTest
+    public void onHandshakeCompleted_updatesCarApiLevel() throws RemoteException, BundlerException {
+        String hostPackageName = "com.google.projection.gearhead";
+        ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
+        int hostApiLevel = CarAppApiLevels.LEVEL_1;
+        HandshakeInfo handshakeInfo = new HandshakeInfo(hostPackageName, hostApiLevel);
+
+        carApp.onHandshakeCompleted(Bundleable.create(handshakeInfo), mock(IOnDoneCallback.class));
+
+        assertThat(mCarAppService.getCarContext().getCarAppApiLevel()).isEqualTo(hostApiLevel);
+    }
+
+    @Test
+    public void onHandshakeCompleted_lowerThanMinApiLevel_throws() throws BundlerException,
+            RemoteException {
+        AppInfo appInfo = new AppInfo(3, 4, "foo");
+        mCarAppServiceController.setAppInfo(appInfo);
+        ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
+
+        HandshakeInfo handshakeInfo = new HandshakeInfo("bar",
+                appInfo.getMinCarAppApiLevel() - 1);
+        IOnDoneCallback callback = mock(IOnDoneCallback.class);
+        carApp.onHandshakeCompleted(Bundleable.create(handshakeInfo), callback);
+
+        verify(callback).onFailure(any());
+    }
+
+    @Test
+    public void onHandshakeCompleted_higherThanCurrentApiLevel_throws() throws BundlerException,
+            RemoteException {
+        AppInfo appInfo = new AppInfo(3, 4, "foo");
+        mCarAppServiceController.setAppInfo(appInfo);
+        ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
+
+        HandshakeInfo handshakeInfo = new HandshakeInfo("bar",
+                appInfo.getLatestCarAppApiLevel() + 1);
+        IOnDoneCallback callback = mock(IOnDoneCallback.class);
+        carApp.onHandshakeCompleted(Bundleable.create(handshakeInfo), callback);
+
+        verify(callback).onFailure(any());
+    }
+
+    @Test
     public void onUnbind_movesLifecycleStateToStopped() throws RemoteException {
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
         carApp.onAppCreate(mMockCarHost, null, new Configuration(), mock(IOnDoneCallback.class));
@@ -233,7 +290,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onUnbind_rebind_callsOnCreateScreen() throws RemoteException {
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
         carApp.onAppCreate(mMockCarHost, null, new Configuration(), mock(IOnDoneCallback.class));
@@ -257,7 +313,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onUnbind_clearsScreenStack() throws RemoteException {
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
         carApp.onAppCreate(mMockCarHost, null, new Configuration(), mock(IOnDoneCallback.class));
@@ -277,7 +332,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void finish() throws RemoteException {
         ICarApp carApp = (ICarApp) mCarAppService.onBind(null);
         carApp.onAppCreate(mMockCarHost, null, new Configuration(), mock(IOnDoneCallback.class));
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/CarContextTest.java b/car/app/app/src/test/java/androidx/car/app/CarContextTest.java
similarity index 73%
rename from car/app/app/src/androidTest/java/androidx/car/app/CarContextTest.java
rename to car/app/app/src/test/java/androidx/car/app/CarContextTest.java
index ba52f42..3db83de 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/CarContextTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/CarContextTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,31 +36,31 @@
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 
+import androidx.activity.OnBackPressedCallback;
 import androidx.annotation.Nullable;
 import androidx.car.app.navigation.NavigationManager;
-import androidx.car.app.test.R;
 import androidx.car.app.testing.TestLifecycleOwner;
 import androidx.lifecycle.Lifecycle.Event;
-import androidx.test.annotation.UiThreadTest;
+import androidx.lifecycle.Lifecycle.State;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.Locale;
 
 /** Tests for {@link CarContext}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class CarContextTest {
-    private static final String APP_SERVICE = "app_manager";
-    private static final String NAVIGATION_SERVICE = "navigation_manager";
-    private static final String SCREEN_MANAGER_SERVICE = "screen_manager";
+    private static final String APP_SERVICE = "app";
+    private static final String NAVIGATION_SERVICE = "navigation";
+    private static final String SCREEN_SERVICE = "screen";
 
     @Mock
     private ICarHost mMockCarHost;
@@ -79,7 +80,6 @@
             new TestLifecycleOwner();
 
     @Before
-    @UiThreadTest
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
         when(mMockCarHost.getHost(CarContext.APP_SERVICE))
@@ -101,7 +101,7 @@
         TestStartCarAppStub startCarAppStub = new TestStartCarAppStub(mMockStartCarApp);
 
         Bundle extras = new Bundle(1);
-        extras.putBinder(CarContext.START_CAR_APP_BINDER_KEY, startCarAppStub.asBinder());
+        extras.putBinder(CarContext.EXTRA_START_CAR_APP_BINDER_KEY, startCarAppStub.asBinder());
         mIntentFromNotification = new Intent().putExtras(extras);
 
         mCarContext = CarContext.create(mLifecycleOwner.mRegistry);
@@ -130,9 +130,9 @@
 
     @Test
     public void getCarService_screenManager() {
-        assertThat(mCarContext.getCarService(CarContext.SCREEN_MANAGER_SERVICE))
+        assertThat(mCarContext.getCarService(CarContext.SCREEN_SERVICE))
                 .isEqualTo(mCarContext.getCarService(ScreenManager.class));
-        assertThat(mCarContext.getCarService(CarContext.SCREEN_MANAGER_SERVICE)).isNotNull();
+        assertThat(mCarContext.getCarService(CarContext.SCREEN_SERVICE)).isNotNull();
     }
 
     @Test
@@ -161,7 +161,7 @@
     @Test
     public void getCarServiceName_screenManager() {
         assertThat(mCarContext.getCarServiceName(ScreenManager.class)).isEqualTo(
-                SCREEN_MANAGER_SERVICE);
+                SCREEN_SERVICE);
     }
 
     @Test
@@ -210,7 +210,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void onConfigurationChanged_updatesTheConfiguration() {
         Configuration configuration = new Configuration();
         configuration.setToDefaults();
@@ -223,14 +222,13 @@
     }
 
     @Test
-    @UiThreadTest
     public void onConfigurationChanged_loadsCorrectNewResource() {
         Configuration ldpiConfig = new Configuration(mCarContext.getResources().getConfiguration());
         ldpiConfig.densityDpi = 120;
 
         mCarContext.onCarConfigurationChanged(ldpiConfig);
 
-        Drawable ldpiDrawable = mCarContext.getDrawable(R.drawable.banana);
+        Drawable ldpiDrawable = TestUtils.getTestDrawable(mCarContext, "banana");
         assertThat(ldpiDrawable.getIntrinsicHeight()).isEqualTo(48);
 
         Configuration mdpiConfig = new Configuration(mCarContext.getResources().getConfiguration());
@@ -238,7 +236,7 @@
 
         mCarContext.onCarConfigurationChanged(mdpiConfig);
 
-        Drawable mdpiDrawable = mCarContext.getDrawable(R.drawable.banana);
+        Drawable mdpiDrawable = TestUtils.getTestDrawable(mCarContext, "banana");
         assertThat(mdpiDrawable.getIntrinsicHeight()).isEqualTo(64);
 
         Configuration hdpiConfig = new Configuration(mCarContext.getResources().getConfiguration());
@@ -246,12 +244,11 @@
 
         mCarContext.onCarConfigurationChanged(hdpiConfig);
 
-        Drawable hdpiDrawable = mCarContext.getDrawable(R.drawable.banana);
+        Drawable hdpiDrawable = TestUtils.getTestDrawable(mCarContext, "banana");
         assertThat(hdpiDrawable.getIntrinsicHeight()).isEqualTo(96);
     }
 
     @Test
-    @UiThreadTest
     // TODO(rampara): Investigate removing usage of deprecated updateConfiguration API
     @SuppressWarnings("deprecation")
     public void changingApplicationContextConfiguration_doesNotChangeTheCarContextConfiguration() {
@@ -260,7 +257,7 @@
 
         mCarContext.onCarConfigurationChanged(ldpiConfig);
 
-        Drawable ldpiDrawable = mCarContext.getDrawable(R.drawable.banana);
+        Drawable ldpiDrawable = TestUtils.getTestDrawable(mCarContext, "banana");
         assertThat(ldpiDrawable.getIntrinsicHeight()).isEqualTo(48);
 
         Configuration mdpiConfig = new Configuration(mCarContext.getResources().getConfiguration());
@@ -272,15 +269,15 @@
                 .updateConfiguration(mdpiConfig,
                         applicationContext.getResources().getDisplayMetrics());
 
-        Drawable carContextDrawable = mCarContext.getDrawable(R.drawable.banana);
+        Drawable carContextDrawable = TestUtils.getTestDrawable(mCarContext, "banana");
         assertThat(carContextDrawable.getIntrinsicHeight()).isEqualTo(48);
 
-        Drawable applicationContextDrawable = applicationContext.getDrawable(R.drawable.banana);
+        Drawable applicationContextDrawable = TestUtils.getTestDrawable(applicationContext,
+                "banana");
         assertThat(applicationContextDrawable.getIntrinsicHeight()).isEqualTo(64);
     }
 
     @Test
-    @UiThreadTest
     // TODO(rampara): Investigate removing usage of deprecated updateConfiguration API
     @SuppressWarnings("deprecation")
     public void changingApplicationContextDisplayMetrics_doesNotChangeCarContextDisplayMetrics() {
@@ -289,7 +286,7 @@
 
         mCarContext.onCarConfigurationChanged(ldpiConfig);
 
-        Drawable ldpiDrawable = mCarContext.getDrawable(R.drawable.banana);
+        Drawable ldpiDrawable = TestUtils.getTestDrawable(mCarContext, "banana");
         assertThat(ldpiDrawable.getIntrinsicHeight()).isEqualTo(48);
 
         Configuration mdpiConfig = new Configuration(mCarContext.getResources().getConfiguration());
@@ -311,9 +308,8 @@
         applicationContext.getResources().updateConfiguration(mdpiConfig, newDisplayMetrics);
 
         assertThat(applicationContext.getResources().getConfiguration()).isEqualTo(mdpiConfig);
-        // TODO(rampara): Investigate why DisplayMetrics isn't updated
-//        assertThat(applicationContext.getResources().getDisplayMetrics()).isEqualTo(
-//                newDisplayMetrics);
+        assertThat(applicationContext.getResources().getDisplayMetrics()).isEqualTo(
+                newDisplayMetrics);
 
         assertThat(mCarContext.getResources().getConfiguration()).isNotEqualTo(mdpiConfig);
         assertThat(mCarContext.getResources().getDisplayMetrics()).isNotEqualTo(newDisplayMetrics);
@@ -352,60 +348,58 @@
         verify(mMockScreen2).dispatchLifecycleEvent(Event.ON_DESTROY);
     }
 
-    // TODO(rampara): Investigate how to mock final methods
-//    @Test
-//    public void
-//    getOnBackPressedDispatcher_withAListenerThatIsStarted_callsTheListenerAndDoesNotPop() {
-//        mCarContext.getCarService(ScreenManager.class).push(mScreen1);
-//        mCarContext.getCarService(ScreenManager.class).push(mScreen2);
-//
-//        OnBackPressedCallback callback = mock(OnBackPressedCallback.class);
-//        when(callback.isEnabled()).thenReturn(true);
-//        mLifecycleOwner.mRegistry.setCurrentState(State.STARTED);
-//
-//        mCarContext.getOnBackPressedDispatcher().addCallback(mLifecycleOwner, callback);
-//        mCarContext.getOnBackPressedDispatcher().onBackPressed();
-//
-//        verify(callback).handleOnBackPressed();
-//        verify(mMockScreen1, never()).dispatchLifecycleEvent(Event.ON_DESTROY);
-//        verify(mMockScreen2, never()).dispatchLifecycleEvent(Event.ON_DESTROY);
-//    }
-//
-//    @Test
-//    public void getOnBackPressedDispatcher_withAListenerThatIsNotStarted_popsAScreen() {
-//        mCarContext.getCarService(ScreenManager.class).push(mScreen1);
-//        mCarContext.getCarService(ScreenManager.class).push(mScreen2);
-//
-//        OnBackPressedCallback callback = mock(OnBackPressedCallback.class);
-//        when(callback.isEnabled()).thenReturn(true);
-//        mLifecycleOwner.mRegistry.setCurrentState(State.CREATED);
-//
-//        mCarContext.getOnBackPressedDispatcher().addCallback(mLifecycleOwner, callback);
-//        mCarContext.getOnBackPressedDispatcher().onBackPressed();
-//
-//        verify(callback, never()).handleOnBackPressed();
-//        verify(mMockScreen1, never()).dispatchLifecycleEvent(Lifecycle.Event.ON_DESTROY);
-//        verify(mMockScreen2).dispatchLifecycleEvent(Lifecycle.Event.ON_DESTROY);
-//    }
-//
-//    @Test
-//    public void getOnBackPressedDispatcher_callsDefaultListenerWheneverTheAddedOneIsNotSTARTED() {
-//        mCarContext.getCarService(ScreenManager.class).push(mScreen1);
-//        mCarContext.getCarService(ScreenManager.class).push(mScreen2);
-//
-//        OnBackPressedCallback callback = mock(OnBackPressedCallback.class);
-//        when(callback.isEnabled()).thenReturn(true);
-//        mLifecycleOwner.mRegistry.setCurrentState(State.CREATED);
-//
-//        mCarContext.getOnBackPressedDispatcher().addCallback(mLifecycleOwner, callback);
-//        mCarContext.getOnBackPressedDispatcher().onBackPressed();
-//
-//        verify(callback, never()).handleOnBackPressed();
-//        verify(mMockScreen2).dispatchLifecycleEvent(Event.ON_DESTROY);
-//
-//        mLifecycleOwner.mRegistry.setCurrentState(State.STARTED);
-//        mCarContext.getOnBackPressedDispatcher().onBackPressed();
-//
-//        verify(callback).handleOnBackPressed();
-//    }
+    @Test
+    public void getOnBackPressedDispatcher_withListenerThatIsStarted_callsListenerAndDoesNotPop() {
+        mCarContext.getCarService(ScreenManager.class).push(mScreen1);
+        mCarContext.getCarService(ScreenManager.class).push(mScreen2);
+
+        OnBackPressedCallback callback = mock(OnBackPressedCallback.class);
+        when(callback.isEnabled()).thenReturn(true);
+        mLifecycleOwner.mRegistry.setCurrentState(State.STARTED);
+
+        mCarContext.getOnBackPressedDispatcher().addCallback(mLifecycleOwner, callback);
+        mCarContext.getOnBackPressedDispatcher().onBackPressed();
+
+        verify(callback).handleOnBackPressed();
+        verify(mMockScreen1, never()).dispatchLifecycleEvent(Event.ON_DESTROY);
+        verify(mMockScreen2, never()).dispatchLifecycleEvent(Event.ON_DESTROY);
+    }
+
+    @Test
+    public void getOnBackPressedDispatcher_withAListenerThatIsNotStarted_popsAScreen() {
+        mCarContext.getCarService(ScreenManager.class).push(mScreen1);
+        mCarContext.getCarService(ScreenManager.class).push(mScreen2);
+
+        OnBackPressedCallback callback = mock(OnBackPressedCallback.class);
+        when(callback.isEnabled()).thenReturn(true);
+        mLifecycleOwner.mRegistry.setCurrentState(State.CREATED);
+
+        mCarContext.getOnBackPressedDispatcher().addCallback(mLifecycleOwner, callback);
+        mCarContext.getOnBackPressedDispatcher().onBackPressed();
+
+        verify(callback, never()).handleOnBackPressed();
+        verify(mMockScreen1, never()).dispatchLifecycleEvent(Event.ON_DESTROY);
+        verify(mMockScreen2).dispatchLifecycleEvent(Event.ON_DESTROY);
+    }
+
+    @Test
+    public void getOnBackPressedDispatcher_callsDefaultListenerWheneverTheAddedOneIsNotSTARTED() {
+        mCarContext.getCarService(ScreenManager.class).push(mScreen1);
+        mCarContext.getCarService(ScreenManager.class).push(mScreen2);
+
+        OnBackPressedCallback callback = mock(OnBackPressedCallback.class);
+        when(callback.isEnabled()).thenReturn(true);
+        mLifecycleOwner.mRegistry.setCurrentState(State.CREATED);
+
+        mCarContext.getOnBackPressedDispatcher().addCallback(mLifecycleOwner, callback);
+        mCarContext.getOnBackPressedDispatcher().onBackPressed();
+
+        verify(callback, never()).handleOnBackPressed();
+        verify(mMockScreen2).dispatchLifecycleEvent(Event.ON_DESTROY);
+
+        mLifecycleOwner.mRegistry.setCurrentState(State.STARTED);
+        mCarContext.getOnBackPressedDispatcher().onBackPressed();
+
+        verify(callback).handleOnBackPressed();
+    }
 }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/CarToastTest.java b/car/app/app/src/test/java/androidx/car/app/CarToastTest.java
similarity index 92%
rename from car/app/app/src/androidTest/java/androidx/car/app/CarToastTest.java
rename to car/app/app/src/test/java/androidx/car/app/CarToastTest.java
index 42f0e70..3ea0e15 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/CarToastTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/CarToastTest.java
@@ -22,23 +22,21 @@
 
 import androidx.car.app.testing.TestAppManager;
 import androidx.car.app.testing.TestCarContext;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link CarToast}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public final class CarToastTest {
     private TestCarContext mCarContext;
 
     @Before
-    @UiThreadTest
     public void setUp() {
         mCarContext = TestCarContext.createCarContext(ApplicationProvider.getApplicationContext());
         mCarContext.reset();
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/HandshakeInfoTest.java b/car/app/app/src/test/java/androidx/car/app/HandshakeInfoTest.java
similarity index 77%
rename from car/app/app/src/androidTest/java/androidx/car/app/HandshakeInfoTest.java
rename to car/app/app/src/test/java/androidx/car/app/HandshakeInfoTest.java
index 40b39c8..a367e7a 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/HandshakeInfoTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/HandshakeInfoTest.java
@@ -18,20 +18,21 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public final class HandshakeInfoTest {
 
     @Test
     public void construct_handshakeInfo() {
         String hostPackageName = "com.google.host";
-        HandshakeInfo handshakeInfo = new HandshakeInfo(hostPackageName);
+        int hostApiLevel = 123;
+        HandshakeInfo handshakeInfo = new HandshakeInfo(hostPackageName, hostApiLevel);
         assertThat(handshakeInfo.getHostPackageName()).isEqualTo(hostPackageName);
+        assertThat(handshakeInfo.getHostCarAppApiLevel()).isEqualTo(hostApiLevel);
     }
 }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/HostDispatcherTest.java b/car/app/app/src/test/java/androidx/car/app/HostDispatcherTest.java
similarity index 96%
rename from car/app/app/src/androidTest/java/androidx/car/app/HostDispatcherTest.java
rename to car/app/app/src/test/java/androidx/car/app/HostDispatcherTest.java
index 3e5bc5c..1366265 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/HostDispatcherTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/HostDispatcherTest.java
@@ -29,19 +29,19 @@
 import androidx.annotation.Nullable;
 import androidx.car.app.navigation.INavigationHost;
 import androidx.car.app.serialization.Bundleable;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link HostDispatcher}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class HostDispatcherTest {
 
     @Mock
@@ -55,7 +55,6 @@
     private HostDispatcher mHostDispatcher = new HostDispatcher();
 
     @Before
-    @UiThreadTest
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
 
@@ -149,7 +148,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void getHost_afterResetting_getsFromCarHost() throws RemoteException {
         assertThat(mHostDispatcher.getHost(CarContext.APP_SERVICE)).isEqualTo(mAppHost);
 
@@ -211,7 +209,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void getHost_afterReset_throwsHostException() {
         mHostDispatcher.resetHosts();
 
@@ -219,7 +216,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void getHost_notBound_throwsHostException() {
         mHostDispatcher = new HostDispatcher();
 
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/ScreenManagerTest.java b/car/app/app/src/test/java/androidx/car/app/ScreenManagerTest.java
similarity index 97%
rename from car/app/app/src/androidTest/java/androidx/car/app/ScreenManagerTest.java
rename to car/app/app/src/test/java/androidx/car/app/ScreenManagerTest.java
index 383164a..4e55c08 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/ScreenManagerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/ScreenManagerTest.java
@@ -34,10 +34,7 @@
 import androidx.car.app.testing.TestLifecycleOwner;
 import androidx.lifecycle.Lifecycle.Event;
 import androidx.lifecycle.Lifecycle.State;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -45,10 +42,12 @@
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link ScreenManager}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public final class ScreenManagerTest {
 
     private TestScreen mScreen1;
@@ -71,7 +70,6 @@
     private ScreenManager mScreenManager;
 
     @Before
-    @UiThreadTest
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
@@ -131,7 +129,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void push_stackHadScreen_addsToStack_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -158,7 +155,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void push_screenAlreadyInStack_movesToTop_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -194,7 +190,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void push_screenAlreadyTopOfStack_noFurtherLifecycleCalls() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -223,7 +218,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void pushForResult_addsToStack_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager,
@@ -251,7 +245,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void pushForResult_screenSetsResult_firstScreenGetsCalled() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
 
@@ -269,7 +262,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void pushForResult_screenSetsResult_firstScreenGetsCalled_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager,
@@ -347,7 +339,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void pop_removes_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -445,7 +436,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void popTo_pops_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -486,7 +476,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void popTo_markerScreenNotInStack_popsToRoot_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockScreen3, mMockAppManager);
@@ -537,7 +526,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void popTo_multipleToPop_pops_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockScreen3, mMockAppManager);
@@ -591,7 +579,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void popTo_notARootTarget_popsExpectedScreens_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockScreen3, mMockAppManager);
@@ -675,7 +662,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void popToRoot_pops_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -713,7 +699,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void popToRoot_multipleToPop_pops_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockScreen3, mMockAppManager);
@@ -764,7 +749,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void popTo_multipleToPop_withResult_pops_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder =
@@ -858,7 +842,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void remove_wasTop_removes_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -897,7 +880,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void remove_wasNotTop_removes_callsProperLifecycleMethods() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockAppManager);
@@ -930,7 +912,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void remove_notInStack_noop() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         InOrder inOrder = inOrder(mMockScreen1, mMockScreen2, mMockScreen3, mMockAppManager);
@@ -960,7 +941,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void getTopTemplate_returnsTemplateFromTopOfStack() {
         Template template =
                 PlaceListMapTemplate.builder()
@@ -972,8 +952,8 @@
                         .setTitle("Title2")
                         .setItemList(ItemList.builder().build())
                         .build();
-        when(mMockScreen1.getTemplate()).thenReturn(template);
-        when(mMockScreen2.getTemplate()).thenReturn(template2);
+        when(mMockScreen1.onGetTemplate()).thenReturn(template);
+        when(mMockScreen2.onGetTemplate()).thenReturn(template2);
 
         mScreenManager.push(mScreen1);
         mScreenManager.push(mScreen2);
@@ -993,7 +973,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onStart_expectedLifecycleChange() {
         mScreenManager.push(mScreen1);
         reset(mMockScreen1);
@@ -1009,7 +988,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onResume_expectedLifecycleChange() {
         mScreenManager.push(mScreen1);
         reset(mMockScreen1);
@@ -1025,7 +1003,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onPause_expectedLifecycleChange() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         mScreenManager.push(mScreen1);
@@ -1042,7 +1019,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onStop_expectedLifecycleChange() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         mScreenManager.push(mScreen1);
@@ -1067,7 +1043,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onDestroy_screenStopped_onlyDestroys() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         mScreenManager.push(mScreen1);
@@ -1083,7 +1058,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onDestroy_screenPaused_stopsAndDestroys() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         mScreenManager.push(mScreen1);
@@ -1100,7 +1074,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onDestroy_screenResumed_pausesStopsAndDestroys() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         mScreenManager.push(mScreen1);
@@ -1117,7 +1090,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void dispatchAppLifecycleEvent_onDestroy_pausesStopsAndDestroysTop_destroysOthers() {
         mLifecycleOwner.mRegistry.handleLifecycleEvent(Event.ON_RESUME);
         mScreenManager.push(mScreen1);
@@ -1140,7 +1112,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void pop_screenReuseLastTemplateId() {
         Template template =
                 PlaceListMapTemplate.builder()
@@ -1152,9 +1123,9 @@
                         .setTitle("Title")
                         .setItemList(ItemList.builder().build())
                         .build();
-        when(mMockScreen1.getTemplate()).thenReturn(template);
-        when(mMockScreen2.getTemplate()).thenReturn(template);
-        when(mMockScreen3.getTemplate()).thenReturn(template);
+        when(mMockScreen1.onGetTemplate()).thenReturn(template);
+        when(mMockScreen2.onGetTemplate()).thenReturn(template);
+        when(mMockScreen3.onGetTemplate()).thenReturn(template);
 
         mScreenManager.push(mScreen1);
         TemplateWrapper wrapper1 = mScreenManager.getTopTemplate();
@@ -1167,8 +1138,8 @@
         mScreenManager.push(mScreen3);
         mScreenManager.pop();
 
-        when(mMockScreen1.getTemplate()).thenReturn(template2);
-        when(mMockScreen2.getTemplate()).thenReturn(template2);
+        when(mMockScreen1.onGetTemplate()).thenReturn(template2);
+        when(mMockScreen2.onGetTemplate()).thenReturn(template2);
 
         // Popping should reuse the last template's id of screen2.
         wrapper2 = mScreenManager.getTopTemplate();
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/ScreenTest.java b/car/app/app/src/test/java/androidx/car/app/ScreenTest.java
similarity index 91%
rename from car/app/app/src/androidTest/java/androidx/car/app/ScreenTest.java
rename to car/app/app/src/test/java/androidx/car/app/ScreenTest.java
index 7b3902e..98e0f42 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/ScreenTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/ScreenTest.java
@@ -31,20 +31,19 @@
 import androidx.car.app.testing.TestScreenManager;
 import androidx.lifecycle.Lifecycle.Event;
 import androidx.lifecycle.Lifecycle.State;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link Screen}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public final class ScreenTest {
     private TestCarContext mCarContext;
 
@@ -54,7 +53,6 @@
     private Screen mScreen;
 
     @Before
-    @UiThreadTest
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mCarContext =
@@ -68,7 +66,7 @@
                         new Screen(mCarContext) {
                             @Override
                             @NonNull
-                            public Template getTemplate() {
+                            public Template onGetTemplate() {
                                 return new Template() {
                                 };
                             }
@@ -77,7 +75,7 @@
         mScreen = new Screen(mCarContext) {
             @Override
             @NonNull
-            public Template getTemplate() {
+            public Template onGetTemplate() {
                 return PlaceListMapTemplate.builder().setItemList(
                         ItemList.builder().build()).build();
             }
@@ -87,7 +85,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void finish_removesSelf() {
         mScreen.finish();
         assertThat(mCarContext.getCarService(TestScreenManager.class).getScreensRemoved())
@@ -95,14 +92,12 @@
     }
 
     @Test
-    @UiThreadTest
     public void onCreate_expectedLifecycleChange() {
         mScreen.dispatchLifecycleEvent(Event.ON_CREATE);
         assertThat(mScreen.getLifecycle().getCurrentState()).isEqualTo(State.CREATED);
     }
 
     @Test
-    @UiThreadTest
     public void onStart_expectedLifecycleChange() {
         mScreen.dispatchLifecycleEvent(Event.ON_START);
         assertThat(mScreen.getLifecycle().getCurrentState()).isEqualTo(State.STARTED);
@@ -115,28 +110,24 @@
     }
 
     @Test
-    @UiThreadTest
     public void onPause_expectedLifecycleChange() {
         mScreen.dispatchLifecycleEvent(Event.ON_PAUSE);
         assertThat(mScreen.getLifecycle().getCurrentState()).isEqualTo(State.STARTED);
     }
 
     @Test
-    @UiThreadTest
     public void onStop_expectedLifecycleChange() {
         mScreen.dispatchLifecycleEvent(Event.ON_STOP);
         assertThat(mScreen.getLifecycle().getCurrentState()).isEqualTo(State.CREATED);
     }
 
     @Test
-    @UiThreadTest
     public void onDestroy_expectedLifecycleChange() {
         mScreen.dispatchLifecycleEvent(Event.ON_DESTROY);
         assertThat(mScreen.getLifecycle().getCurrentState()).isEqualTo(State.DESTROYED);
     }
 
     @Test
-    @UiThreadTest
     public void setResult_callsThemockOnScreenResultCallback() {
         mScreen.setOnResultCallback(mMockOnScreenResultCallback);
 
@@ -151,7 +142,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void finish_screenIsDestroyed() {
         mScreen.finish();
         assertThat(mScreen.getLifecycle().getCurrentState()).isEqualTo(State.DESTROYED);
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/TestData.java b/car/app/app/src/test/java/androidx/car/app/TestData.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/TestData.java
rename to car/app/app/src/test/java/androidx/car/app/TestData.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/TestScreen.java b/car/app/app/src/test/java/androidx/car/app/TestScreen.java
similarity index 93%
rename from car/app/app/src/androidTest/java/androidx/car/app/TestScreen.java
rename to car/app/app/src/test/java/androidx/car/app/TestScreen.java
index 77f6720..e0d2cbe 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/TestScreen.java
+++ b/car/app/app/src/test/java/androidx/car/app/TestScreen.java
@@ -31,8 +31,8 @@
 
     @NonNull
     @Override
-    public Template getTemplate() {
-        return mScreenForMocking.getTemplate();
+    public Template onGetTemplate() {
+        return mScreenForMocking.onGetTemplate();
     }
 
     @Override
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/TestUtils.java b/car/app/app/src/test/java/androidx/car/app/TestUtils.java
similarity index 89%
rename from car/app/app/src/androidTest/java/androidx/car/app/TestUtils.java
rename to car/app/app/src/test/java/androidx/car/app/TestUtils.java
index 5378ae2..7c6f6cc 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/TestUtils.java
+++ b/car/app/app/src/test/java/androidx/car/app/TestUtils.java
@@ -27,8 +27,10 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
 import android.text.SpannableString;
 
+import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -42,6 +44,7 @@
 import androidx.car.app.model.Pane;
 import androidx.car.app.model.Row;
 import androidx.car.app.model.SectionedItemList;
+import androidx.core.graphics.drawable.IconCompat;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -59,6 +62,20 @@
     private TestUtils() {
     }
 
+    public static Drawable getTestDrawable(Context context, String drawable) {
+        return context.getDrawable(getTestDrawableResId(context, drawable));
+    }
+
+    public static CarIcon getTestCarIcon(Context context, String drawable) {
+        return CarIcon.of(IconCompat.createWithResource(context,
+                TestUtils.getTestDrawableResId(context, drawable)));
+    }
+
+    @DrawableRes
+    public static int getTestDrawableResId(Context context, String drawable) {
+        return context.getResources().getIdentifier(drawable, "drawable", context.getPackageName());
+    }
+
     /**
      * Returns a {@link DateTimeWithZone} instance from a date string and a time zone id.
      *
@@ -113,7 +130,7 @@
         }
 
         if (isSelectable) {
-            builder.setSelectable(index -> {
+            builder.setOnSelectedListener(index -> {
             });
         }
 
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ActionStripTest.java b/car/app/app/src/test/java/androidx/car/app/model/ActionStripTest.java
similarity index 86%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/ActionStripTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/ActionStripTest.java
index e288a77..778db2d 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ActionStripTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ActionStripTest.java
@@ -20,15 +20,14 @@
 
 import static org.junit.Assert.assertThrows;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link ActionStrip}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ActionStripTest {
     @Test
     public void createEmpty_throws() {
@@ -75,20 +74,6 @@
     }
 
     @Test
-    public void clearActions() {
-        Action action1 = Action.BACK;
-        Action action2 = Action.APP_ICON;
-        ActionStrip list =
-                ActionStrip.builder()
-                        .addAction(action1)
-                        .addAction(action2)
-                        .clearActions()
-                        .addAction(action2)
-                        .build();
-        assertThat(list.getActions()).hasSize(1);
-    }
-
-    @Test
     public void getActionOfType() {
         Action action1 = Action.BACK;
         Action action2 = Action.builder().setTitle("Test").setOnClickListener(() -> {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ActionTest.java b/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
similarity index 81%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/ActionTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
index 8b48fe2..1535bf9 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ActionTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
@@ -23,16 +23,14 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.net.Uri;
 
 import androidx.car.app.IOnDoneCallback;
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.test.R;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.TestUtils;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -40,10 +38,12 @@
 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 Action}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ActionTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -79,23 +79,20 @@
         assertThrows(
                 IllegalArgumentException.class,
                 () -> Action.builder()
-                                .setTitle("foo")
-                                .setOnClickListener(onClickListener)
-                                .setBackgroundColor(CarColor.createCustom(0xdead, 0xbeef))
-                                .build());
+                        .setTitle("foo")
+                        .setOnClickListener(onClickListener)
+                        .setBackgroundColor(CarColor.createCustom(0xdead, 0xbeef))
+                        .build());
     }
 
     @Test
     public void create_noTitleDefault() {
         OnClickListener onClickListener = mock(OnClickListener.class);
         Action action = Action.builder()
-                        .setIcon(
-                                CarIcon.of(
-                                        IconCompat.createWithResource(
-                                                ApplicationProvider.getApplicationContext(),
-                                                R.drawable.ic_test_1)))
-                        .setOnClickListener(onClickListener)
-                        .build();
+                .setIcon(TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                        "ic_test_1"))
+                .setOnClickListener(onClickListener)
+                .build();
         assertThat(action.getTitle()).isNull();
     }
 
@@ -116,19 +113,18 @@
     }
 
     @Test
-    @UiThreadTest
     public void createInstance() {
         OnClickListener onClickListener = mock(OnClickListener.class);
-        IconCompat icon =
-                IconCompat.createWithResource(
-                        ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1);
+        Context context = ApplicationProvider.getApplicationContext();
+        IconCompat icon = IconCompat.createWithResource(
+                context, TestUtils.getTestDrawableResId(context, "ic_test_1"));
         String title = "foo";
         Action action = Action.builder()
-                        .setTitle(title)
-                        .setIcon(CarIcon.of(icon))
-                        .setBackgroundColor(CarColor.BLUE)
-                        .setOnClickListener(onClickListener)
-                        .build();
+                .setTitle(title)
+                .setIcon(CarIcon.of(icon))
+                .setBackgroundColor(CarColor.BLUE)
+                .setOnClickListener(onClickListener)
+                .build();
         assertThat(icon).isEqualTo(action.getIcon().getIcon());
         assertThat(CarText.create(title)).isEqualTo(action.getTitle());
         assertThat(CarColor.BLUE).isEqualTo(action.getBackgroundColor());
@@ -199,9 +195,9 @@
         CarIcon icon2 = CarIcon.APP_ICON;
 
         Action action1 = Action.builder().setOnClickListener(() -> {
-                }).setTitle(title).setIcon(icon1).build();
+        }).setTitle(title).setIcon(icon1).build();
         Action action2 = Action.builder().setOnClickListener(() -> {
-                }).setTitle(title).setIcon(icon2).build();
+        }).setTitle(title).setIcon(icon2).build();
 
         assertThat(action2).isNotEqualTo(action1);
     }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/CarIconSpanTest.java b/car/app/app/src/test/java/androidx/car/app/model/CarIconSpanTest.java
similarity index 84%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/CarIconSpanTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/CarIconSpanTest.java
index 2439237..0237996 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/CarIconSpanTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/CarIconSpanTest.java
@@ -21,29 +21,30 @@
 import static org.junit.Assert.assertThrows;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.net.Uri;
 
-import androidx.car.app.test.R;
+import androidx.car.app.TestUtils;
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link CarIconSpan}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class CarIconSpanTest {
     private IconCompat mIcon;
 
     @Before
     public void setup() {
-        mIcon =
-                IconCompat.createWithResource(
-                        ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1);
+        Context context = ApplicationProvider.getApplicationContext();
+        mIcon = IconCompat.createWithResource(
+                context, TestUtils.getTestDrawableResId(context, "ic_test_1"));
     }
 
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/CarIconTest.java b/car/app/app/src/test/java/androidx/car/app/model/CarIconTest.java
similarity index 87%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/CarIconTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/CarIconTest.java
index 1152c7e..e6a33ab 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/CarIconTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/CarIconTest.java
@@ -28,32 +28,33 @@
 import static org.junit.Assert.assertThrows;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.net.Uri;
 
-import androidx.car.app.test.R;
+import androidx.car.app.TestUtils;
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.io.File;
 
 /** Tests for {@link CarIcon}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class CarIconTest {
     private IconCompat mIcon;
 
     @Before
     public void setup() {
-        mIcon =
-                IconCompat.createWithResource(
-                        ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1);
+        Context context = ApplicationProvider.getApplicationContext();
+        mIcon = IconCompat.createWithResource(
+                context, TestUtils.getTestDrawableResId(context, "ic_test_1"));
     }
 
     @Test
@@ -142,11 +143,10 @@
     public void equals() {
         assertThat(BACK.equals(BACK)).isTrue();
         CarIcon carIcon = CarIcon.of(mIcon);
+        Context context = ApplicationProvider.getApplicationContext();
 
-        assertThat(
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1)))
+        assertThat(CarIcon.of(IconCompat.createWithResource(
+                context, TestUtils.getTestDrawableResId(context, "ic_test_1"))))
                 .isEqualTo(carIcon);
     }
 
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/DateTimeWithZoneTest.java b/car/app/app/src/test/java/androidx/car/app/model/DateTimeWithZoneTest.java
similarity index 85%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/DateTimeWithZoneTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/DateTimeWithZoneTest.java
index 1844d51..43bdbf0 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/DateTimeWithZoneTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/DateTimeWithZoneTest.java
@@ -24,12 +24,10 @@
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.time.Duration;
 import java.time.ZonedDateTime;
@@ -38,8 +36,8 @@
 import java.util.TimeZone;
 
 /** Tests for {@link DateTimeWithZone}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class DateTimeWithZoneTest {
     @Test
     @SuppressWarnings("JdkObsolete")
@@ -73,36 +71,29 @@
         // Negative time.
         assertThrows(
                 IllegalArgumentException.class,
-                () -> {
-                    DateTimeWithZone.create(-1, (int) timeZoneOffsetSeconds, zoneShortName);
-                });
+                () -> DateTimeWithZone.create(-1, (int) timeZoneOffsetSeconds, zoneShortName));
 
         // Offset out of range.
         assertThrows(
                 IllegalArgumentException.class,
-                () -> {
-                    DateTimeWithZone.create(timeSinceEpochMillis, 18 * 60 * 60 + 1, zoneShortName);
-                });
+                () -> DateTimeWithZone.create(timeSinceEpochMillis, 18 * 60 * 60 + 1,
+                        zoneShortName));
         assertThrows(
                 IllegalArgumentException.class,
-                () -> {
-                    DateTimeWithZone.create(timeSinceEpochMillis, -18 * 60 * 60 - 1, zoneShortName);
-                });
+                () -> DateTimeWithZone.create(timeSinceEpochMillis, -18 * 60 * 60 - 1,
+                        zoneShortName));
 
         // Null short name.
         assertThrows(
                 NullPointerException.class,
-                () -> {
-                    DateTimeWithZone.create(timeSinceEpochMillis, (int) timeZoneOffsetSeconds,
-                            null);
-                });
+                () -> DateTimeWithZone.create(timeSinceEpochMillis, (int) timeZoneOffsetSeconds,
+                        null));
 
         // Empty short name.
         assertThrows(
                 IllegalArgumentException.class,
-                () -> {
-                    DateTimeWithZone.create(timeSinceEpochMillis, (int) timeZoneOffsetSeconds, "");
-                });
+                () -> DateTimeWithZone.create(timeSinceEpochMillis, (int) timeZoneOffsetSeconds,
+                        ""));
     }
 
     @Test
@@ -131,20 +122,15 @@
         // Negative time.
         assertThrows(
                 IllegalArgumentException.class,
-                () -> {
-                    DateTimeWithZone.create(-1, timeZone);
-                });
+                () -> DateTimeWithZone.create(-1, timeZone));
 
         // Null time zone.
         assertThrows(
                 NullPointerException.class,
-                () -> {
-                    DateTimeWithZone.create(123, null);
-                });
+                () -> DateTimeWithZone.create(123, null));
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 26)
     public void create_withZonedDateTime() {
         ZonedDateTime zonedDateTime = ZonedDateTime.parse("2020-05-14T19:57:00-07:00[US/Pacific]");
         DateTimeWithZone dateTimeWithZone = DateTimeWithZone.create(zonedDateTime);
@@ -153,7 +139,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 26)
     public void create_withZonedDateTime_argumentChecks() {
         // Null date time.
         assertThrows(
@@ -164,7 +149,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 26)
     public void equals() {
         TimeZone timeZone = TimeZone.getTimeZone("US/Pacific");
         long timeSinceEpochMillis = System.currentTimeMillis();
@@ -183,7 +167,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 26)
     public void notEquals_differentTimeSinceEpoch() {
         TimeZone timeZone = TimeZone.getTimeZone("US/Pacific");
         long timeSinceEpochMillis = System.currentTimeMillis();
@@ -203,7 +186,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 26)
     public void notEquals_differentTimeZoneOffsetSeconds() {
         TimeZone timeZone = TimeZone.getTimeZone("US/Pacific");
         long timeSinceEpochMillis = System.currentTimeMillis();
@@ -223,7 +205,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 26)
     public void notEquals_differentTimeZone() {
         TimeZone timeZone = TimeZone.getTimeZone("US/Pacific");
         long timeSinceEpochMillis = System.currentTimeMillis();
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/DistanceSpanTest.java b/car/app/app/src/test/java/androidx/car/app/model/DistanceSpanTest.java
similarity index 90%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/DistanceSpanTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/DistanceSpanTest.java
index a05bed7..704770b 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/DistanceSpanTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/DistanceSpanTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link DistanceSpan}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class DistanceSpanTest {
     private final Distance mDistance =
             Distance.create(/* displayDistance= */ 10, Distance.UNIT_KILOMETERS);
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/DistanceTest.java b/car/app/app/src/test/java/androidx/car/app/model/DistanceTest.java
similarity index 93%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/DistanceTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/DistanceTest.java
index 91c7028..4ebe6fb 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/DistanceTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/DistanceTest.java
@@ -24,15 +24,14 @@
 
 import static org.junit.Assert.assertThrows;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link Distance}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class DistanceTest {
 
     private static final double DISPLAY_DISTANCE = 1.2d;
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/DurationSpanTest.java b/car/app/app/src/test/java/androidx/car/app/model/DurationSpanTest.java
similarity index 88%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/DurationSpanTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/DurationSpanTest.java
index e43f2e1..cce7162 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/DurationSpanTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/DurationSpanTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link DurationSpan}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class DurationSpanTest {
     @Test
     public void constructor() {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ForegroundCarColorSpanTest.java b/car/app/app/src/test/java/androidx/car/app/model/ForegroundCarColorSpanTest.java
similarity index 91%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/ForegroundCarColorSpanTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/ForegroundCarColorSpanTest.java
index 4f320f0..a3b92a1 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ForegroundCarColorSpanTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ForegroundCarColorSpanTest.java
@@ -23,15 +23,14 @@
 
 import static org.junit.Assert.assertThrows;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link CarIconSpan}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ForegroundCarColorSpanTest {
     @Test
     public void constructor() {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/GridItemTest.java b/car/app/app/src/test/java/androidx/car/app/model/GridItemTest.java
similarity index 95%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/GridItemTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/GridItemTest.java
index 5fc726f0..e8e7c41 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/GridItemTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/GridItemTest.java
@@ -27,17 +27,16 @@
 
 import android.os.RemoteException;
 
-import androidx.car.app.host.OnDoneCallback;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.car.app.OnDoneCallback;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link GridItem}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class GridItemTest {
 
     @Test
@@ -128,7 +127,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void clickListener() throws RemoteException {
         OnClickListener onClickListener = mock(OnClickListener.class);
         GridItem gridItem =
diff --git a/car/app/app/src/test/java/androidx/car/app/model/GridTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/GridTemplateTest.java
new file mode 100644
index 0000000..215b685
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/model/GridTemplateTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.car.app.model;
+
+import static androidx.car.app.model.CarIcon.BACK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.car.app.TestUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link GridTemplate}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class GridTemplateTest {
+    @Test
+    public void createInstance_emptyList_notLoading_throws() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> GridTemplate.builder().setTitle("Title").build());
+
+        // Positive case
+        GridTemplate.builder().setTitle("Title").setLoading(true).build();
+    }
+
+    @Test
+    public void createInstance_isLoading_hasList_throws() {
+        assertThrows(
+                IllegalStateException.class,
+                () ->
+                        GridTemplate.builder()
+                                .setTitle("Title")
+                                .setLoading(true)
+                                .setSingleList(TestUtils.getGridItemList(2))
+                                .build());
+    }
+
+    @Test
+    public void createInstance_noHeaderTitleOrAction_throws() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> GridTemplate.builder().setSingleList(TestUtils.getGridItemList(2)).build());
+
+        // Positive cases.
+        GridTemplate.builder().setTitle("Title").setSingleList(
+                TestUtils.getGridItemList(2)).build();
+        GridTemplate.builder()
+                .setHeaderAction(Action.BACK)
+                .setSingleList(TestUtils.getGridItemList(2))
+                .build();
+    }
+
+    @Test
+    public void createInstance_setSingleList() {
+        ItemList list = TestUtils.getGridItemList(2);
+        GridTemplate template = GridTemplate.builder().setTitle("Title").setSingleList(
+                list).build();
+        assertThat(template.getSingleList()).isEqualTo(list);
+    }
+
+    @Test
+    public void createInstance_setHeaderAction_invalidActionThrows() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        GridTemplate.builder()
+                                .setHeaderAction(
+                                        Action.builder().setTitle("Action").setOnClickListener(
+                                                () -> {
+                                                }).build()));
+    }
+
+    @Test
+    public void createInstance_setHeaderAction() {
+        GridTemplate template =
+                GridTemplate.builder()
+                        .setSingleList(TestUtils.getGridItemList(2))
+                        .setHeaderAction(Action.BACK)
+                        .build();
+        assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
+    }
+
+    @Test
+    public void createInstance_setActionStrip() {
+        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
+        GridTemplate template =
+                GridTemplate.builder()
+                        .setSingleList(TestUtils.getGridItemList(2))
+                        .setTitle("Title")
+                        .setActionStrip(actionStrip)
+                        .build();
+        assertThat(template.getActionStrip()).isEqualTo(actionStrip);
+    }
+
+    @Test
+    public void createInstance_setBackground() {
+        GridTemplate template =
+                GridTemplate.builder()
+                        .setTitle("Title")
+                        .setLoading(true)
+                        .setBackgroundImage(BACK)
+                        .build();
+        assertThat(template.getBackgroundImage()).isEqualTo(BACK);
+    }
+
+    @Test
+    public void resetList_clearsSingleList() {
+        GridTemplate.Builder builder =
+                GridTemplate.builder()
+                        .setSingleList(TestUtils.getGridItemList(2))
+                        .setHeaderAction(Action.BACK);
+
+        assertThrows(IllegalStateException.class, () -> builder.clearAllLists().build());
+    }
+
+    @Test
+    public void equals() {
+        ItemList itemList = ItemList.builder().build();
+        String title = "title";
+        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
+
+        GridTemplate template =
+                GridTemplate.builder()
+                        .setSingleList(itemList)
+                        .setHeaderAction(Action.BACK)
+                        .setActionStrip(actionStrip)
+                        .setTitle(title)
+                        .build();
+
+        assertThat(template)
+                .isEqualTo(
+                        GridTemplate.builder()
+                                .setSingleList(itemList)
+                                .setHeaderAction(Action.BACK)
+                                .setActionStrip(actionStrip)
+                                .setTitle(title)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentItemList() {
+        ItemList itemList = ItemList.builder().build();
+
+        GridTemplate template =
+                GridTemplate.builder().setTitle("Title").setSingleList(itemList).build();
+
+        assertThat(template)
+                .isNotEqualTo(
+                        GridTemplate.builder()
+                                .setTitle("Title")
+                                .setSingleList(
+                                        ItemList.builder().addItem(
+                                                GridItem.builder().setImage(BACK).build()).build())
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentHeaderAction() {
+        ItemList itemList = ItemList.builder().build();
+
+        GridTemplate template =
+                GridTemplate.builder().setSingleList(itemList).setHeaderAction(Action.BACK).build();
+
+        assertThat(template)
+                .isNotEqualTo(
+                        GridTemplate.builder()
+                                .setSingleList(itemList)
+                                .setHeaderAction(Action.APP_ICON)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentTitle() {
+        ItemList itemList = ItemList.builder().build();
+        String title = "title";
+
+        GridTemplate template = GridTemplate.builder().setSingleList(itemList).setTitle(
+                title).build();
+
+        assertThat(template)
+                .isNotEqualTo(GridTemplate.builder().setSingleList(itemList).setTitle(
+                        "foo").build());
+    }
+
+    @Test
+    public void notEquals_differentActionStrip() {
+        ItemList itemList = ItemList.builder().build();
+        String title = "title";
+
+        GridTemplate template =
+                GridTemplate.builder()
+                        .setSingleList(itemList)
+                        .setTitle(title)
+                        .setActionStrip(ActionStrip.builder().addAction(Action.BACK).build())
+                        .build();
+
+        assertThat(template)
+                .isNotEqualTo(
+                        GridTemplate.builder()
+                                .setSingleList(itemList)
+                                .setTitle(title)
+                                .setActionStrip(
+                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
+                                .build());
+    }
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/ItemListTest.java b/car/app/app/src/test/java/androidx/car/app/model/ItemListTest.java
new file mode 100644
index 0000000..a928807
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/model/ItemListTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.car.app.model;
+
+import static androidx.car.app.model.CarIcon.BACK;
+import static androidx.car.app.model.ItemList.builder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+
+import androidx.car.app.IOnDoneCallback;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.WrappedRuntimeException;
+import androidx.car.app.model.ItemList.OnItemVisibilityChangedListener;
+import androidx.car.app.model.ItemList.OnSelectedListener;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.Collections;
+
+/** Tests for {@link ItemListTest}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class ItemListTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IOnDoneCallback.Stub mMockOnDoneCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void createEmpty() {
+        ItemList list = builder().build();
+        assertThat(list.getItems()).isEqualTo(Collections.emptyList());
+    }
+
+    @Test
+    public void createRows() {
+        Row row1 = Row.builder().setTitle("Row1").build();
+        Row row2 = Row.builder().setTitle("Row2").build();
+        ItemList list = builder().addItem(row1).addItem(row2).build();
+
+        assertThat(list.getItems()).hasSize(2);
+        assertThat(list.getItems().get(0)).isEqualTo(row1);
+        assertThat(list.getItems().get(1)).isEqualTo(row2);
+    }
+
+    @Test
+    public void createGridItems() {
+        GridItem gridItem1 = GridItem.builder().setImage(BACK).build();
+        GridItem gridItem2 = GridItem.builder().setImage(BACK).build();
+        ItemList list = builder().addItem(gridItem1).addItem(gridItem2).build();
+
+        assertThat(list.getItems()).containsExactly(gridItem1, gridItem2).inOrder();
+    }
+
+    @Test
+    public void setSelectedable_emptyList_throws() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> builder().setOnSelectedListener(selectedIndex -> {
+                }).build());
+    }
+
+    @Test
+    public void setSelectedIndex_greaterThanListSize_throws() {
+        Row row1 = Row.builder().setTitle("Row1").build();
+        assertThrows(
+                IllegalStateException.class,
+                () -> builder()
+                        .addItem(row1)
+                        .setOnSelectedListener(selectedIndex -> {
+                        })
+                        .setSelectedIndex(2)
+                        .build());
+    }
+
+    @Test
+    public void setSelectable() throws RemoteException {
+        OnSelectedListener mockListener = mock(OnSelectedListener.class);
+        ItemList itemList =
+                builder()
+                        .addItem(Row.builder().setTitle("title").build())
+                        .setOnSelectedListener(mockListener)
+                        .build();
+
+        OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
+
+
+        itemList.getOnSelectedListener().onSelected(0, onDoneCallback);
+        verify(mockListener).onSelected(eq(0));
+        verify(onDoneCallback).onSuccess(null);
+    }
+
+    @Test
+    public void setSelectable_disallowOnClickListenerInRows() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> builder()
+                        .addItem(Row.builder().setTitle("foo").setOnClickListener(() -> {
+                        }).build())
+                        .setOnSelectedListener((index) -> {
+                        })
+                        .build());
+
+        // Positive test.
+        builder()
+                .addItem(Row.builder().setTitle("foo").build())
+                .setOnSelectedListener((index) -> {
+                })
+                .build();
+    }
+
+    @Test
+    public void setSelectable_disallowToggleInRow() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> builder()
+                        .addItem(Row.builder().setToggle(Toggle.builder(isChecked -> {
+                        }).build()).build())
+                        .setOnSelectedListener((index) -> {
+                        })
+                        .build());
+    }
+
+    @Test
+    public void setOnItemVisibilityChangeListener_triggerListener() {
+        OnItemVisibilityChangedListener listener = mock(OnItemVisibilityChangedListener.class);
+        ItemList list =
+                builder()
+                        .addItem(Row.builder().setTitle("1").build())
+                        .setOnItemsVisibilityChangedListener(listener)
+                        .build();
+
+        OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
+        list.getOnItemsVisibilityChangedListener().onItemVisibilityChanged(0, 1,
+                onDoneCallback);
+        ArgumentCaptor<Integer> startIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> endIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(listener).onItemVisibilityChanged(startIndexCaptor.capture(),
+                endIndexCaptor.capture());
+        verify(onDoneCallback).onSuccess(null);
+        assertThat(startIndexCaptor.getValue()).isEqualTo(0);
+        assertThat(endIndexCaptor.getValue()).isEqualTo(1);
+    }
+
+    @Test
+    public void setOnItemVisibilityChangeListener_triggerListenerWithFailure() {
+        OnItemVisibilityChangedListener listener = mock(OnItemVisibilityChangedListener.class);
+        ItemList list =
+                builder()
+                        .addItem(Row.builder().setTitle("1").build())
+                        .setOnItemsVisibilityChangedListener(listener)
+                        .build();
+
+        String testExceptionMessage = "Test exception";
+        doThrow(new RuntimeException(testExceptionMessage)).when(listener).onItemVisibilityChanged(
+                0, 1);
+
+        OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
+        try {
+            list.getOnItemsVisibilityChangedListener().onItemVisibilityChanged(0, 1,
+                    onDoneCallback);
+        } catch (WrappedRuntimeException e) {
+            assertThat(e.getMessage()).contains(testExceptionMessage);
+        }
+
+        ArgumentCaptor<Integer> startIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> endIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(listener).onItemVisibilityChanged(startIndexCaptor.capture(),
+                endIndexCaptor.capture());
+        verify(onDoneCallback).onFailure(any());
+        assertThat(startIndexCaptor.getValue()).isEqualTo(0);
+        assertThat(endIndexCaptor.getValue()).isEqualTo(1);
+    }
+
+    @Test
+    public void equals_itemListWithRows() {
+        Row row = Row.builder().setTitle("Title").build();
+        ItemList itemList =
+                builder()
+                        .setOnSelectedListener((index) -> {
+                        })
+                        .setNoItemsMessage("no items")
+                        .setSelectedIndex(0)
+                        .setOnItemsVisibilityChangedListener((start, end) -> {
+                        })
+                        .addItem(row)
+                        .build();
+        assertThat(itemList)
+                .isEqualTo(
+                        builder()
+                                .setOnSelectedListener((index) -> {
+                                })
+                                .setNoItemsMessage("no items")
+                                .setSelectedIndex(0)
+                                .setOnItemsVisibilityChangedListener((start, end) -> {
+                                })
+                                .addItem(row)
+                                .build());
+    }
+
+    @Test
+    public void equals_itemListWithGridItems() {
+        GridItem gridItem = GridItem.builder().setImage(BACK).setTitle("Title").build();
+        ItemList itemList =
+                builder()
+                        .setOnSelectedListener((index) -> {
+                        })
+                        .setNoItemsMessage("no items")
+                        .setSelectedIndex(0)
+                        .setOnItemsVisibilityChangedListener((start, end) -> {
+                        })
+                        .addItem(gridItem)
+                        .build();
+        assertThat(itemList)
+                .isEqualTo(
+                        builder()
+                                .setOnSelectedListener((index) -> {
+                                })
+                                .setNoItemsMessage("no items")
+                                .setSelectedIndex(0)
+                                .setOnItemsVisibilityChangedListener((start, end) -> {
+                                })
+                                .addItem(gridItem)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentNoItemsMessage() {
+        ItemList itemList = builder().setNoItemsMessage("no items").build();
+        assertThat(itemList).isNotEqualTo(builder().setNoItemsMessage("YO").build());
+    }
+
+    @Test
+    public void notEquals_differentSelectedIndex() {
+        Row row = Row.builder().setTitle("Title").build();
+        ItemList itemList =
+                builder().setOnSelectedListener((index) -> {
+                }).addItem(row).addItem(row).build();
+        assertThat(itemList)
+                .isNotEqualTo(
+                        builder()
+                                .setOnSelectedListener((index) -> {
+                                })
+                                .setSelectedIndex(1)
+                                .addItem(row)
+                                .addItem(row)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_missingSelectedListener() {
+        Row row = Row.builder().setTitle("Title").build();
+        ItemList itemList =
+                builder().setOnSelectedListener((index) -> {
+                }).addItem(row).addItem(row).build();
+        assertThat(itemList).isNotEqualTo(builder().addItem(row).addItem(row).build());
+    }
+
+    @Test
+    public void notEquals_missingVisibilityChangedListener() {
+        Row row = Row.builder().setTitle("Title").build();
+        ItemList itemList =
+                builder()
+                        .setOnItemsVisibilityChangedListener((start, end) -> {
+                        })
+                        .addItem(row)
+                        .addItem(row)
+                        .build();
+        assertThat(itemList).isNotEqualTo(builder().addItem(row).addItem(row).build());
+    }
+
+    @Test
+    public void notEquals_differentRows() {
+        Row row = Row.builder().setTitle("Title").build();
+        ItemList itemList = builder().addItem(row).addItem(row).build();
+        assertThat(itemList).isNotEqualTo(builder().addItem(row).build());
+    }
+
+    @Test
+    public void notEquals_differentGridItems() {
+        GridItem gridItem = GridItem.builder().setImage(BACK).setTitle("Title").build();
+        ItemList itemList = builder().addItem(gridItem).addItem(gridItem).build();
+        assertThat(itemList).isNotEqualTo(builder().addItem(gridItem).build());
+    }
+}
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/LatLngTest.java b/car/app/app/src/test/java/androidx/car/app/model/LatLngTest.java
similarity index 91%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/LatLngTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/LatLngTest.java
index c170d65..86b24de 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/LatLngTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/LatLngTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link LatLng}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class LatLngTest {
     @Test
     public void createInstance() {
diff --git a/car/app/app/src/test/java/androidx/car/app/model/ListTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/ListTemplateTest.java
new file mode 100644
index 0000000..30d52b0
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/model/ListTemplateTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.car.app.model;
+
+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 ListTemplate}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class ListTemplateTest {
+    @Test
+    public void createInstance_emptyList_notLoading_Throws() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> ListTemplate.builder().setTitle("Title").build());
+
+        // Positive case
+        ListTemplate.builder().setTitle("Title").setLoading(true).build();
+    }
+
+    @Test
+    public void createInstance_isLoading_hasList_Throws() {
+        assertThrows(
+                IllegalStateException.class,
+                () ->
+                        ListTemplate.builder()
+                                .setTitle("Title")
+                                .setLoading(true)
+                                .setSingleList(getList())
+                                .build());
+    }
+
+    @Test
+    public void addEmptyList_throws() {
+        ItemList emptyList = ItemList.builder().build();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> ListTemplate.builder().setTitle("Title").addList(emptyList,
+                        "header").build());
+    }
+
+    @Test
+    public void addList_emptyHeader_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> ListTemplate.builder().setTitle("Title").addList(getList(), "").build());
+    }
+
+    @Test
+    public void addList_withVisibilityListener_throws() {
+        ItemList list =
+                ItemList.builder()
+                        .addItem(Row.builder().setTitle("Title").build())
+                        .setOnItemsVisibilityChangedListener((start, end) -> {
+                        })
+                        .build();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> ListTemplate.builder().setTitle("Title").addList(list, "header").build());
+    }
+
+    @Test
+    public void addList_moreThanMaxTexts_throws() {
+        Row rowExceedsMaxTexts =
+                Row.builder().setTitle("Title").addText("text1").addText("text2").addText(
+                        "text3").build();
+        Row rowMeetingMaxTexts =
+                Row.builder().setTitle("Title").addText("text1").addText("text2").build();
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        ListTemplate.builder()
+                                .setTitle("Title")
+                                .setSingleList(
+                                        ItemList.builder().addItem(rowExceedsMaxTexts).build())
+                                .build());
+
+        // Positive case.
+        ListTemplate.builder()
+                .setTitle("Title")
+                .setSingleList(ItemList.builder().addItem(rowMeetingMaxTexts).build())
+                .build();
+    }
+
+    @Test
+    public void createInstance_noHeaderTitleOrAction_throws() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> ListTemplate.builder().setSingleList(getList()).build());
+
+        // Positive cases/.
+        ListTemplate.builder().setTitle("Title").setSingleList(getList()).build();
+        ListTemplate.builder().setHeaderAction(Action.BACK).setSingleList(getList()).build();
+    }
+
+    @Test
+    public void createInstance_setSingleList() {
+        ItemList list = getList();
+        ListTemplate template = ListTemplate.builder().setTitle("Title").setSingleList(
+                list).build();
+        assertThat(template.getSingleList()).isEqualTo(list);
+        assertThat(template.getSectionLists()).isEmpty();
+    }
+
+    @Test
+    public void createInstance_addList() {
+        ItemList list1 = getList();
+        ItemList list2 = getList();
+        ListTemplate template =
+                ListTemplate.builder()
+                        .setTitle("Title")
+                        .addList(list1, "header1")
+                        .addList(list2, "header2")
+                        .build();
+        assertThat(template.getSingleList()).isNull();
+        assertThat(template.getSectionLists()).hasSize(2);
+        assertThat(template.getSectionLists().get(0).getItemList()).isEqualTo(list1);
+        assertThat(template.getSectionLists().get(0).getHeader().getText()).isEqualTo("header1");
+        assertThat(template.getSectionLists().get(1).getItemList()).isEqualTo(list2);
+        assertThat(template.getSectionLists().get(1).getHeader().getText()).isEqualTo("header2");
+    }
+
+    @Test
+    public void setSingleList_clearLists() {
+        ItemList list1 = getList();
+        ItemList list2 = getList();
+        ItemList list3 = getList();
+        ListTemplate template =
+                ListTemplate.builder()
+                        .setTitle("Title")
+                        .addList(list1, "header1")
+                        .addList(list2, "header2")
+                        .setSingleList(list3)
+                        .build();
+        assertThat(template.getSingleList()).isEqualTo(list3);
+        assertThat(template.getSectionLists()).isEmpty();
+    }
+
+    @Test
+    public void addList_clearSingleList() {
+        ItemList list1 = getList();
+        ItemList list2 = getList();
+        ItemList list3 = getList();
+        ListTemplate template =
+                ListTemplate.builder()
+                        .setTitle("Title")
+                        .setSingleList(list1)
+                        .addList(list2, "header1")
+                        .addList(list3, "header2")
+                        .build();
+        assertThat(template.getSingleList()).isNull();
+        assertThat(template.getSectionLists()).hasSize(2);
+    }
+
+    @Test
+    public void createInstance_setHeaderAction_invalidActionThrows() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        ListTemplate.builder()
+                                .setHeaderAction(
+                                        Action.builder().setTitle("Action").setOnClickListener(
+                                                () -> {
+                                                }).build()));
+    }
+
+    @Test
+    public void createInstance_setHeaderAction() {
+        ListTemplate template =
+                ListTemplate.builder().setSingleList(getList()).setHeaderAction(
+                        Action.BACK).build();
+        assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
+    }
+
+    @Test
+    public void createInstance_setActionStrip() {
+        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
+        ListTemplate template =
+                ListTemplate.builder()
+                        .setTitle("Title")
+                        .setSingleList(getList())
+                        .setActionStrip(actionStrip)
+                        .build();
+        assertThat(template.getActionStrip()).isEqualTo(actionStrip);
+    }
+
+    @Test
+    public void equals() {
+        ItemList itemList = ItemList.builder().build();
+        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
+        String title = "title";
+
+        ListTemplate template =
+                ListTemplate.builder()
+                        .setSingleList(itemList)
+                        .setActionStrip(actionStrip)
+                        .setHeaderAction(Action.BACK)
+                        .setTitle(title)
+                        .build();
+
+        assertThat(template)
+                .isEqualTo(
+                        ListTemplate.builder()
+                                .setSingleList(itemList)
+                                .setActionStrip(actionStrip)
+                                .setHeaderAction(Action.BACK)
+                                .setTitle(title)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentItemList() {
+        ItemList itemList = ItemList.builder().build();
+
+        ListTemplate template =
+                ListTemplate.builder().setTitle("Title").setSingleList(itemList).build();
+
+        assertThat(template)
+                .isNotEqualTo(
+                        ListTemplate.builder()
+                                .setTitle("Title")
+                                .setSingleList(
+                                        ItemList.builder().addItem(
+                                                Row.builder().setTitle("Title").build()).build())
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentHeaderAction() {
+        ItemList itemList = ItemList.builder().build();
+
+        ListTemplate template =
+                ListTemplate.builder().setSingleList(itemList).setHeaderAction(Action.BACK).build();
+
+        assertThat(template)
+                .isNotEqualTo(
+                        ListTemplate.builder()
+                                .setSingleList(itemList)
+                                .setHeaderAction(Action.APP_ICON)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentActionStrip() {
+        ItemList itemList = ItemList.builder().build();
+        ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
+
+        ListTemplate template =
+                ListTemplate.builder()
+                        .setTitle("Title")
+                        .setSingleList(itemList)
+                        .setActionStrip(actionStrip)
+                        .build();
+
+        assertThat(template)
+                .isNotEqualTo(
+                        ListTemplate.builder()
+                                .setTitle("Title")
+                                .setSingleList(itemList)
+                                .setActionStrip(
+                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentTitle() {
+        ItemList itemList = ItemList.builder().build();
+        String title = "title";
+
+        ListTemplate template = ListTemplate.builder().setSingleList(itemList).setTitle(
+                title).build();
+
+        assertThat(template)
+                .isNotEqualTo(ListTemplate.builder().setSingleList(itemList).setTitle(
+                        "yo").build());
+    }
+
+    private static ItemList getList() {
+        Row row1 = Row.builder().setTitle("Bananas").build();
+        Row row2 = Row.builder().setTitle("Oranges").build();
+        return ItemList.builder().addItem(row1).addItem(row2).build();
+    }
+}
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/MessageTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
similarity index 81%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/MessageTemplateTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
index 4784044..eb791af 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/MessageTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
@@ -26,21 +26,18 @@
 import android.net.Uri;
 import android.util.Log;
 
-import androidx.car.app.test.R;
-import androidx.car.app.utils.Logger;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import com.google.common.collect.ImmutableList;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link MessageTemplate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class MessageTemplateTest {
 
     private final String mTitle = "header";
@@ -85,7 +82,7 @@
         assertThat(template.getTitle().getText()).isEqualTo("header");
         assertThat(template.getIcon()).isNull();
         assertThat(template.getHeaderAction()).isNull();
-        assertThat(template.getActionList()).isNull();
+        assertThat(template.getActions()).isNull();
         assertThat(template.getDebugMessage()).isNull();
     }
 
@@ -124,58 +121,7 @@
                 Log.getStackTraceString(exception));
         assertThat(template.getIcon()).isEqualTo(icon);
         assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
-        assertThat(template.getActionList().getList()).containsExactly(action);
-    }
-
-    @Test
-    public void validate_isRefresh() {
-        Logger logger = message -> {
-        };
-        MessageTemplate template = MessageTemplate.builder(mMessage).setTitle(mTitle).build();
-
-        assertThat(template.isRefresh(template, logger)).isTrue();
-
-        // Allowed mutable fields: icon, action strip and actions.
-        Action action = Action.builder().setOnClickListener(() -> {
-        }).setTitle("foo").build();
-        assertThat(
-                template.isRefresh(
-                        MessageTemplate.builder(mMessage)
-                                .setTitle(mTitle)
-                                .setIcon(
-                                        CarIcon.of(
-                                                IconCompat.createWithResource(
-                                                        ApplicationProvider.getApplicationContext(),
-                                                        R.drawable.ic_test_1)))
-                                .setHeaderAction(Action.BACK)
-                                .setActions(ImmutableList.of(action))
-                                .build(),
-                        logger))
-                .isTrue();
-
-        // Text changes are disallowed.
-        assertThat(
-                template.isRefresh(MessageTemplate.builder("Message2").setTitle(mTitle).build(),
-                        logger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        MessageTemplate.builder(mMessage).setTitle(mTitle).setDebugMessage(
-                                "Debug").build(),
-                        logger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        MessageTemplate.builder(mMessage)
-                                .setTitle(mTitle)
-                                .setDebugCause(new IllegalArgumentException("Exception"))
-                                .build(),
-                        logger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        MessageTemplate.builder(mMessage).setTitle("Header2").build(), logger))
-                .isFalse();
+        assertThat(template.getActions().getList()).containsExactly(action);
     }
 
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/MetadataTest.java b/car/app/app/src/test/java/androidx/car/app/model/MetadataTest.java
similarity index 92%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/MetadataTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/MetadataTest.java
index a67ff2d..e3310f11 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/MetadataTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/MetadataTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for the {@link Metadata} class. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class MetadataTest {
     @Test
     public void setAndGetPlace() {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ModelUtilsTest.java b/car/app/app/src/test/java/androidx/car/app/model/ModelUtilsTest.java
similarity index 79%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/ModelUtilsTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/ModelUtilsTest.java
index 4a82bff..bbf190f 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ModelUtilsTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ModelUtilsTest.java
@@ -20,26 +20,24 @@
 
 import android.text.SpannableString;
 
-import androidx.car.app.test.R;
-import androidx.core.graphics.drawable.IconCompat;
+import androidx.car.app.TestUtils;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import com.google.common.collect.ImmutableList;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link PlaceListMapTemplate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ModelUtilsTest {
     @Test
     public void validateAllNonBrowsableRowsHaveDistances() {
-        DistanceSpan span =
-                DistanceSpan.create(
-                        Distance.create(/* displayDistance= */ 1, Distance.UNIT_KILOMETERS_P1));
+        DistanceSpan span = DistanceSpan.create(
+                Distance.create(/* displayDistance= */ 1, Distance.UNIT_KILOMETERS_P1));
         SpannableString stringWithDistance = new SpannableString("Test");
         stringWithDistance.setSpan(span, /* start= */ 0, /* end= */ 1, /* flags= */ 0);
         SpannableString stringWithInvalidDistance = new SpannableString("Test");
@@ -56,14 +54,12 @@
 
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        ModelUtils.validateAllNonBrowsableRowsHaveDistance(
-                                ImmutableList.of(rowWithDistance, rowWithInvalidDistance)));
+                () -> ModelUtils.validateAllNonBrowsableRowsHaveDistance(
+                        ImmutableList.of(rowWithDistance, rowWithInvalidDistance)));
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        ModelUtils.validateAllNonBrowsableRowsHaveDistance(
-                                ImmutableList.of(rowWithDistance, rowWithoutDistance)));
+                () -> ModelUtils.validateAllNonBrowsableRowsHaveDistance(
+                        ImmutableList.of(rowWithDistance, rowWithoutDistance)));
 
         // Positive cases
         ModelUtils.validateAllNonBrowsableRowsHaveDistance(ImmutableList.of());
@@ -102,14 +98,12 @@
 
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        ModelUtils.validateAllRowsHaveDistanceOrDuration(
-                                ImmutableList.of(rowWithDuration, rowWithInvalidDuration)));
+                () -> ModelUtils.validateAllRowsHaveDistanceOrDuration(
+                        ImmutableList.of(rowWithDuration, rowWithInvalidDuration)));
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        ModelUtils.validateAllRowsHaveDistanceOrDuration(
-                                ImmutableList.of(rowWithDuration, plainRow)));
+                () -> ModelUtils.validateAllRowsHaveDistanceOrDuration(
+                        ImmutableList.of(rowWithDuration, plainRow)));
 
         // Positive cases.
         ModelUtils.validateAllRowsHaveDistanceOrDuration(ImmutableList.of());
@@ -123,10 +117,8 @@
 
     @Test
     public void validateAllRowsHaveOnlySmallSizedImages() {
-        CarIcon carIcon =
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
+        CarIcon carIcon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                "ic_test_1");
         Row rowWithNoImage = Row.builder().setTitle("title1").build();
         Row rowWithSmallImage =
                 Row.builder().setTitle("title2").setImage(carIcon, Row.IMAGE_TYPE_SMALL).build();
@@ -139,9 +131,8 @@
                         ImmutableList.of(rowWithLargeImage)));
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        ModelUtils.validateAllRowsHaveOnlySmallImages(
-                                ImmutableList.of(rowWithNoImage, rowWithLargeImage)));
+                () -> ModelUtils.validateAllRowsHaveOnlySmallImages(
+                        ImmutableList.of(rowWithNoImage, rowWithLargeImage)));
 
         // Positive cases
         ModelUtils.validateAllRowsHaveOnlySmallImages(ImmutableList.of());
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/host/model/OnClickListenerWrapperTest.java b/car/app/app/src/test/java/androidx/car/app/model/OnClickListenerWrapperTest.java
similarity index 82%
rename from car/app/app/src/androidTest/java/androidx/car/app/host/model/OnClickListenerWrapperTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/OnClickListenerWrapperTest.java
index 889e339..3afb7ee 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/host/model/OnClickListenerWrapperTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/OnClickListenerWrapperTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.host.model;
+package androidx.car.app.model;
 
 /** Tests for {@link OnClickListenerWrapper}. */
 
@@ -23,11 +23,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.model.OnClickListener;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.car.app.OnDoneCallback;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -35,9 +31,11 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class OnClickListenerWrapperTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -46,7 +44,6 @@
     OnClickListener mMockOnClickListener;
 
     @Test
-    @UiThreadTest
     public void create() {
         OnClickListenerWrapper wrapper = OnClickListenerWrapperImpl.create(mMockOnClickListener);
         assertThat(wrapper.isParkedOnly()).isFalse();
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/PaneTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/PaneTemplateTest.java
similarity index 67%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/PaneTemplateTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/PaneTemplateTest.java
index 6275cd6..80f0bf3 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/PaneTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/PaneTemplateTest.java
@@ -20,22 +20,16 @@
 
 import static org.junit.Assert.assertThrows;
 
-import android.text.SpannableString;
-
 import androidx.car.app.TestUtils;
-import androidx.car.app.test.R;
-import androidx.car.app.utils.Logger;
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link PaneTemplate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class PaneTemplateTest {
 
     @Test
@@ -141,94 +135,6 @@
     }
 
     @Test
-    public void validate_isRefresh() {
-        Logger logger = message -> {
-        };
-        Row.Builder row = Row.builder().setTitle("Row1");
-        PaneTemplate template =
-                PaneTemplate.builder(Pane.builder().addRow(row.build()).build()).setTitle(
-                        "Title").build();
-
-        assertThat(template.isRefresh(template, logger)).isTrue();
-
-        // Going from loading state to new content is allowed.
-        assertThat(
-                template.isRefresh(
-                        PaneTemplate.builder(Pane.builder().setLoading(true).build())
-                                .setTitle("Title")
-                                .build(),
-                        logger))
-                .isTrue();
-
-        // Other allowed mutable states.
-        SpannableString stringWithSpan = new SpannableString("Row1");
-        stringWithSpan.setSpan(DurationSpan.create(1), 0, /* end= */ 1, /* flags= */ 0);
-        IconCompat icon = IconCompat.createWithResource(
-                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1);
-        assertThat(
-                template.isRefresh(
-                        PaneTemplate.builder(
-                                Pane.builder()
-                                        .addRow(
-                                                row.setImage(CarIcon.of(icon))
-                                                        .setTitle(stringWithSpan)
-                                                        .build())
-                                        .build())
-                                .setTitle("Title")
-                                .setHeaderAction(Action.BACK)
-                                .setActionStrip(
-                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
-                                .build(),
-                        logger))
-                .isTrue();
-
-        // Title updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        PaneTemplate.builder(Pane.builder().addRow(row.build()).build())
-                                .setTitle("Title2")
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Text updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        PaneTemplate.builder(
-                                Pane.builder().addRow(row.setTitle("Row2").build()).build())
-                                .setTitle("Title")
-                                .build(),
-                        logger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        PaneTemplate.builder(
-                                Pane.builder().addRow(row.addText("Text").build()).build())
-                                .setTitle("Title")
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Additional rows are disallowed.
-        assertThat(
-                template.isRefresh(
-                        PaneTemplate.builder(Pane.builder().addRow(row.build()).addRow(
-                                row.build()).build())
-                                .setTitle("Title")
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Going from content to loading state is disallowed.
-        assertThat(
-                PaneTemplate.builder(Pane.builder().setLoading(true).build())
-                        .setTitle("Title")
-                        .build()
-                        .isRefresh(template, logger))
-                .isFalse();
-    }
-
-    @Test
     public void equals() {
         Pane pane = Pane.builder().addRow(Row.builder().setTitle("Title").build()).build();
         ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/PaneTest.java b/car/app/app/src/test/java/androidx/car/app/model/PaneTest.java
similarity index 61%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/PaneTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/PaneTest.java
index 2531bd6..9fdb2f3 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/PaneTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/PaneTest.java
@@ -20,23 +20,19 @@
 
 import static org.junit.Assert.assertThrows;
 
-import android.text.SpannableString;
-
-import androidx.car.app.utils.Logger;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import com.google.common.collect.ImmutableList;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.Arrays;
 import java.util.List;
 
 /** Tests for {@link Pane}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class PaneTest {
     @Test
     public void createEmptyRows_throws() {
@@ -65,13 +61,6 @@
     }
 
     @Test
-    public void clearRows() {
-        Row row = createRow(1);
-        Pane pane = Pane.builder().addRow(row).addRow(row).clearRows().addRow(row).build();
-        assertThat(pane.getRows()).hasSize(1);
-    }
-
-    @Test
     public void addRow_multiple() {
         Row row1 = createRow(1);
         Row row2 = createRow(2);
@@ -88,7 +77,7 @@
         Pane pane =
                 Pane.builder().addRow(Row.builder().setTitle("Title").build()).setActions(
                         actions).build();
-        assertActions(pane.getActionList(), actions);
+        assertActions(pane.getActions(), actions);
     }
 
     @Test
@@ -100,67 +89,6 @@
     }
 
     @Test
-    public void validate_isRefresh() {
-        Logger logger = message -> {
-        };
-        Row.Builder row = Row.builder().setTitle("Title1");
-
-        Pane.Builder builder = Pane.builder().setLoading(true);
-        Pane pane = builder.build();
-        assertThat(pane.isRefresh(builder.build(), logger)).isTrue();
-
-        // Going from loading state to new content is allowed.
-        Pane paneWithRows = Pane.builder().addRow(row.build()).build();
-        assertThat(paneWithRows.isRefresh(pane, logger)).isTrue();
-
-        // Text updates are disallowed.
-        Pane paneWithDifferentTitle = Pane.builder().addRow(row.setTitle("Title2").build()).build();
-        Pane paneWithDifferentText = Pane.builder().addRow(row.addText("Text").build()).build();
-        assertThat(paneWithDifferentTitle.isRefresh(paneWithRows, logger)).isFalse();
-        assertThat(paneWithDifferentText.isRefresh(paneWithRows, logger)).isFalse();
-
-        // Additional rows are disallowed.
-        Pane paneWithTwoRows = Pane.builder().addRow(row.build()).addRow(row.build()).build();
-        assertThat(paneWithTwoRows.isRefresh(paneWithRows, logger)).isFalse();
-
-        // Going from content to loading state is disallowed.
-        assertThat(pane.isRefresh(paneWithRows, logger)).isFalse();
-    }
-
-    @Test
-    public void validate_isRefresh_differentSpansAreIgnored() {
-        Logger logger = message -> {
-        };
-        SpannableString textWithDistanceSpan = new SpannableString("Text");
-        textWithDistanceSpan.setSpan(
-                DistanceSpan.create(Distance.create(1000, Distance.UNIT_KILOMETERS)),
-                /* start= */ 0,
-                /* end= */ 1,
-                /* flags= */ 0);
-        SpannableString textWithDurationSpan = new SpannableString("Text");
-        textWithDurationSpan.setSpan(DurationSpan.create(1), 0, /* end= */ 1, /* flags= */ 0);
-
-        Pane pane1 =
-                Pane.builder()
-                        .addRow(
-                                Row.builder().setTitle(textWithDistanceSpan).addText(
-                                        textWithDurationSpan).build())
-                        .build();
-        Pane pane2 =
-                Pane.builder()
-                        .addRow(
-                                Row.builder().setTitle(textWithDurationSpan).addText(
-                                        textWithDistanceSpan).build())
-                        .build();
-        Pane pane3 =
-                Pane.builder().addRow(Row.builder().setTitle("Text2").addText(
-                        "Text2").build()).build();
-
-        assertThat(pane2.isRefresh(pane1, logger)).isTrue();
-        assertThat(pane3.isRefresh(pane1, logger)).isFalse();
-    }
-
-    @Test
     public void equals() {
         Pane pane =
                 Pane.builder()
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ParkedOnlyOnClickListenerTest.java b/car/app/app/src/test/java/androidx/car/app/model/ParkedOnlyOnClickListenerTest.java
similarity index 83%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/ParkedOnlyOnClickListenerTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/ParkedOnlyOnClickListenerTest.java
index 4880a6d..d871996 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ParkedOnlyOnClickListenerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ParkedOnlyOnClickListenerTest.java
@@ -23,12 +23,7 @@
 
 import android.os.RemoteException;
 
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.host.model.OnClickListenerWrapper;
-import androidx.car.app.host.model.OnClickListenerWrapperImpl;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.car.app.OnDoneCallback;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,10 +31,12 @@
 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 OnClickListenerWrapper}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ParkedOnlyOnClickListenerTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -48,7 +45,6 @@
     OnClickListener mMockOnClickListener;
 
     @Test
-    @UiThreadTest
     public void create() throws RemoteException {
         ParkedOnlyOnClickListener parkedOnlyOnClickListener =
                 ParkedOnlyOnClickListener.create(mMockOnClickListener);
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/PlaceListMapTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/PlaceListMapTemplateTest.java
similarity index 79%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/PlaceListMapTemplateTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/PlaceListMapTemplateTest.java
index 16257d0..d590b0b 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/PlaceListMapTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/PlaceListMapTemplateTest.java
@@ -24,19 +24,16 @@
 import android.text.SpannableString;
 
 import androidx.car.app.TestUtils;
-import androidx.car.app.test.R;
-import androidx.car.app.utils.Logger;
-import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link PlaceListMapTemplate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class PlaceListMapTemplateTest {
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final DistanceSpan mDistanceSpan =
@@ -287,121 +284,6 @@
     }
 
     @Test
-    public void validate_isRefresh() {
-        Logger logger = message -> {
-        };
-        SpannableString title = new SpannableString("Title");
-        title.setSpan(mDistanceSpan, 0, 1, 0);
-        Row.Builder row =
-                Row.builder().setTitle(title).setBrowsable(true).setOnClickListener(() -> {
-                });
-        PlaceListMapTemplate template =
-                PlaceListMapTemplate.builder()
-                        .setTitle("Title")
-                        .setItemList(ItemList.builder().addItem(row.build()).build())
-                        .build();
-
-        assertThat(template.isRefresh(template, logger)).isTrue();
-
-        // Going from loading state to new content is allowed.
-        assertThat(
-                template.isRefresh(
-                        PlaceListMapTemplate.builder().setTitle("Title").setLoading(true).build(),
-                        logger))
-                .isTrue();
-
-        // Other allowed mutable states.
-        SpannableString stringWithSpan = new SpannableString("Title");
-        stringWithSpan.setSpan(mDistanceSpan, 1, /* end= */ 2, /* flags= */ 0);
-        ItemList itemList = ItemList.builder()
-                .addItem(
-                        row.setOnClickListener(() -> {
-                        })
-                                .setBrowsable(false)
-                                .setTitle(stringWithSpan)
-                                .setImage(
-                                        CarIcon.of(
-                                                IconCompat.createWithResource(
-                                                        ApplicationProvider.getApplicationContext(),
-                                                        R.drawable.ic_test_1)))
-                                .setMetadata(
-                                        Metadata.ofPlace(
-                                                Place.builder(
-                                                        LatLng.create(
-                                                                1,
-                                                                1)).build()))
-                                .build())
-                .build();
-        assertThat(
-                template.isRefresh(
-                        PlaceListMapTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(itemList)
-                                .setHeaderAction(Action.BACK)
-                                .setActionStrip(
-                                        ActionStrip.builder().addAction(Action.APP_ICON).build())
-                                .build(),
-                        logger))
-                .isTrue();
-
-        // Title updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        PlaceListMapTemplate.builder()
-                                .setItemList(ItemList.builder().addItem(row.build()).build())
-                                .setTitle("Title2")
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Text updates are disallowed.
-        SpannableString title2 = new SpannableString("Title2");
-        title2.setSpan(mDistanceSpan, 0, 1, 0);
-        assertThat(
-                template.isRefresh(
-                        PlaceListMapTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(ItemList.builder().addItem(
-                                        row.setTitle(title2).build()).build())
-                                .build(),
-                        logger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        PlaceListMapTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(
-                                        ItemList.builder()
-                                                .addItem(row.setTitle(title).addText(
-                                                        "Text").build())
-                                                .build())
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Additional rows are disallowed.
-        assertThat(
-                template.isRefresh(
-                        PlaceListMapTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(
-                                        ItemList.builder().addItem(row.build()).addItem(
-                                                row.build()).build())
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Going from content to loading state is disallowed.
-        assertThat(
-                PlaceListMapTemplate.builder()
-                        .setTitle("Title")
-                        .setLoading(true)
-                        .build()
-                        .isRefresh(template, logger))
-                .isFalse();
-    }
-
-    @Test
     public void equals() {
         ActionStrip actionStrip = ActionStrip.builder().addAction(Action.BACK).build();
         String title = "foo";
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/PlaceMarkerTest.java b/car/app/app/src/test/java/androidx/car/app/model/PlaceMarkerTest.java
similarity index 67%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/PlaceMarkerTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/PlaceMarkerTest.java
index 3e7cc36..41a25e1 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/PlaceMarkerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/PlaceMarkerTest.java
@@ -26,18 +26,18 @@
 import android.content.ContentResolver;
 import android.net.Uri;
 
-import androidx.car.app.test.R;
+import androidx.car.app.TestUtils;
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link PlaceMarker}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class PlaceMarkerTest {
 
     @Test
@@ -48,17 +48,14 @@
 
     @Test
     public void setColor_withImageTypeIcon_throws() {
-        CarIcon icon =
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
+        CarIcon icon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                "ic_test_1");
         assertThrows(
                 IllegalStateException.class,
-                () ->
-                        PlaceMarker.builder()
-                                .setIcon(icon, PlaceMarker.TYPE_IMAGE)
-                                .setColor(CarColor.SECONDARY)
-                                .build());
+                () -> PlaceMarker.builder()
+                        .setIcon(icon, PlaceMarker.TYPE_IMAGE)
+                        .setColor(CarColor.SECONDARY)
+                        .build());
     }
 
     @Test
@@ -75,16 +72,13 @@
 
     @Test
     public void createInstance() {
-        CarIcon icon =
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
-        PlaceMarker marker1 =
-                PlaceMarker.builder()
-                        .setIcon(icon, PlaceMarker.TYPE_ICON)
-                        .setLabel("foo")
-                        .setColor(CarColor.SECONDARY)
-                        .build();
+        CarIcon icon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                "ic_test_1");
+        PlaceMarker marker1 = PlaceMarker.builder()
+                .setIcon(icon, PlaceMarker.TYPE_ICON)
+                .setLabel("foo")
+                .setColor(CarColor.SECONDARY)
+                .build();
         assertThat(marker1.getIcon()).isEqualTo(icon);
         assertThat(marker1.getIconType()).isEqualTo(PlaceMarker.TYPE_ICON);
         assertThat(marker1.getColor()).isEqualTo(CarColor.SECONDARY);
@@ -103,23 +97,19 @@
 
     @Test
     public void equals() {
-        CarIcon carIcon =
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
-        PlaceMarker marker =
-                PlaceMarker.builder()
-                        .setIcon(carIcon, PlaceMarker.TYPE_ICON)
-                        .setLabel("foo")
-                        .setColor(CarColor.SECONDARY)
-                        .build();
+        CarIcon carIcon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                "ic_test_1");
+        PlaceMarker marker = PlaceMarker.builder()
+                .setIcon(carIcon, PlaceMarker.TYPE_ICON)
+                .setLabel("foo")
+                .setColor(CarColor.SECONDARY)
+                .build();
 
-        assertThat(
-                PlaceMarker.builder()
-                        .setIcon(carIcon, PlaceMarker.TYPE_ICON)
-                        .setLabel("foo")
-                        .setColor(CarColor.SECONDARY)
-                        .build())
+        assertThat(PlaceMarker.builder()
+                .setIcon(carIcon, PlaceMarker.TYPE_ICON)
+                .setLabel("foo")
+                .setColor(CarColor.SECONDARY)
+                .build())
                 .isEqualTo(marker);
     }
 
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/PlaceTest.java b/car/app/app/src/test/java/androidx/car/app/model/PlaceTest.java
similarity index 93%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/PlaceTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/PlaceTest.java
index 664dd26..d4ef129 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/PlaceTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/PlaceTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for the {@link Place} class. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class PlaceTest {
     /** Tests basic setter and getter operations. */
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/RowTest.java b/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
similarity index 82%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/RowTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/RowTest.java
index c60b39e..69c140b 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/RowTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
@@ -25,20 +25,18 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import androidx.car.app.host.OnDoneCallback;
-import androidx.car.app.test.R;
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.annotation.UiThreadTest;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.TestUtils;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link Row}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class RowTest {
     @Test
     public void create_defaultValues() {
@@ -60,22 +58,7 @@
     }
 
     @Test
-    public void title_carText() {
-        CarText title = CarText.create("foo");
-        Row row = Row.builder().setTitle(title).build();
-        assertThat(title).isEqualTo(row.getTitle());
-    }
-
-    @Test
     public void text_charSequence() {
-        CarText text1 = CarText.create("foo");
-        CarText text2 = CarText.create("bar");
-        Row row = Row.builder().setTitle("Title").addText(text1).addText(text2).build();
-        assertThat(row.getTexts()).containsExactly(text1, text2);
-    }
-
-    @Test
-    public void text_carText() {
         String text1 = "foo";
         String text2 = "bar";
         Row row = Row.builder().setTitle("Title").addText(text1).addText(text2).build();
@@ -98,14 +81,6 @@
     }
 
     @Test
-    public void setSectionHeader() {
-        Row row =
-                Row.builder().setFlags(Row.ROW_FLAG_SECTION_HEADER).setTitle(
-                        "section header").build();
-        assertThat(row.getFlags() & Row.ROW_FLAG_SECTION_HEADER).isNotEqualTo(0);
-    }
-
-    @Test
     public void setOnClickListenerAndToggle_throws() {
         Toggle toggle1 = Toggle.builder(isChecked -> {
         }).build();
@@ -121,7 +96,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void clickListener() {
         OnClickListener onClickListener = mock(OnClickListener.class);
         Row row = Row.builder().setTitle("Title").setOnClickListener(onClickListener).build();
@@ -168,11 +142,8 @@
                 })
                 .setTitle("Title")
                 .addText("Text")
-                .setImage(
-                        CarIcon.of(
-                                IconCompat.createWithResource(
-                                        ApplicationProvider.getApplicationContext(),
-                                        R.drawable.ic_test_1)))
+                .setImage(TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                        "ic_test_1"))
                 .build();
     }
 
@@ -187,7 +158,6 @@
                         .setOnClickListener(() -> {
                         })
                         .setBrowsable(false)
-                        .setFlags(1)
                         .setMetadata(Metadata.EMPTY_METADATA)
                         .addText(title)
                         .build();
@@ -199,7 +169,6 @@
                         .setOnClickListener(() -> {
                         })
                         .setBrowsable(false)
-                        .setFlags(1)
                         .setMetadata(Metadata.EMPTY_METADATA)
                         .addText(title)
                         .build())
@@ -247,13 +216,6 @@
     }
 
     @Test
-    public void notEquals_differentFlags() {
-        Row row = Row.builder().setTitle("Title").setFlags(1).build();
-
-        assertThat(Row.builder().setTitle("Title").setFlags(2).build()).isNotEqualTo(row);
-    }
-
-    @Test
     public void notEquals_differentMetadata() {
         Row row = Row.builder().setTitle("Title").setMetadata(Metadata.EMPTY_METADATA).build();
 
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/SearchTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/SearchTemplateTest.java
similarity index 97%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/SearchTemplateTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/SearchTemplateTest.java
index 16c52ba..4fcf641 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/SearchTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/SearchTemplateTest.java
@@ -26,13 +26,10 @@
 
 import android.os.RemoteException;
 
+import androidx.car.app.OnDoneCallback;
 import androidx.car.app.SearchListener;
 import androidx.car.app.TestUtils;
 import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnDoneCallback;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -40,10 +37,12 @@
 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 SearchTemplate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class SearchTemplateTest {
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -124,7 +123,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void buildWithValues() throws RemoteException {
         String initialSearchText = "searchTemplate for this!!";
         String searchHint = "This is not a hint";
@@ -155,7 +153,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void buildWithValues_failureOnSearchSubmitted() throws RemoteException {
         String initialSearchText = "searchTemplate for this!!";
         String searchHint = "This is not a hint";
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/SectionedItemListTest.java b/car/app/app/src/test/java/androidx/car/app/model/SectionedItemListTest.java
similarity index 94%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/SectionedItemListTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/SectionedItemListTest.java
index 69f5124..b80e38a 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/SectionedItemListTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/SectionedItemListTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link ItemListTest}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class SectionedItemListTest {
 
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/TemplateWrapperTest.java b/car/app/app/src/test/java/androidx/car/app/model/TemplateWrapperTest.java
similarity index 95%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/TemplateWrapperTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/TemplateWrapperTest.java
index 3cbb446..f8ff387 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/TemplateWrapperTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/TemplateWrapperTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link TemplateWrapper}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class TemplateWrapperTest {
     @Test
     public void createInstance() {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/ToggleTest.java b/car/app/app/src/test/java/androidx/car/app/model/ToggleTest.java
similarity index 92%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/ToggleTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/ToggleTest.java
index 5f7aca2..d4d3575 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/ToggleTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ToggleTest.java
@@ -23,12 +23,9 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import androidx.car.app.OnDoneCallback;
 import androidx.car.app.WrappedRuntimeException;
-import androidx.car.app.host.OnDoneCallback;
 import androidx.car.app.model.Toggle.OnCheckedChangeListener;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,10 +33,12 @@
 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 Toggle}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ToggleTest {
     @Rule
     public MockitoRule mocks = MockitoJUnit.rule();
@@ -54,7 +53,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void build_checkedChange_sendsCheckedChangeCall() {
         Toggle toggle = Toggle.builder(mMockOnCheckedChangeListener).setChecked(true).build();
         OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
@@ -65,7 +63,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void build_checkedChange_sendsCheckedChangeCallWithFailure() {
         String testExceptionMessage = "Test exception";
         doThrow(new RuntimeException(testExceptionMessage)).when(
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/constraints/ActionsConstraintsTest.java b/car/app/app/src/test/java/androidx/car/app/model/constraints/ActionsConstraintsTest.java
similarity index 63%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/constraints/ActionsConstraintsTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/constraints/ActionsConstraintsTest.java
index ab577b5..0ac564e 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/constraints/ActionsConstraintsTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/constraints/ActionsConstraintsTest.java
@@ -24,20 +24,18 @@
 import androidx.car.app.model.Action;
 import androidx.car.app.model.ActionStrip;
 import androidx.car.app.model.CarIcon;
-import androidx.car.app.test.R;
-import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.Collections;
 
 /** Tests for {@link ActionsConstraints}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ActionsConstraintsTest {
     @Test
     public void createEmpty() {
@@ -51,23 +49,21 @@
     public void create_requiredExceedsMaxAllowedActions() {
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        ActionsConstraints.builder()
-                                .setMaxActions(1)
-                                .addRequiredActionType(Action.TYPE_BACK)
-                                .addRequiredActionType(Action.TYPE_CUSTOM)
-                                .build());
+                () -> ActionsConstraints.builder()
+                        .setMaxActions(1)
+                        .addRequiredActionType(Action.TYPE_BACK)
+                        .addRequiredActionType(Action.TYPE_CUSTOM)
+                        .build());
     }
 
     @Test
     public void create_requiredAlsoDisallowed() {
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        ActionsConstraints.builder()
-                                .addRequiredActionType(Action.TYPE_BACK)
-                                .addDisallowedActionType(Action.TYPE_BACK)
-                                .build());
+                () -> ActionsConstraints.builder()
+                        .addRequiredActionType(Action.TYPE_BACK)
+                        .addDisallowedActionType(Action.TYPE_BACK)
+                        .build());
     }
 
     @Test
@@ -94,10 +90,8 @@
                         .addDisallowedActionType(Action.TYPE_BACK)
                         .build();
 
-        CarIcon carIcon =
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
+        CarIcon carIcon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                "ic_test_1");
         Action actionWithIcon = TestUtils.createAction(null, carIcon);
         Action actionWithTitle = TestUtils.createAction("Title", carIcon);
 
@@ -115,39 +109,35 @@
         // Missing required type.
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                ActionStrip.builder().addAction(
-                                        Action.APP_ICON).build().getActions()));
+                () -> constraints.validateOrThrow(
+                        ActionStrip.builder().addAction(
+                                Action.APP_ICON).build().getActions()));
 
         // Disallowed type
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                ActionStrip.builder().addAction(Action.BACK).build().getActions()));
+                () -> constraints.validateOrThrow(
+                        ActionStrip.builder().addAction(Action.BACK).build().getActions()));
 
         // Over max allowed actions
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                ActionStrip.builder()
-                                        .addAction(Action.APP_ICON)
-                                        .addAction(actionWithIcon)
-                                        .addAction(actionWithTitle)
-                                        .build()
-                                        .getActions()));
+                () -> constraints.validateOrThrow(
+                        ActionStrip.builder()
+                                .addAction(Action.APP_ICON)
+                                .addAction(actionWithIcon)
+                                .addAction(actionWithTitle)
+                                .build()
+                                .getActions()));
 
         // Over max allowed actions with title
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                ActionStrip.builder()
-                                        .addAction(actionWithTitle)
-                                        .addAction(actionWithTitle)
-                                        .build()
-                                        .getActions()));
+                () -> constraints.validateOrThrow(
+                        ActionStrip.builder()
+                                .addAction(actionWithTitle)
+                                .addAction(actionWithTitle)
+                                .build()
+                                .getActions()));
     }
 }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/constraints/RowConstraintsTest.java b/car/app/app/src/test/java/androidx/car/app/model/constraints/RowConstraintsTest.java
similarity index 66%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/constraints/RowConstraintsTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/constraints/RowConstraintsTest.java
index 9b4d96e..5bc88c5 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/constraints/RowConstraintsTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/constraints/RowConstraintsTest.java
@@ -18,21 +18,20 @@
 
 import static org.junit.Assert.assertThrows;
 
+import androidx.car.app.TestUtils;
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.Row;
 import androidx.car.app.model.Toggle;
-import androidx.car.app.test.R;
-import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link RowConstraints}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class RowConstraintsTest {
     @Test
     public void validate_clickListener() {
@@ -43,10 +42,9 @@
 
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                Row.builder().setTitle("Title)").setOnClickListener(() -> {
-                                }).build()));
+                () -> constraints.validateOrThrow(
+                        Row.builder().setTitle("Title)").setOnClickListener(() -> {
+                        }).build()));
 
         // Positive cases
         constraints.validateOrThrow(Row.builder().setTitle("Title").build());
@@ -62,13 +60,12 @@
 
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                Row.builder()
-                                        .setTitle("Title)")
-                                        .setToggle(Toggle.builder(isChecked -> {
-                                        }).build())
-                                        .build()));
+                () -> constraints.validateOrThrow(
+                        Row.builder()
+                                .setTitle("Title)")
+                                .setToggle(Toggle.builder(isChecked -> {
+                                }).build())
+                                .build()));
 
         // Positive cases
         constraints.validateOrThrow(Row.builder().setTitle("Title").build());
@@ -81,16 +78,13 @@
     public void validate_images() {
         RowConstraints constraints = RowConstraints.builder().setImageAllowed(false).build();
         RowConstraints allowConstraints = RowConstraints.builder().setImageAllowed(true).build();
-        CarIcon carIcon =
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
+        CarIcon carIcon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                "ic_test_1");
 
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                Row.builder().setTitle("Title)").setImage(carIcon).build()));
+                () -> constraints.validateOrThrow(
+                        Row.builder().setTitle("Title)").setImage(carIcon).build()));
 
         // Positive cases
         constraints.validateOrThrow(Row.builder().setTitle("Title").build());
@@ -103,14 +97,13 @@
 
         assertThrows(
                 IllegalArgumentException.class,
-                () ->
-                        constraints.validateOrThrow(
-                                Row.builder()
-                                        .setTitle("Title)")
-                                        .addText("text1")
-                                        .addText("text2")
-                                        .addText("text3")
-                                        .build()));
+                () -> constraints.validateOrThrow(
+                        Row.builder()
+                                .setTitle("Title)")
+                                .addText("text1")
+                                .addText("text2")
+                                .addText("text3")
+                                .build()));
 
         // Positive cases
         constraints.validateOrThrow(
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/model/constraints/RowListConstraintsTest.java b/car/app/app/src/test/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
similarity index 95%
rename from car/app/app/src/androidTest/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
rename to car/app/app/src/test/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
index 56b5273..131dd52 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
@@ -19,15 +19,15 @@
 import static org.junit.Assert.assertThrows;
 
 import androidx.car.app.TestUtils;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link RowListConstraints}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class RowListConstraintsTest {
     @Test
     public void validate_itemList_noSelectable() {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/NavigationManagerTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/NavigationManagerTest.java
similarity index 78%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/NavigationManagerTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/NavigationManagerTest.java
index 051358e..6605c9b 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/NavigationManagerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/NavigationManagerTest.java
@@ -37,9 +37,8 @@
 import androidx.car.app.navigation.model.TravelEstimate;
 import androidx.car.app.navigation.model.Trip;
 import androidx.car.app.serialization.Bundleable;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.car.app.testing.TestCarContext;
+import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,12 +46,15 @@
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /** Tests for {@link NavigationManager}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class NavigationManagerTest {
     @Mock
     private ICarHost mMockCarHost;
@@ -88,10 +90,12 @@
                     .build();
 
     @Before
-    @UiThreadTest
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
 
+        TestCarContext testCarContext =
+                TestCarContext.createCarContext(ApplicationProvider.getApplicationContext());
+
         INavigationHost navHostStub =
                 new INavigationHost.Stub() {
                     @Override
@@ -113,15 +117,14 @@
 
         mHostDispatcher.setCarHost(mMockCarHost);
 
-        mNavigationManager = NavigationManager.create(mHostDispatcher);
+        mNavigationManager = NavigationManager.create(testCarContext, mHostDispatcher);
     }
 
     @Test
-    @UiThreadTest
     public void navigationStarted_sendState_navigationEnded() throws RemoteException {
         InOrder inOrder = inOrder(mMockNavHost);
 
-        mNavigationManager.setListener(mNavigationListener);
+        mNavigationManager.setNavigationManagerListener(mNavigationListener);
         mNavigationManager.navigationStarted();
         inOrder.verify(mMockNavHost).navigationStarted();
 
@@ -133,16 +136,14 @@
     }
 
     @Test
-    @UiThreadTest
     public void navigationStarted_noListenerSet() throws RemoteException {
         assertThrows(IllegalStateException.class, () -> mNavigationManager.navigationStarted());
     }
 
     @Test
-    @UiThreadTest
     public void navigationStarted_multiple() throws RemoteException {
 
-        mNavigationManager.setListener(mNavigationListener);
+        mNavigationManager.setNavigationManagerListener(mNavigationListener);
         mNavigationManager.navigationStarted();
 
         mNavigationManager.navigationStarted();
@@ -150,7 +151,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void navigationEnded_multiple_not_started() throws RemoteException {
         mNavigationManager.navigationEnded();
         mNavigationManager.navigationEnded();
@@ -164,44 +164,52 @@
     }
 
     @Test
-    @UiThreadTest
-    public void stopNavigation_notNavigating() throws RemoteException {
-        mNavigationManager.setListener(mNavigationListener);
-        mNavigationManager.getIInterface().stopNavigation(mock(IOnDoneCallback.class));
-        verify(mNavigationListener, never()).stopNavigation();
+    public void onStopNavigation_notNavigating() throws RemoteException {
+        mNavigationManager.setNavigationManagerListener(mNavigationListener);
+        mNavigationManager.getIInterface().onStopNavigation(mock(IOnDoneCallback.class));
+        verify(mNavigationListener, never()).onStopNavigation();
     }
 
     @Test
-    @UiThreadTest
-    public void stopNavigation_navigating_restart() throws RemoteException {
+    public void onStopNavigation_navigating_restart() throws RemoteException {
         InOrder inOrder = inOrder(mMockNavHost, mNavigationListener);
 
-        mNavigationManager.setListener(mNavigationListener);
+        mNavigationManager.setNavigationManagerListener(new SynchronousExecutor(),
+                mNavigationListener);
         mNavigationManager.navigationStarted();
         inOrder.verify(mMockNavHost).navigationStarted();
 
-        mNavigationManager.getIInterface().stopNavigation(mock(IOnDoneCallback.class));
-        inOrder.verify(mNavigationListener).stopNavigation();
+        mNavigationManager.getIInterface().onStopNavigation(mock(IOnDoneCallback.class));
+
+        inOrder.verify(mNavigationListener).onStopNavigation();
 
         mNavigationManager.navigationStarted();
         inOrder.verify(mMockNavHost).navigationStarted();
     }
 
     @Test
-    @UiThreadTest
     public void onAutoDriveEnabled_callsListener() {
-        mNavigationManager.setListener(mNavigationListener);
+        mNavigationManager.setNavigationManagerListener(new SynchronousExecutor(),
+                mNavigationListener);
         mNavigationManager.onAutoDriveEnabled();
 
         verify(mNavigationListener).onAutoDriveEnabled();
     }
 
     @Test
-    @UiThreadTest
     public void onAutoDriveEnabledBeforeRegisteringListener_callsListener() {
         mNavigationManager.onAutoDriveEnabled();
-        mNavigationManager.setListener(mNavigationListener);
+        mNavigationManager.setNavigationManagerListener(new SynchronousExecutor(),
+                mNavigationListener);
 
         verify(mNavigationListener).onAutoDriveEnabled();
     }
+
+    static class SynchronousExecutor implements Executor {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    }
+
 }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/DestinationTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/DestinationTest.java
similarity index 96%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/DestinationTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/DestinationTest.java
index ba52c24..0663a13 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/DestinationTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/DestinationTest.java
@@ -25,15 +25,15 @@
 
 import androidx.car.app.model.CarIcon;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link Destination}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class DestinationTest {
 
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/LaneDirectionTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/LaneDirectionTest.java
similarity index 92%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/LaneDirectionTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/LaneDirectionTest.java
index 3f17b37..3d691ec 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/LaneDirectionTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/LaneDirectionTest.java
@@ -20,15 +20,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link LaneDirection}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class LaneDirectionTest {
 
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/LaneTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/LaneTest.java
similarity index 94%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/LaneTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/LaneTest.java
index 8d4a1197..aee2408 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/LaneTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/LaneTest.java
@@ -21,15 +21,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link Lane}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class LaneTest {
 
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/ManeuverTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/ManeuverTest.java
similarity index 97%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/ManeuverTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/ManeuverTest.java
index 390b2ee..5064bda 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/ManeuverTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/ManeuverTest.java
@@ -17,11 +17,11 @@
 package androidx.car.app.navigation.model;
 
 import static androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_LEFT;
-import static androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER;
 import static androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW;
 import static androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE;
 import static androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW;
 import static androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE;
+import static androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW;
 import static androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -33,15 +33,15 @@
 
 import androidx.car.app.model.CarIcon;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link Maneuver}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class ManeuverTest {
 
     @Test
@@ -175,7 +175,7 @@
         assertThrows(
                 IllegalArgumentException.class,
                 () ->
-                        Maneuver.builder(TYPE_ROUNDABOUT_ENTER)
+                        Maneuver.builder(TYPE_ROUNDABOUT_ENTER_CW)
                                 .setRoundaboutExitNumber(1)
                                 .setRoundaboutExitAngle(1)
                                 .setIcon(CarIcon.APP_ICON)
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/MessageInfoTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/MessageInfoTest.java
similarity index 95%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/MessageInfoTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/MessageInfoTest.java
index 1be5851..81063ca 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/MessageInfoTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/MessageInfoTest.java
@@ -25,15 +25,15 @@
 
 import androidx.car.app.model.CarIcon;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link MessageInfoTest}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class MessageInfoTest {
 
     @Test
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/NavigationTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/NavigationTemplateTest.java
similarity index 86%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/NavigationTemplateTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/NavigationTemplateTest.java
index 7b82386..8e3b04a 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/NavigationTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/NavigationTemplateTest.java
@@ -28,19 +28,18 @@
 import androidx.car.app.model.CarColor;
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.Distance;
-import androidx.car.app.utils.Logger;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.concurrent.TimeUnit;
 
 /** Tests for {@link NavigationTemplate}. */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class NavigationTemplateTest {
     private final ActionStrip mActionStrip =
             ActionStrip.builder().addAction(TestUtils.createAction("test", null)).build();
@@ -108,60 +107,6 @@
     }
 
     @Test
-    public void validate_isRefresh() {
-        Logger logger = message -> {
-        };
-
-        TravelEstimate travelEstimate =
-                TravelEstimate.create(
-                        Distance.create(/* displayDistance= */ 20, Distance.UNIT_METERS),
-                        TimeUnit.HOURS.toSeconds(1),
-                        createDateTimeWithZone("2020-05-14T19:57:00-07:00", "US/Pacific"));
-
-        Step currentStep =
-                Step.builder("Hop on a ferry")
-                        .addLane(
-                                Lane.builder()
-                                        .addDirection(LaneDirection.create(
-                                                LaneDirection.SHAPE_NORMAL_LEFT, false))
-                                        .build())
-                        .setLanesImage(CarIcon.ALERT)
-                        .build();
-        Distance currentDistance = Distance.create(/* displayDistance= */ 100,
-                Distance.UNIT_METERS);
-
-        NavigationTemplate reroutingTemplate =
-                NavigationTemplate.builder()
-                        .setNavigationInfo(RoutingInfo.builder().setIsLoading(true).build())
-                        .setActionStrip(mActionStrip)
-                        .build();
-
-        NavigationTemplate navigatingTemplate =
-                NavigationTemplate.builder()
-                        .setNavigationInfo(
-                                RoutingInfo.builder()
-                                        .setCurrentStep(currentStep, currentDistance)
-                                        .setJunctionImage(CarIcon.ALERT)
-                                        .setNextStep(currentStep)
-                                        .build())
-                        .setActionStrip(mActionStrip)
-                        .setDestinationTravelEstimate(travelEstimate)
-                        .setBackgroundColor(CarColor.BLUE)
-                        .build();
-
-        NavigationTemplate arrivedTemplate =
-                NavigationTemplate.builder()
-                        .setNavigationInfo(MessageInfo.builder("Arrived!").setText(
-                                "name\naddress").build())
-                        .setActionStrip(mActionStrip)
-                        .build();
-
-        assertThat(navigatingTemplate.isRefresh(reroutingTemplate, logger)).isTrue();
-        assertThat(arrivedTemplate.isRefresh(navigatingTemplate, logger)).isTrue();
-        assertThat(reroutingTemplate.isRefresh(arrivedTemplate, logger)).isTrue();
-    }
-
-    @Test
     public void equals() {
         TravelEstimate travelEstimate =
                 TravelEstimate.create(
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/PlaceListNavigationTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/PlaceListNavigationTemplateTest.java
similarity index 75%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/PlaceListNavigationTemplateTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/PlaceListNavigationTemplateTest.java
index ff80881..cd5aa98 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/PlaceListNavigationTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/PlaceListNavigationTemplateTest.java
@@ -37,19 +37,16 @@
 import androidx.car.app.model.PlaceMarker;
 import androidx.car.app.model.Row;
 import androidx.car.app.model.Toggle;
-import androidx.car.app.test.R;
-import androidx.car.app.utils.Logger;
-import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link PlaceListNavigationTemplate}. */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class PlaceListNavigationTemplateTest {
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final DistanceSpan mDistanceSpan =
@@ -63,7 +60,7 @@
                 () -> PlaceListNavigationTemplate.builder().setTitle("Title").build());
 
         // Positive case
-        PlaceListNavigationTemplate.builder().setTitle("Title").setIsLoading(true).build();
+        PlaceListNavigationTemplate.builder().setTitle("Title").setLoading(true).build();
     }
 
     @Test
@@ -73,7 +70,7 @@
                 () ->
                         PlaceListNavigationTemplate.builder()
                                 .setTitle("Title")
-                                .setIsLoading(true)
+                                .setLoading(true)
                                 .setItemList(ItemList.builder().build())
                                 .build());
     }
@@ -283,116 +280,6 @@
     }
 
     @Test
-    public void validate_isRefresh() {
-        Logger logger = message -> {
-        };
-        SpannableString title = new SpannableString("Title");
-        title.setSpan(mDistanceSpan, /* start= */ 0, /* end= */ 1, /* flags= */ 0);
-        Row.Builder row =
-                Row.builder().setTitle(title).setBrowsable(true).setOnClickListener(() -> {
-                });
-        PlaceListNavigationTemplate template =
-                PlaceListNavigationTemplate.builder()
-                        .setTitle("Title")
-                        .setItemList(ItemList.builder().addItem(row.build()).build())
-                        .build();
-
-        assertThat(template.isRefresh(template, logger)).isTrue();
-
-        // Going from loading state to new content is allowed.
-        assertThat(
-                template.isRefresh(
-                        PlaceListNavigationTemplate.builder().setTitle("Title").setIsLoading(
-                                true).build(),
-                        logger))
-                .isTrue();
-
-        // Other allowed mutable states.
-        SpannableString stringWithSpan = new SpannableString("Title");
-        stringWithSpan.setSpan(mDistanceSpan, 1, /* end= */ 2, /* flags= */ 0);
-        assertThat(template.isRefresh(PlaceListNavigationTemplate.builder()
-                        .setTitle("Title")
-                        .setItemList(ItemList.builder()
-                                .addItem(row.setOnClickListener(() -> {
-                                })
-                                        .setBrowsable(false)
-                                        .setTitle(stringWithSpan)
-                                        .setImage(CarIcon.of(
-                                                IconCompat.createWithResource(
-                                                        ApplicationProvider.getApplicationContext(),
-                                                        R.drawable.ic_test_1)))
-                                        .setMetadata(Metadata.ofPlace(
-                                                Place.builder(
-                                                        LatLng.create(
-                                                                1,
-                                                                1)).build()))
-                                        .build())
-                                .build())
-                        .setHeaderAction(Action.BACK)
-                        .setActionStrip(
-                                ActionStrip.builder().addAction(Action.APP_ICON).build())
-                        .build(),
-                logger))
-                .isTrue();
-
-        // Title updates are disallowed.
-        assertThat(
-                template.isRefresh(
-                        PlaceListNavigationTemplate.builder()
-                                .setItemList(ItemList.builder().addItem(row.build()).build())
-                                .setTitle("Title2")
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Text updates are disallowed.
-        SpannableString title2 = new SpannableString("Title2");
-        title2.setSpan(mDistanceSpan, /* start= */ 0, /* end= */ 1, /* flags= */ 0);
-        assertThat(
-                template.isRefresh(
-                        PlaceListNavigationTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(ItemList.builder().addItem(
-                                        row.setTitle(title2).build()).build())
-                                .build(),
-                        logger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        PlaceListNavigationTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(
-                                        ItemList.builder()
-                                                .addItem(row.setTitle(title).addText(
-                                                        "Text").build())
-                                                .build())
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Additional rows are disallowed.
-        assertThat(
-                template.isRefresh(
-                        PlaceListNavigationTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(
-                                        ItemList.builder().addItem(row.build()).addItem(
-                                                row.build()).build())
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Going from content to loading state is disallowed.
-        assertThat(
-                PlaceListNavigationTemplate.builder()
-                        .setTitle("Title")
-                        .setIsLoading(true)
-                        .build()
-                        .isRefresh(template, logger))
-                .isFalse();
-    }
-
-    @Test
     public void equals() {
         PlaceListNavigationTemplate template =
                 PlaceListNavigationTemplate.builder()
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplateTest.java
similarity index 74%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplateTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplateTest.java
index d06b943..dcf4c11 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplateTest.java
@@ -26,8 +26,8 @@
 import android.text.SpannableString;
 
 import androidx.car.app.CarAppPermission;
+import androidx.car.app.OnDoneCallback;
 import androidx.car.app.TestUtils;
-import androidx.car.app.host.OnDoneCallback;
 import androidx.car.app.model.Action;
 import androidx.car.app.model.ActionStrip;
 import androidx.car.app.model.CarIcon;
@@ -36,20 +36,16 @@
 import androidx.car.app.model.ItemList;
 import androidx.car.app.model.OnClickListener;
 import androidx.car.app.model.Row;
-import androidx.car.app.test.R;
-import androidx.car.app.utils.Logger;
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link RoutePreviewNavigationTemplate}. */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class RoutePreviewNavigationTemplateTest {
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private static final DistanceSpan DISTANCE =
@@ -63,7 +59,7 @@
                 () -> RoutePreviewNavigationTemplate.builder().setTitle("Title").build());
 
         // Positive case
-        RoutePreviewNavigationTemplate.builder().setTitle("Title").setIsLoading(true).build();
+        RoutePreviewNavigationTemplate.builder().setTitle("Title").setLoading(true).build();
     }
 
     @Test
@@ -72,7 +68,7 @@
                 IllegalStateException.class,
                 () -> RoutePreviewNavigationTemplate.builder()
                         .setTitle("Title")
-                        .setIsLoading(true)
+                        .setLoading(true)
                         .setItemList(
                                 TestUtils.createItemListWithDistanceSpan(2, true, DISTANCE))
                         .build());
@@ -109,7 +105,7 @@
                         .setItemList(
                                 ItemList.builder()
                                         .addItem(rowExceedsMaxTexts)
-                                        .setSelectable(selectedIndex -> {
+                                        .setOnSelectedListener(selectedIndex -> {
                                         })
                                         .build()));
 
@@ -119,7 +115,7 @@
                 .setItemList(
                         ItemList.builder()
                                 .addItem(rowMeetingMaxTexts)
-                                .setSelectable(selectedIndex -> {
+                                .setOnSelectedListener(selectedIndex -> {
                                 })
                                 .build());
     }
@@ -128,13 +124,13 @@
     public void noHeaderTitleOrAction_throws() {
         assertThrows(
                 IllegalStateException.class,
-                () -> RoutePreviewNavigationTemplate.builder().setIsLoading(true).build());
+                () -> RoutePreviewNavigationTemplate.builder().setLoading(true).build());
 
         // Positive cases.
-        RoutePreviewNavigationTemplate.builder().setTitle("Title").setIsLoading(true).build();
+        RoutePreviewNavigationTemplate.builder().setTitle("Title").setLoading(true).build();
         RoutePreviewNavigationTemplate.builder()
                 .setHeaderAction(Action.BACK)
-                .setIsLoading(true)
+                .setLoading(true)
                 .build();
     }
 
@@ -186,7 +182,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void setOnNavigateAction() {
         OnClickListener mockListener = mock(OnClickListener.class);
         RoutePreviewNavigationTemplate template =
@@ -217,7 +212,7 @@
                         .build());
 
         // Positive case
-        RoutePreviewNavigationTemplate.builder().setTitle("Title").setIsLoading(true).build();
+        RoutePreviewNavigationTemplate.builder().setTitle("Title").setLoading(true).build();
     }
 
     @Test
@@ -235,13 +230,13 @@
                         .build());
 
         // Positive case
-        RoutePreviewNavigationTemplate.builder().setTitle("Title").setIsLoading(true).build();
+        RoutePreviewNavigationTemplate.builder().setTitle("Title").setLoading(true).build();
     }
 
     @Test
     public void createInstance_navigateActionNoTitle_throws() {
-        CarIcon carIcon = CarIcon.of(IconCompat.createWithResource(
-                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
+        CarIcon carIcon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                "ic_test_1");
         assertThrows(
                 IllegalArgumentException.class,
                 () -> RoutePreviewNavigationTemplate.builder()
@@ -272,9 +267,8 @@
         Row rowWithTime = Row.builder().setTitle(title).build();
         Row rowWithoutTime = Row.builder().setTitle("Google Bve").build();
         Action navigateAction = Action.builder()
-                .setIcon(CarIcon.of(IconCompat.createWithResource(
-                        ApplicationProvider.getApplicationContext(),
-                        R.drawable.ic_test_1)))
+                .setIcon(TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+                        "ic_test_1"))
                 .setTitle("Navigate")
                 .setOnClickListener(() -> {
                 })
@@ -287,7 +281,7 @@
                         .setItemList(ItemList.builder()
                                 .addItem(rowWithTime)
                                 .addItem(rowWithoutTime)
-                                .setSelectable(index -> {
+                                .setOnSelectedListener(index -> {
                                 })
                                 .build())
                         .setNavigateAction(navigateAction)
@@ -296,138 +290,13 @@
         // Positive case
         RoutePreviewNavigationTemplate.builder()
                 .setTitle("Title")
-                .setItemList(ItemList.builder().setSelectable(index -> {
+                .setItemList(ItemList.builder().setOnSelectedListener(index -> {
                 }).addItem(rowWithTime).build())
                 .setNavigateAction(navigateAction)
                 .build();
     }
 
     @Test
-    public void validate_isRefresh() {
-        Logger logger = message -> {
-        };
-        SpannableString title = new SpannableString("Title");
-        title.setSpan(DISTANCE, 0, 1, 0);
-        Row.Builder row = Row.builder().setTitle(title);
-        Action navigateAction = Action.builder().setTitle("Navigate").setOnClickListener(() -> {
-        }).build();
-        RoutePreviewNavigationTemplate template = RoutePreviewNavigationTemplate.builder()
-                .setTitle("Title")
-                .setItemList(ItemList.builder().addItem(row.build()).setSelectable(
-                        index -> {
-                        }).build())
-                .setNavigateAction(navigateAction)
-                .build();
-
-        assertThat(template.isRefresh(template, logger)).isTrue();
-
-        // Going from loading state to new content is allowed.
-        assertThat(template.isRefresh(
-                RoutePreviewNavigationTemplate.builder()
-                        .setTitle("Title")
-                        .setIsLoading(true)
-                        .build(),
-                logger))
-                .isTrue();
-
-        // Other allowed mutable states.
-        SpannableString stringWithSpan = new SpannableString("Title");
-        stringWithSpan.setSpan(DISTANCE, 1, /* end= */ 2, /* flags= */ 0);
-        assertThat(template.isRefresh(
-                RoutePreviewNavigationTemplate.builder()
-                        .setTitle("Title")
-                        .setItemList(ItemList.builder()
-                                .addItem(row.setImage(
-                                        CarIcon.of(IconCompat.createWithResource(
-                                                ApplicationProvider.getApplicationContext(),
-                                                R.drawable.ic_test_1)))
-                                        .setTitle(stringWithSpan)
-                                        .build())
-                                .setSelectable(index -> {
-                                })
-                                .build())
-                        .setHeaderAction(Action.BACK)
-                        .setNavigateAction(Action.builder().setTitle(
-                                "Navigate2").setOnClickListener(() -> {
-                                }
-                        ).build())
-                        .setActionStrip(ActionStrip.builder().addAction(Action.APP_ICON).build())
-                        .build(),
-                logger))
-                .isTrue();
-
-        // Title updates are disallowed.
-        assertThat(template.isRefresh(
-                RoutePreviewNavigationTemplate.builder()
-                        .setItemList(ItemList.builder().addItem(row.build()).setSelectable(
-                                index -> {
-                                }).build())
-                        .setTitle("Title2")
-                        .setNavigateAction(navigateAction)
-                        .build(),
-                logger))
-                .isFalse();
-
-        // Text updates are disallowed.
-        SpannableString title2 = new SpannableString("Title2");
-        title2.setSpan(DISTANCE, 0, 1, 0);
-        assertThat(
-                template.isRefresh(
-                        RoutePreviewNavigationTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(
-                                        ItemList.builder()
-                                                .addItem(row.setTitle(title2).build())
-                                                .setSelectable(index -> {
-                                                })
-                                                .build())
-                                .setNavigateAction(navigateAction)
-                                .build(),
-                        logger))
-                .isFalse();
-        assertThat(
-                template.isRefresh(
-                        RoutePreviewNavigationTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(
-                                        ItemList.builder()
-                                                .addItem(row.addText("Text").build())
-                                                .setSelectable(index -> {
-                                                })
-                                                .build())
-                                .setNavigateAction(navigateAction)
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Additional rows are disallowed.
-        assertThat(
-                template.isRefresh(
-                        RoutePreviewNavigationTemplate.builder()
-                                .setTitle("Title")
-                                .setItemList(
-                                        ItemList.builder()
-                                                .addItem(row.build())
-                                                .addItem(row.build())
-                                                .setSelectable(index -> {
-                                                })
-                                                .build())
-                                .setNavigateAction(navigateAction)
-                                .build(),
-                        logger))
-                .isFalse();
-
-        // Going from content to loading state is disallowed.
-        assertThat(
-                RoutePreviewNavigationTemplate.builder()
-                        .setTitle("Title")
-                        .setIsLoading(true)
-                        .build()
-                        .isRefresh(template, logger))
-                .isFalse();
-    }
-
-    @Test
     public void equals() {
         RoutePreviewNavigationTemplate template =
                 RoutePreviewNavigationTemplate.builder()
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/RoutingInfoTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/RoutingInfoTest.java
similarity index 97%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/RoutingInfoTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/RoutingInfoTest.java
index 58ea716..8cd974c 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/RoutingInfoTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/RoutingInfoTest.java
@@ -26,15 +26,15 @@
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.Distance;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link RoutingInfoTest}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class RoutingInfoTest {
 
     private final Maneuver mManeuver =
@@ -54,7 +54,7 @@
         assertThrows(
                 IllegalStateException.class,
                 () -> RoutingInfo.builder()
-                        .setIsLoading(true)
+                        .setLoading(true)
                         .setCurrentStep(mCurrentStep, mCurrentDistance)
                         .build());
     }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/StepTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/StepTest.java
similarity index 97%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/StepTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/StepTest.java
index bc8a79d..3c0f0c8 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/StepTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/StepTest.java
@@ -24,15 +24,15 @@
 import static org.junit.Assert.assertThrows;
 
 import androidx.car.app.model.CarIcon;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 /** Tests for {@link Step}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class StepTest {
     @Test
     public void createInstance() {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/TravelEstimateTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/TravelEstimateTest.java
similarity index 71%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/TravelEstimateTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/TravelEstimateTest.java
index 50d0c0c..ca589ec 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/TravelEstimateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/TravelEstimateTest.java
@@ -18,6 +18,7 @@
 
 import static androidx.car.app.TestUtils.assertDateTimeWithZoneEquals;
 import static androidx.car.app.TestUtils.createDateTimeWithZone;
+import static androidx.car.app.navigation.model.TravelEstimate.REMAINING_TIME_UNKNOWN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -26,12 +27,11 @@
 import androidx.car.app.model.CarColor;
 import androidx.car.app.model.DateTimeWithZone;
 import androidx.car.app.model.Distance;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.time.Duration;
 import java.time.ZonedDateTime;
@@ -40,8 +40,8 @@
 import java.util.concurrent.TimeUnit;
 
 /** Tests for {@link TravelEstimate}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class TravelEstimateTest {
     private final DateTimeWithZone mArrivalTime =
             createDateTimeWithZone("2020-04-14T15:57:00", "US/Pacific");
@@ -50,7 +50,21 @@
     private final long mRemainingTime = TimeUnit.HOURS.toMillis(10);
 
     @Test
-    @SdkSuppress(minSdkVersion = 26)
+    public void build_default_to_unknown_time() {
+        DateTimeWithZone arrivalTime = createDateTimeWithZone("2020-04-14T15:57:00", "US/Pacific");
+        Distance remainingDistance = Distance.create(/* displayDistance= */ 100,
+                Distance.UNIT_METERS);
+        long remainingTime = TimeUnit.HOURS.toMillis(10);
+        TravelEstimate travelEstimate =
+                TravelEstimate.builder(remainingDistance, arrivalTime).build();
+
+        assertThat(travelEstimate.getRemainingDistance()).isEqualTo(remainingDistance);
+        assertThat(travelEstimate.getRemainingTimeSeconds()).isEqualTo(REMAINING_TIME_UNKNOWN);
+        assertThat(travelEstimate.getArrivalTimeAtDestination()).isEqualTo(arrivalTime);
+        assertThat(travelEstimate.getRemainingTimeColor()).isEqualTo(CarColor.DEFAULT);
+    }
+
+    @Test
     public void create_duration() {
         ZonedDateTime arrivalTime = ZonedDateTime.parse("2020-05-14T19:57:00-07:00[US/Pacific]");
         Duration remainingTime = Duration.ofHours(10);
@@ -82,6 +96,46 @@
     }
 
     @Test
+    public void create_unknown_remaining_time_in_seconds() {
+        DateTimeWithZone arrivalTime = createDateTimeWithZone("2020-04-14T15:57:00", "US/Pacific");
+        Distance remainingDistance = Distance.create(/* displayDistance= */ 100,
+                Distance.UNIT_METERS);
+        TravelEstimate travelEstimate =
+                TravelEstimate.create(remainingDistance, REMAINING_TIME_UNKNOWN, arrivalTime);
+
+        assertThat(travelEstimate.getRemainingDistance()).isEqualTo(remainingDistance);
+        assertThat(travelEstimate.getRemainingTimeSeconds()).isEqualTo(REMAINING_TIME_UNKNOWN);
+        assertThat(travelEstimate.getArrivalTimeAtDestination()).isEqualTo(arrivalTime);
+        assertThat(travelEstimate.getRemainingTimeColor()).isEqualTo(CarColor.DEFAULT);
+    }
+
+    @Test
+    public void create_unknown_remaining_time() {
+        ZonedDateTime arrivalTime = ZonedDateTime.parse("2020-05-14T19:57:00-07:00[US/Pacific]");
+        Distance remainingDistance = Distance.create(/* displayDistance= */ 100,
+                Distance.UNIT_METERS);
+        TravelEstimate travelEstimate =
+                TravelEstimate.create(
+                        remainingDistance, Duration.ofSeconds(REMAINING_TIME_UNKNOWN), arrivalTime);
+
+        assertThat(travelEstimate.getRemainingDistance()).isEqualTo(remainingDistance);
+        assertThat(travelEstimate.getRemainingTimeSeconds()).isEqualTo(REMAINING_TIME_UNKNOWN);
+        assertDateTimeWithZoneEquals(arrivalTime, travelEstimate.getArrivalTimeAtDestination());
+        assertThat(travelEstimate.getRemainingTimeColor()).isEqualTo(CarColor.DEFAULT);
+    }
+
+    @Test
+    public void create_invalid_remaining_time() {
+        DateTimeWithZone arrivalTime = createDateTimeWithZone("2020-04-14T15:57:00", "US/Pacific");
+        Distance remainingDistance = Distance.create(/* displayDistance= */ 100,
+                Distance.UNIT_METERS);
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> TravelEstimate.builder(remainingDistance,
+                        arrivalTime).setRemainingTimeSeconds(-2));
+    }
+
+    @Test
     public void create_custom_remainingTimeColor() {
         DateTimeWithZone arrivalTime = createDateTimeWithZone("2020-04-14T15:57:00", "US/Pacific");
         Distance remainingDistance = Distance.create(/* displayDistance= */ 100,
@@ -100,8 +154,8 @@
         for (CarColor carColor : allowedColors) {
             TravelEstimate travelEstimate =
                     TravelEstimate.builder(remainingDistance,
-                            TimeUnit.MILLISECONDS.toSeconds(remainingTime),
                             arrivalTime)
+                            .setRemainingTimeSeconds(TimeUnit.MILLISECONDS.toSeconds(remainingTime))
                             .setRemainingTimeColor(carColor)
                             .build();
 
@@ -132,8 +186,8 @@
         for (CarColor carColor : allowedColors) {
             TravelEstimate travelEstimate =
                     TravelEstimate.builder(remainingDistance,
-                            TimeUnit.MILLISECONDS.toSeconds(remainingTime),
                             arrivalTime)
+                            .setRemainingTimeSeconds(TimeUnit.MILLISECONDS.toSeconds(remainingTime))
                             .setRemainingDistanceColor(carColor)
                             .build();
 
@@ -155,8 +209,9 @@
                 IllegalArgumentException.class,
                 () ->
                         TravelEstimate.builder(remainingDistance,
-                                TimeUnit.MILLISECONDS.toSeconds(remainingTime),
                                 arrivalTime)
+                                .setRemainingTimeSeconds(
+                                        TimeUnit.MILLISECONDS.toSeconds(remainingTime))
                                 .setRemainingTimeColor(CarColor.createCustom(1, 2)));
     }
 
@@ -220,16 +275,17 @@
     public void notEquals_differentRemainingTimeColor() {
         TravelEstimate travelEstimate =
                 TravelEstimate.builder(mRemainingDistance,
-                        TimeUnit.MILLISECONDS.toSeconds(mRemainingTime),
                         mArrivalTime)
+                        .setRemainingTimeSeconds(TimeUnit.MILLISECONDS.toSeconds(mRemainingTime))
                         .setRemainingTimeColor(CarColor.YELLOW)
                         .build();
 
         assertThat(travelEstimate)
                 .isNotEqualTo(
                         TravelEstimate.builder(mRemainingDistance,
-                                TimeUnit.MILLISECONDS.toSeconds(mRemainingTime),
                                 mArrivalTime)
+                                .setRemainingTimeSeconds(
+                                        TimeUnit.MILLISECONDS.toSeconds(mRemainingTime))
                                 .setRemainingTimeColor(CarColor.GREEN)
                                 .build());
     }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/TripTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/TripTest.java
similarity index 93%
rename from car/app/app/src/androidTest/java/androidx/car/app/navigation/model/TripTest.java
rename to car/app/app/src/test/java/androidx/car/app/navigation/model/TripTest.java
index 4c936e3..7fb0d3c 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/navigation/model/TripTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/TripTest.java
@@ -26,17 +26,17 @@
 
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.Distance;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.concurrent.TimeUnit;
 
 /** Tests for {@link Trip}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class TripTest {
 
     private final Step mStep =
@@ -71,7 +71,7 @@
                         .addDestinationTravelEstimate(mDestinationTravelEstimate)
                         .addStepTravelEstimate(mStepTravelEstimate)
                         .setCurrentRoad(ROAD)
-                        .setIsLoading(false)
+                        .setLoading(false)
                         .build();
 
         assertThat(trip.getDestinations()).hasSize(1);
@@ -112,7 +112,7 @@
                         .addDestination(mDestination)
                         .addDestinationTravelEstimate(mDestinationTravelEstimate)
                         .setCurrentRoad(ROAD)
-                        .setIsLoading(true)
+                        .setLoading(true)
                         .build();
 
         assertThat(trip.getDestinations()).hasSize(1);
@@ -129,17 +129,17 @@
     public void createInstance_loading_with_steps() {
         assertThrows(
                 IllegalArgumentException.class,
-                () -> Trip.builder().addStep(mStep).setIsLoading(true).build());
+                () -> Trip.builder().addStep(mStep).setLoading(true).build());
         assertThrows(
                 IllegalArgumentException.class,
-                () -> Trip.builder().addStepTravelEstimate(mStepTravelEstimate).setIsLoading(
+                () -> Trip.builder().addStepTravelEstimate(mStepTravelEstimate).setLoading(
                         true).build());
         assertThrows(
                 IllegalArgumentException.class,
                 () -> Trip.builder()
                         .addStep(mStep)
                         .addStepTravelEstimate(mStepTravelEstimate)
-                        .setIsLoading(true)
+                        .setLoading(true)
                         .build());
     }
 }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/notification/CarAppExtenderTest.java b/car/app/app/src/test/java/androidx/car/app/notification/CarAppExtenderTest.java
similarity index 83%
rename from car/app/app/src/androidTest/java/androidx/car/app/notification/CarAppExtenderTest.java
rename to car/app/app/src/test/java/androidx/car/app/notification/CarAppExtenderTest.java
index 1a35531..db35c5a 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/notification/CarAppExtenderTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/notification/CarAppExtenderTest.java
@@ -27,22 +27,21 @@
 import android.os.Bundle;
 
 import androidx.annotation.NonNull;
-import androidx.car.app.test.R;
+import androidx.car.app.TestUtils;
 import androidx.core.app.NotificationCompat;
 import androidx.core.app.NotificationManagerCompat;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.List;
 
 /** Tests for {@link CarAppExtender}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public final class CarAppExtenderTest {
     private static final String NOTIFICATION_CHANNEL_ID = "test carextender channel id";
     private static final String INTENT_PRIMARY_ACTION =
@@ -76,8 +75,8 @@
         assertThat(carAppExtender.isExtended()).isFalse();
         assertThat(carAppExtender.getContentTitle()).isNull();
         assertThat(carAppExtender.getContentText()).isNull();
-        assertThat(carAppExtender.getSmallIconResId()).isEqualTo(0);
-        assertThat(carAppExtender.getLargeIconBitmap()).isNull();
+        assertThat(carAppExtender.getSmallIcon()).isEqualTo(0);
+        assertThat(carAppExtender.getLargeIcon()).isNull();
         assertThat(carAppExtender.getContentIntent()).isNull();
         assertThat(carAppExtender.getDeleteIntent()).isNull();
         assertThat(carAppExtender.getActions()).isEmpty();
@@ -129,22 +128,23 @@
 
     @Test
     public void notification_extended_setSmallIcon() {
-        int resId = R.drawable.ic_test_1;
+        int resId = TestUtils.getTestDrawableResId(mContext, "ic_test_1");
         NotificationCompat.Builder builder =
                 new NotificationCompat.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .extend(CarAppExtender.builder().setSmallIcon(resId).build());
 
-        assertThat(new CarAppExtender(builder.build()).getSmallIconResId()).isEqualTo(resId);
+        assertThat(new CarAppExtender(builder.build()).getSmallIcon()).isEqualTo(resId);
     }
 
     @Test
     public void notification_extended_setLargeIcon() {
-        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_test_2);
+        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+                TestUtils.getTestDrawableResId(mContext, "ic_test_2"));
         NotificationCompat.Builder builder =
                 new NotificationCompat.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .extend(CarAppExtender.builder().setLargeIcon(bitmap).build());
 
-        assertThat(new CarAppExtender(builder.build()).getLargeIconBitmap()).isEqualTo(bitmap);
+        assertThat(new CarAppExtender(builder.build()).getLargeIcon()).isEqualTo(bitmap);
     }
 
     @Test
@@ -179,14 +179,13 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 23)
     public void notification_extended_addActions() {
-        int icon1 = R.drawable.ic_test_1;
+        int icon1 = TestUtils.getTestDrawableResId(mContext, "ic_test_1");
         CharSequence title1 = "FirstAction";
         Intent intent1 = new Intent(INTENT_PRIMARY_ACTION);
         PendingIntent actionIntent1 = PendingIntent.getBroadcast(mContext, 0, intent1, 0);
 
-        int icon2 = R.drawable.ic_test_2;
+        int icon2 = TestUtils.getTestDrawableResId(mContext, "ic_test_2");
         CharSequence title2 = "SecondAction";
         Intent intent2 = new Intent(INTENT_SECONDARY_ACTION);
         PendingIntent actionIntent2 = PendingIntent.getBroadcast(mContext, 0, intent2, 0);
@@ -210,31 +209,6 @@
     }
 
     @Test
-    public void notification_extended_clearActions() {
-        int icon1 = R.drawable.ic_test_1;
-        CharSequence title1 = "FirstAction";
-        Intent intent1 = new Intent(INTENT_PRIMARY_ACTION);
-        PendingIntent actionIntent1 = PendingIntent.getBroadcast(mContext, 0, intent1, 0);
-
-        int icon2 = R.drawable.ic_test_2;
-        CharSequence title2 = "SecondAction";
-        Intent intent2 = new Intent(INTENT_SECONDARY_ACTION);
-        PendingIntent actionIntent2 = PendingIntent.getBroadcast(mContext, 0, intent2, 0);
-
-        NotificationCompat.Builder builder =
-                new NotificationCompat.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                        .extend(
-                                CarAppExtender.builder()
-                                        .addAction(icon1, title1, actionIntent1)
-                                        .addAction(icon2, title2, actionIntent2)
-                                        .clearActions()
-                                        .build());
-
-        List<Action> actions = new CarAppExtender(builder.build()).getActions();
-        assertThat(actions).isEmpty();
-    }
-
-    @Test
     public void notification_extended_setImportance() {
         NotificationCompat.Builder builder =
                 new NotificationCompat.Builder(mContext, NOTIFICATION_CHANNEL_ID)
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/serialization/BundlerTest.java b/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
similarity index 96%
rename from car/app/app/src/androidTest/java/androidx/car/app/serialization/BundlerTest.java
rename to car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
index eccc838..6bdf76b 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/serialization/BundlerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/serialization/BundlerTest.java
@@ -31,7 +31,8 @@
 import android.os.RemoteException;
 
 import androidx.annotation.Nullable;
-import androidx.car.app.host.OnDoneCallback;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.TestUtils;
 import androidx.car.app.model.Action;
 import androidx.car.app.model.ActionStrip;
 import androidx.car.app.model.CarIcon;
@@ -46,17 +47,14 @@
 import androidx.car.app.model.Row;
 import androidx.car.app.serialization.Bundler.CycleDetectedBundlerException;
 import androidx.car.app.serialization.Bundler.TracedBundlerException;
-import androidx.car.app.test.R;
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
 
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -70,8 +68,8 @@
 import java.util.Set;
 
 /** Tests for {@link Bundler}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
 public class BundlerTest {
     private static final String TAG_CLASS_NAME = "tag_class_name";
     private static final String TAG_CLASS_TYPE = "tag_class_type";
@@ -160,7 +158,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void binderSerialization() throws BundlerException, RemoteException {
         OnClickListener clickListener = mock(OnClickListener.class);
 
@@ -289,7 +286,8 @@
     @Test
     public void imageSerialization_resource() throws BundlerException {
         Context context = ApplicationProvider.getApplicationContext();
-        IconCompat image = IconCompat.createWithResource(context, R.drawable.ic_test_1);
+        IconCompat image = IconCompat.createWithResource(context,
+                TestUtils.getTestDrawableResId(mContext, "ic_test_1"));
         Bundle bundle = Bundler.toBundle(image);
 
         assertThat(CarIcon.of((IconCompat) Bundler.fromBundle(bundle))).isEqualTo(
@@ -307,7 +305,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 23)
     public void imageSerialization_Icon() throws BundlerException {
         Bitmap bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.ARGB_8888);
         try {
@@ -332,10 +329,7 @@
         String row1Subtitle = "row1subtitle";
 
         LatLng latLng2 = LatLng.create(4522.234, 34.234);
-        CarIcon carIcon =
-                CarIcon.of(
-                        IconCompat.createWithResource(
-                                ApplicationProvider.getApplicationContext(), R.drawable.ic_test_1));
+        CarIcon carIcon = TestUtils.getTestCarIcon(mContext, "ic_test_1");
         PlaceMarker marker2 = PlaceMarker.builder().setIcon(carIcon, PlaceMarker.TYPE_ICON).build();
         String row2Title = "row2";
         String row2Subtitle = "row2subtitle";
@@ -530,12 +524,14 @@
         assertThrows(BundlerException.class, () -> Bundler.fromBundle(bundle));
     }
 
-    @Test
-    public void classMissingDefaultConstructorSerialization_throwsBundlerException() {
-        assertThrows(
-                BundlerException.class,
-                () -> Bundler.toBundle(new TestClassMissingDefaultConstructor(1)));
-    }
+    //TODO(rampara): Investigate why default constructor is still found when running with
+    // robolectric.
+//    @Test
+//    public void classMissingDefaultConstructorSerialization_throwsBundlerException() {
+//        assertThrows(
+//                BundlerException.class,
+//                () -> Bundler.toBundle(new TestClassMissingDefaultConstructor(1)));
+//    }
 
     @Test
     public void arraySerialization_throwsBundlerException() {
@@ -551,8 +547,7 @@
 
     @Test
     public void imageCompat_dejetify() throws BundlerException {
-        CarIcon image = CarIcon.of(IconCompat.createWithResource(mContext, R.drawable.ic_test_1));
-
+        CarIcon image = TestUtils.getTestCarIcon(mContext, "ic_test_1");
         Bundle bundle = Bundler.toBundle(image);
 
         // Get the field for the image, and re-write it with a "jetified" key.
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/CarAppServiceController.java b/car/app/app/src/test/java/androidx/car/app/testing/CarAppServiceController.java
similarity index 93%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/CarAppServiceController.java
rename to car/app/app/src/test/java/androidx/car/app/testing/CarAppServiceController.java
index 709f742..a37aa5c 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/testing/CarAppServiceController.java
+++ b/car/app/app/src/test/java/androidx/car/app/testing/CarAppServiceController.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.car.app.AppInfo;
 import androidx.car.app.CarAppService;
 import androidx.car.app.HostInfo;
 import androidx.car.app.ICarApp;
@@ -182,6 +183,17 @@
         }
     }
 
+    public void setAppInfo(@Nullable AppInfo appInfo) {
+        try {
+            Field appInfoField = CarAppService.class.getDeclaredField("mAppInfo");
+            appInfoField.setAccessible(true);
+            appInfoField.set(mCarAppService, appInfo);
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalStateException(
+                    "Failed to set CarAppService appInfo value for testing", e);
+        }
+    }
+
     /** Retrieves the {@link CarAppService} that is being controlled. */
     @NonNull
     public CarAppService get() {
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/FakeHost.java b/car/app/app/src/test/java/androidx/car/app/testing/FakeHost.java
similarity index 95%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/FakeHost.java
rename to car/app/app/src/test/java/androidx/car/app/testing/FakeHost.java
index e6e30b1..498870a5 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/testing/FakeHost.java
+++ b/car/app/app/src/test/java/androidx/car/app/testing/FakeHost.java
@@ -41,8 +41,8 @@
  * <p>This fake allows sending a {@link PendingIntent} as if the user clicked on a notification
  * action.
  *
- * <p>It will also perform expected host behaviors such as calling {@link Screen#getTemplate} after
- * {@link AppManager#invalidate} is called.
+ * <p>It will also perform expected host behaviors such as calling {@link Screen#onGetTemplate}
+ * after {@link AppManager#invalidate} is called.
  */
 public class FakeHost {
     private final ICarHost.Stub mCarHost = new TestCarHost();
@@ -73,7 +73,7 @@
 
         Bundle extras = new Bundle(1);
         extras.putBinder(
-                CarContext.START_CAR_APP_BINDER_KEY,
+                CarContext.EXTRA_START_CAR_APP_BINDER_KEY,
                 mTestCarContext.getStartCarAppStub().asBinder());
         Intent extraData = new Intent().putExtras(extras);
 
@@ -122,7 +122,7 @@
             Screen top = mTestCarContext.getCarService(TestScreenManager.class).getTop();
             mTestCarContext
                     .getCarService(TestAppManager.class)
-                    .addTemplateReturned(top, top.getTemplate());
+                    .addTemplateReturned(top, top.onGetTemplate());
         }
 
         @Override
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/ScreenController.java b/car/app/app/src/test/java/androidx/car/app/testing/ScreenController.java
similarity index 98%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/ScreenController.java
rename to car/app/app/src/test/java/androidx/car/app/testing/ScreenController.java
index 07cb534..8565154 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/testing/ScreenController.java
+++ b/car/app/app/src/test/java/androidx/car/app/testing/ScreenController.java
@@ -38,7 +38,7 @@
  *
  * <ul>
  *   <li>Moving a {@link Screen} through its different {@link State}s.
- *   <li>Retrieving all {@link Template}s returned from {@link Screen#getTemplate}. The values can
+ *   <li>Retrieving all {@link Template}s returned from {@link Screen#onGetTemplate}. The values can
  *       be reset with {@link #reset}.
  * </ul>
  */
@@ -58,7 +58,7 @@
     }
 
     /**
-     * Retrieves all the {@link Template}s returned from {@link Screen#getTemplate} for the {@link
+     * Retrieves all the {@link Template}s returned from {@link Screen#onGetTemplate} for the {@link
      * Screen} being controlled.
      *
      * <p>The templates are stored in order of calls.
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/TestAppManager.java b/car/app/app/src/test/java/androidx/car/app/testing/TestAppManager.java
similarity index 92%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/TestAppManager.java
rename to car/app/app/src/test/java/androidx/car/app/testing/TestAppManager.java
index 92d74b5..9a5b1f0 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/testing/TestAppManager.java
+++ b/car/app/app/src/test/java/androidx/car/app/testing/TestAppManager.java
@@ -36,8 +36,8 @@
  *
  * <ul>
  *   <li>All {@link SurfaceListener}s set via calling {@link AppManager#setSurfaceListener}.
- *   <li>The {@link Template}s returned from {@link Screen#getTemplate} due to invalidate calls via
- *       {@link AppManager#invalidate}.
+ *   <li>The {@link Template}s returned from {@link Screen#onGetTemplate} due to invalidate calls
+ *       via {@link AppManager#invalidate}.
  *   <li>All toasts shown via calling {@link AppManager#showToast}.
  * </ul>
  */
@@ -79,8 +79,9 @@
     }
 
     /**
-     * Retrieves all the {@link Template}s returned from {@link Screen#getTemplate} due to a call to
-     * {@link AppManager#invalidate}, and the respective {@link Screen} instance that returned it.
+     * Retrieves all the {@link Template}s returned from {@link Screen#onGetTemplate} due to a call
+     * to {@link AppManager#invalidate}, and the respective {@link Screen} instance that returned
+     * it.
      *
      * <p>The results are stored in order of calls.
      *
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/TestCarContext.java b/car/app/app/src/test/java/androidx/car/app/testing/TestCarContext.java
similarity index 98%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/TestCarContext.java
rename to car/app/app/src/test/java/androidx/car/app/testing/TestCarContext.java
index 4f09fbd..19e5091 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/testing/TestCarContext.java
+++ b/car/app/app/src/test/java/androidx/car/app/testing/TestCarContext.java
@@ -86,7 +86,7 @@
         } else if (serviceClass.isInstance(mTestNavigationManager)) {
             serviceName = NAVIGATION_SERVICE;
         } else if (serviceClass.isInstance(mTestScreenManager)) {
-            serviceName = SCREEN_MANAGER_SERVICE;
+            serviceName = SCREEN_SERVICE;
         } else {
             serviceName = getCarServiceName(serviceClass);
         }
@@ -107,7 +107,7 @@
                 return mTestAppManager;
             case CarContext.NAVIGATION_SERVICE:
                 return mTestNavigationManager;
-            case CarContext.SCREEN_MANAGER_SERVICE:
+            case CarContext.SCREEN_SERVICE:
                 return mTestScreenManager;
             default:
                 // Fall out
@@ -226,7 +226,7 @@
         this.mFakeHost = new FakeHost(this);
         this.mTestLifecycleOwner = testLifecycleOwner;
         this.mTestAppManager = new TestAppManager(this, hostDispatcher);
-        this.mTestNavigationManager = new TestNavigationManager(hostDispatcher);
+        this.mTestNavigationManager = new TestNavigationManager(this, hostDispatcher);
         this.mTestScreenManager = new TestScreenManager(this);
     }
 }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/TestLifecycleOwner.java b/car/app/app/src/test/java/androidx/car/app/testing/TestLifecycleOwner.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/TestLifecycleOwner.java
rename to car/app/app/src/test/java/androidx/car/app/testing/TestLifecycleOwner.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/TestOnDoneCallbackStub.java b/car/app/app/src/test/java/androidx/car/app/testing/TestOnDoneCallbackStub.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/TestOnDoneCallbackStub.java
rename to car/app/app/src/test/java/androidx/car/app/testing/TestOnDoneCallbackStub.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/TestScreenManager.java b/car/app/app/src/test/java/androidx/car/app/testing/TestScreenManager.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/TestScreenManager.java
rename to car/app/app/src/test/java/androidx/car/app/testing/TestScreenManager.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/model/ControllerUtil.java b/car/app/app/src/test/java/androidx/car/app/testing/model/ControllerUtil.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/model/ControllerUtil.java
rename to car/app/app/src/test/java/androidx/car/app/testing/model/ControllerUtil.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/model/LaneController.java b/car/app/app/src/test/java/androidx/car/app/testing/model/LaneController.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/model/LaneController.java
rename to car/app/app/src/test/java/androidx/car/app/testing/model/LaneController.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/TestNavigationManager.java b/car/app/app/src/test/java/androidx/car/app/testing/navigation/TestNavigationManager.java
similarity index 85%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/TestNavigationManager.java
rename to car/app/app/src/test/java/androidx/car/app/testing/navigation/TestNavigationManager.java
index db99643..3892e58 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/TestNavigationManager.java
+++ b/car/app/app/src/test/java/androidx/car/app/testing/navigation/TestNavigationManager.java
@@ -24,6 +24,7 @@
 import androidx.car.app.navigation.NavigationManager;
 import androidx.car.app.navigation.NavigationManagerListener;
 import androidx.car.app.navigation.model.Trip;
+import androidx.car.app.testing.TestCarContext;
 import androidx.car.app.testing.navigation.model.TripController;
 
 import java.util.ArrayList;
@@ -37,7 +38,8 @@
  *
  * <ul>
  *   <li>All the {@link Trip}s sent via {@link NavigationManager#updateTrip}.
- *   <li>All the {@link NavigationManagerListener}s set via {@link NavigationManager#setListener}.
+ *   <li>All the {@link NavigationManagerListener}s set via
+ *   {@link NavigationManager#setNavigationManagerListener}.
  *   <li>Count of times that the navigation was started via {@link
  *       NavigationManager#navigationStarted()}.
  *   <li>Count of times that the navigation was ended via {@link NavigationManager#navigationEnded}.
@@ -71,14 +73,14 @@
 
     /**
      * Retrieves all the {@link NavigationManagerListener}s added via {@link
-     * NavigationManager#setListener(NavigationManagerListener)}.
+     * NavigationManager#setNavigationManagerListener(NavigationManagerListener)}.
      *
      * <p>The listeners are stored in order of calls.
      *
      * <p>The listeners will be stored until {@link #reset} is called.
      */
     @NonNull
-    public List<NavigationManagerListener> getNavigationManagerListenersSet() {
+    public List<NavigationManagerListener> getNavigationManagerCallbacksSet() {
         return mListenersSet;
     }
 
@@ -105,9 +107,9 @@
     }
 
     @Override
-    public void setListener(@Nullable NavigationManagerListener listener) {
+    public void setNavigationManagerListener(@Nullable NavigationManagerListener listener) {
         mListenersSet.add(listener);
-        super.setListener(listener);
+        super.setNavigationManagerListener(listener);
     }
 
     @Override
@@ -122,7 +124,7 @@
         super.navigationEnded();
     }
 
-    public TestNavigationManager(HostDispatcher hostDispatcher) {
-        super(hostDispatcher);
+    public TestNavigationManager(TestCarContext testCarContext, HostDispatcher hostDispatcher) {
+        super(testCarContext, hostDispatcher);
     }
 }
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/model/DestinationController.java b/car/app/app/src/test/java/androidx/car/app/testing/navigation/model/DestinationController.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/model/DestinationController.java
rename to car/app/app/src/test/java/androidx/car/app/testing/navigation/model/DestinationController.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/model/StepController.java b/car/app/app/src/test/java/androidx/car/app/testing/navigation/model/StepController.java
similarity index 100%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/model/StepController.java
rename to car/app/app/src/test/java/androidx/car/app/testing/navigation/model/StepController.java
diff --git a/car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/model/TripController.java b/car/app/app/src/test/java/androidx/car/app/testing/navigation/model/TripController.java
similarity index 98%
rename from car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/model/TripController.java
rename to car/app/app/src/test/java/androidx/car/app/testing/navigation/model/TripController.java
index c6e9e10..692d408 100644
--- a/car/app/app/src/androidTest/java/androidx/car/app/testing/navigation/model/TripController.java
+++ b/car/app/app/src/test/java/androidx/car/app/testing/navigation/model/TripController.java
@@ -40,7 +40,7 @@
  *   <li>The {@link TravelEstimate}s set via {@link Trip.Builder#addDestinationTravelEstimate}.
  *   <li>The {@link TravelEstimate}s set via {@link Trip.Builder#addStepTravelEstimate}.
  *   <li>The current road set via {@link Trip.Builder#setCurrentRoad}.
- *   <li>The loading state set via {@link Trip.Builder#setIsLoading}.
+ *   <li>The loading state set via {@link Trip.Builder#setLoading}.
  * </ul>
  */
 public class TripController {
diff --git a/car/app/app/src/test/java/androidx/car/app/versioning/CarAppApiLevelsTest.java b/car/app/app/src/test/java/androidx/car/app/versioning/CarAppApiLevelsTest.java
new file mode 100644
index 0000000..916e933
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/versioning/CarAppApiLevelsTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.car.app.versioning;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarAppApiLevelsTest {
+    @Test
+    public void isValid_apiLowerThanOldest_notValid() {
+        assertThat(CarAppApiLevels.isValid(CarAppApiLevels.OLDEST - 1)).isFalse();
+    }
+
+    @Test
+    public void isValid_apiHigherThanLatest_notValid() {
+        assertThat(CarAppApiLevels.isValid(CarAppApiLevels.LATEST + 1)).isFalse();
+    }
+}
diff --git a/car/app/app/src/androidTest/res/drawable-hdpi/banana.png b/car/app/app/src/test/res/drawable-hdpi/banana.png
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable-hdpi/banana.png
rename to car/app/app/src/test/res/drawable-hdpi/banana.png
Binary files differ
diff --git a/car/app/app/src/androidTest/res/drawable-ldpi/banana.png b/car/app/app/src/test/res/drawable-ldpi/banana.png
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable-ldpi/banana.png
rename to car/app/app/src/test/res/drawable-ldpi/banana.png
Binary files differ
diff --git a/car/app/app/src/androidTest/res/drawable-mdpi/banana.png b/car/app/app/src/test/res/drawable-mdpi/banana.png
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable-mdpi/banana.png
rename to car/app/app/src/test/res/drawable-mdpi/banana.png
Binary files differ
diff --git a/car/app/app/src/androidTest/res/drawable-xhdpi/banana.png b/car/app/app/src/test/res/drawable-xhdpi/banana.png
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable-xhdpi/banana.png
rename to car/app/app/src/test/res/drawable-xhdpi/banana.png
Binary files differ
diff --git a/car/app/app/src/androidTest/res/drawable-xxhdpi/banana.png b/car/app/app/src/test/res/drawable-xxhdpi/banana.png
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable-xxhdpi/banana.png
rename to car/app/app/src/test/res/drawable-xxhdpi/banana.png
Binary files differ
diff --git a/car/app/app/src/androidTest/res/drawable/banana.png b/car/app/app/src/test/res/drawable/banana.png
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable/banana.png
rename to car/app/app/src/test/res/drawable/banana.png
Binary files differ
diff --git a/car/app/app/src/androidTest/res/drawable/ic_test_1.xml b/car/app/app/src/test/res/drawable/ic_test_1.xml
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable/ic_test_1.xml
rename to car/app/app/src/test/res/drawable/ic_test_1.xml
diff --git a/car/app/app/src/androidTest/res/drawable/ic_test_2.xml b/car/app/app/src/test/res/drawable/ic_test_2.xml
similarity index 100%
rename from car/app/app/src/androidTest/res/drawable/ic_test_2.xml
rename to car/app/app/src/test/res/drawable/ic_test_2.xml
diff --git a/car/app/app/src/test/resources/robolectric.properties b/car/app/app/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..ce87047
--- /dev/null
+++ b/car/app/app/src/test/resources/robolectric.properties
@@ -0,0 +1,3 @@
+# Robolectric currently doesn't support API 30, so we have to explicitly specify 29 as the target
+# sdk for now. Remove when no longer necessary.
+sdk=29
diff --git a/collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.java b/collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.java
new file mode 100644
index 0000000..feafa5c
--- /dev/null
+++ b/collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.collection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class IndexBasedArrayIteratorTest {
+
+    @Test
+    public void iterateAll() {
+        Iterator<String> iterator = new ArraySet<>(setOf("a", "b", "c")).iterator();
+        assertThat(toList(iterator)).containsExactly("a", "b", "c");
+    }
+
+    @Test
+    public void iterateEmptyList() {
+        Iterator<String> iterator = new ArraySet<String>().iterator();
+        assertThat(iterator.hasNext()).isFalse();
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void iterateEmptyListThrowsUponNext() {
+        Iterator<String> iterator = new ArraySet<String>().iterator();
+        iterator.next();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void removeSameItemTwice() {
+        Iterator<String> iterator = new ArraySet<>(listOf("a", "b", "c")).iterator();
+        iterator.next(); // move to next
+        iterator.remove();
+        iterator.remove();
+    }
+
+
+    @Test
+    public void removeLast() {
+        removeViaIterator(
+                /* original= */ setOf("a", "b", "c"),
+                /* toBeRemoved= */ setOf("c"),
+                /* expected= */ setOf("a", "b"));
+    }
+
+    @Test
+    public void removeFirst() {
+        removeViaIterator(
+                /* original= */ setOf("a", "b", "c"),
+                /* toBeRemoved= */ setOf("a"),
+                /* expected= */ setOf("b", "c"));
+    }
+
+    @Test
+    public void removeMid() {
+        removeViaIterator(
+                /* original= */ setOf("a", "b", "c"),
+                /* toBeRemoved= */ setOf("b"),
+                /* expected= */ setOf("a", "c"));
+    }
+
+    @Test
+    public void removeConsecutive() {
+        removeViaIterator(
+                /* original= */ setOf("a", "b", "c", "d"),
+                /* toBeRemoved= */ setOf("b", "c"),
+                /* expected= */ setOf("a", "d"));
+    }
+
+    @Test
+    public void removeLastTwo() {
+        removeViaIterator(
+                /* original= */ setOf("a", "b", "c", "d"),
+                /* toBeRemoved= */ setOf("c", "d"),
+                /* expected= */ setOf("a", "b"));
+    }
+
+    @Test
+    public void removeFirstTwo() {
+        removeViaIterator(
+                /* original= */ setOf("a", "b", "c", "d"),
+                /* toBeRemoved= */ setOf("a", "b"),
+                /* expected= */ setOf("c", "d"));
+    }
+
+    @Test
+    public void removeMultiple() {
+        removeViaIterator(
+                /* original= */ setOf("a", "b", "c", "d"),
+                /* toBeRemoved= */ setOf("a", "c"),
+                /* expected= */ setOf("b", "d"));
+    }
+
+    private static void removeViaIterator(
+            Set<String> original,
+            Set<String> toBeRemoved,
+            Set<String> expected) {
+        ArraySet<String> subject = new ArraySet<>(original);
+        Iterator<String> iterator = subject.iterator();
+        while (iterator.hasNext()) {
+            String next = iterator.next();
+            if (toBeRemoved.contains(next)) {
+                iterator.remove();
+            }
+        }
+        assertThat(subject).containsExactlyElementsIn(expected);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <V> List<V> listOf(V... values) {
+        List<V> list = new ArrayList<>();
+        for (V value : values) {
+            list.add(value);
+        }
+        return list;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <V> Set<V> setOf(V... values) {
+        Set<V> set = new HashSet<>();
+        for (V value : values) {
+            set.add(value);
+        }
+        return set;
+    }
+
+    private static <V> List<V> toList(Iterator<V> iterator) {
+        List<V> list = new ArrayList<>();
+        while (iterator.hasNext()) {
+            list.add(iterator.next());
+        }
+        return list;
+    }
+}
diff --git a/collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.kt b/collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.kt
deleted file mode 100644
index e9326f2..0000000
--- a/collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.kt
+++ /dev/null
@@ -1,121 +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.collection
-
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class IndexBasedArrayIteratorTest {
-
-    @Test
-    fun iterateAll() {
-        val iterator = ArraySet(setOf("a", "b", "c")).iterator()
-        assertThat(iterator.asSequence().toList()).containsExactly("a", "b", "c")
-    }
-
-    @Test
-    fun iterateEmptyList() {
-        val iterator = ArraySet<String>().iterator()
-        assertThat(iterator.hasNext()).isFalse()
-
-        assertThat(
-            runCatching {
-                iterator.next()
-            }.exceptionOrNull()
-        ).isInstanceOf(NoSuchElementException::class.java)
-    }
-
-    @Test
-    fun removeSameItemTwice() {
-        val iterator = ArraySet(listOf("a", "b", "c")).iterator()
-        iterator.next() // move to next
-        iterator.remove()
-        assertThat(
-            runCatching {
-                iterator.remove()
-            }.exceptionOrNull()
-        ).isInstanceOf(IllegalStateException::class.java)
-    }
-
-    @Test
-    fun removeLast() = removeViaIterator(
-        original = setOf("a", "b", "c"),
-        toBeRemoved = setOf("c"),
-        expected = setOf("a", "b")
-    )
-
-    @Test
-    fun removeFirst() = removeViaIterator(
-        original = setOf("a", "b", "c"),
-        toBeRemoved = setOf("a"),
-        expected = setOf("b", "c")
-    )
-
-    @Test
-    fun removeMid() = removeViaIterator(
-        original = setOf("a", "b", "c"),
-        toBeRemoved = setOf("b"),
-        expected = setOf("a", "c")
-    )
-
-    @Test
-    fun removeConsecutive() = removeViaIterator(
-        original = setOf("a", "b", "c", "d"),
-        toBeRemoved = setOf("b", "c"),
-        expected = setOf("a", "d")
-    )
-
-    @Test
-    fun removeLastTwo() = removeViaIterator(
-        original = setOf("a", "b", "c", "d"),
-        toBeRemoved = setOf("c", "d"),
-        expected = setOf("a", "b")
-    )
-
-    @Test
-    fun removeFirstTwo() = removeViaIterator(
-        original = setOf("a", "b", "c", "d"),
-        toBeRemoved = setOf("a", "b"),
-        expected = setOf("c", "d")
-    )
-
-    @Test
-    fun removeMultiple() = removeViaIterator(
-        original = setOf("a", "b", "c", "d"),
-        toBeRemoved = setOf("a", "c"),
-        expected = setOf("b", "d")
-    )
-
-    private fun removeViaIterator(
-        original: Set<String>,
-        toBeRemoved: Set<String>,
-        expected: Set<String>
-    ) {
-        val subject = ArraySet(original)
-        val iterator = subject.iterator()
-        while (iterator.hasNext()) {
-            val next = iterator.next()
-            if (next in toBeRemoved) {
-                iterator.remove()
-            }
-        }
-        assertThat(subject).containsExactlyElementsIn(expected)
-    }
-}
diff --git a/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/ComplexInteractions.kt b/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/ComplexInteractions.kt
index 223f3c9..5a44235 100644
--- a/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/ComplexInteractions.kt
+++ b/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/ComplexInteractions.kt
@@ -30,7 +30,7 @@
 import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.integration.demos.common.DemoCategory
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
@@ -120,7 +120,7 @@
         }
     Button(
         onClick = { state.value = !state.value },
-        colors = ButtonConstants.defaultButtonColors(backgroundColor = color)
+        colors = ButtonDefaults.buttonColors(backgroundColor = color)
     ) {
         Text("Click me")
     }
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index 2603144..3169b1f 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -55,7 +55,7 @@
   public final class AnimationConstants {
     field public static final int DefaultDurationMillis = 300; // 0x12c
     field public static final androidx.compose.animation.core.AnimationConstants INSTANCE;
-    field public static final int Infinite = 2147483647; // 0x7fffffff
+    field @Deprecated public static final int Infinite = 2147483647; // 0x7fffffff
   }
 
   public enum AnimationEndReason {
@@ -235,7 +235,7 @@
     method public void dispatchTime$metalava_module(long frameTimeMillis);
   }
 
-  public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
+  public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.FiniteAnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
 
@@ -257,6 +257,10 @@
     property public float absVelocityThreshold;
   }
 
+  public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+  }
+
   public interface FloatAnimationSpec extends androidx.compose.animation.core.AnimationSpec<java.lang.Float> {
     method public long getDurationMillis(float start, float end, float startVelocity);
     method public default float getEndVelocity(float start, float end, float startVelocity);
@@ -309,6 +313,15 @@
     property public final int duration;
   }
 
+  public final class InfiniteRepeatableSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+    ctor public InfiniteRepeatableSpec(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public androidx.compose.animation.core.DurationBasedAnimationSpec<T> getAnimation();
+    method public androidx.compose.animation.core.RepeatMode getRepeatMode();
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    property public final androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation;
+    property public final androidx.compose.animation.core.RepeatMode repeatMode;
+  }
+
   public final class IntPropKey implements androidx.compose.animation.core.PropKey<java.lang.Integer,androidx.compose.animation.core.AnimationVector1D> {
     ctor public IntPropKey(String label);
     ctor public IntPropKey();
@@ -405,18 +418,18 @@
     enum_constant public static final androidx.compose.animation.core.RepeatMode Reverse;
   }
 
-  @androidx.compose.runtime.Immutable public final class RepeatableSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class RepeatableSpec<T> implements androidx.compose.animation.core.FiniteAnimationSpec<T> {
     ctor public RepeatableSpec(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, androidx.compose.animation.core.RepeatMode repeatMode);
     method public androidx.compose.animation.core.DurationBasedAnimationSpec<T> getAnimation();
     method public int getIterations();
     method public androidx.compose.animation.core.RepeatMode getRepeatMode();
-    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
     property public final androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation;
     property public final int iterations;
     property public final androidx.compose.animation.core.RepeatMode repeatMode;
   }
 
-  @androidx.compose.runtime.Immutable public final class SnapSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class SnapSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
     ctor public SnapSpec(int delay);
     ctor public SnapSpec();
     method public int getDelay();
@@ -443,7 +456,7 @@
   public final class SpringSimulationKt {
   }
 
-  @androidx.compose.runtime.Immutable public final class SpringSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class SpringSpec<T> implements androidx.compose.animation.core.FiniteAnimationSpec<T> {
     ctor public SpringSpec(float dampingRatio, float stiffness, T? visibilityThreshold);
     ctor public SpringSpec();
     method public float getDampingRatio();
@@ -520,9 +533,10 @@
 
   public final class TransitionDefinitionKt {
     method public static <T> androidx.compose.animation.core.TransitionAnimation<T> createAnimation(androidx.compose.animation.core.TransitionDefinition<T>, androidx.compose.animation.core.AnimationClockObservable clock, optional T? initState);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.KeyframesSpec<T> keyframes(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig<T>,kotlin.Unit> init);
-    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
-    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> snap(optional int delayMillis);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SnapSpec<T> snap(optional int delayMillis);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SpringSpec<T> spring(optional float dampingRatio, optional float stiffness, optional T? visibilityThreshold);
     method public static <T> androidx.compose.animation.core.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.TransitionDefinition<T>,kotlin.Unit> init);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.TweenSpec<T> tween(optional int durationMillis, optional int delayMillis, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> easing);
@@ -571,7 +585,7 @@
   public final class VectorizedAnimationSpecKt {
   }
 
-  public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     method public int getDelayMillis();
     method public int getDurationMillis();
     method public default long getDurationMillis(V start, V end, V startVelocity);
@@ -579,13 +593,20 @@
     property public abstract int durationMillis;
   }
 
-  public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public interface VectorizedFiniteAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  }
+
+  public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedFloatAnimationSpec(androidx.compose.animation.core.FloatAnimationSpec anim);
     method public long getDurationMillis(V start, V end, V startVelocity);
     method public V getValue(long playTime, V start, V end, V startVelocity);
     method public V getVelocity(long playTime, V start, V end, V startVelocity);
   }
 
+  public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+    ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+  }
+
   public final class VectorizedKeyframesSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> {
     ctor public VectorizedKeyframesSpec(java.util.Map<java.lang.Integer,? extends kotlin.Pair<? extends V,? extends kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float>>> keyframes, int durationMillis, int delayMillis);
     method public int getDelayMillis();
@@ -596,7 +617,7 @@
     property public int durationMillis;
   }
 
-  public final class VectorizedRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public final class VectorizedRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedRepeatableSpec(int iterations, androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
     method public long getDurationMillis(V start, V end, V startVelocity);
     method public V getValue(long playTime, V start, V end, V startVelocity);
@@ -614,7 +635,7 @@
     property public int durationMillis;
   }
 
-  public final class VectorizedSpringSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public final class VectorizedSpringSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedSpringSpec(float dampingRatio, float stiffness, V? visibilityThreshold);
     method public float getDampingRatio();
     method public float getStiffness();
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index 2603144..3169b1f 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -55,7 +55,7 @@
   public final class AnimationConstants {
     field public static final int DefaultDurationMillis = 300; // 0x12c
     field public static final androidx.compose.animation.core.AnimationConstants INSTANCE;
-    field public static final int Infinite = 2147483647; // 0x7fffffff
+    field @Deprecated public static final int Infinite = 2147483647; // 0x7fffffff
   }
 
   public enum AnimationEndReason {
@@ -235,7 +235,7 @@
     method public void dispatchTime$metalava_module(long frameTimeMillis);
   }
 
-  public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
+  public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.FiniteAnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
 
@@ -257,6 +257,10 @@
     property public float absVelocityThreshold;
   }
 
+  public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+  }
+
   public interface FloatAnimationSpec extends androidx.compose.animation.core.AnimationSpec<java.lang.Float> {
     method public long getDurationMillis(float start, float end, float startVelocity);
     method public default float getEndVelocity(float start, float end, float startVelocity);
@@ -309,6 +313,15 @@
     property public final int duration;
   }
 
+  public final class InfiniteRepeatableSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+    ctor public InfiniteRepeatableSpec(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public androidx.compose.animation.core.DurationBasedAnimationSpec<T> getAnimation();
+    method public androidx.compose.animation.core.RepeatMode getRepeatMode();
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    property public final androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation;
+    property public final androidx.compose.animation.core.RepeatMode repeatMode;
+  }
+
   public final class IntPropKey implements androidx.compose.animation.core.PropKey<java.lang.Integer,androidx.compose.animation.core.AnimationVector1D> {
     ctor public IntPropKey(String label);
     ctor public IntPropKey();
@@ -405,18 +418,18 @@
     enum_constant public static final androidx.compose.animation.core.RepeatMode Reverse;
   }
 
-  @androidx.compose.runtime.Immutable public final class RepeatableSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class RepeatableSpec<T> implements androidx.compose.animation.core.FiniteAnimationSpec<T> {
     ctor public RepeatableSpec(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, androidx.compose.animation.core.RepeatMode repeatMode);
     method public androidx.compose.animation.core.DurationBasedAnimationSpec<T> getAnimation();
     method public int getIterations();
     method public androidx.compose.animation.core.RepeatMode getRepeatMode();
-    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
     property public final androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation;
     property public final int iterations;
     property public final androidx.compose.animation.core.RepeatMode repeatMode;
   }
 
-  @androidx.compose.runtime.Immutable public final class SnapSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class SnapSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
     ctor public SnapSpec(int delay);
     ctor public SnapSpec();
     method public int getDelay();
@@ -443,7 +456,7 @@
   public final class SpringSimulationKt {
   }
 
-  @androidx.compose.runtime.Immutable public final class SpringSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class SpringSpec<T> implements androidx.compose.animation.core.FiniteAnimationSpec<T> {
     ctor public SpringSpec(float dampingRatio, float stiffness, T? visibilityThreshold);
     ctor public SpringSpec();
     method public float getDampingRatio();
@@ -520,9 +533,10 @@
 
   public final class TransitionDefinitionKt {
     method public static <T> androidx.compose.animation.core.TransitionAnimation<T> createAnimation(androidx.compose.animation.core.TransitionDefinition<T>, androidx.compose.animation.core.AnimationClockObservable clock, optional T? initState);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.KeyframesSpec<T> keyframes(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig<T>,kotlin.Unit> init);
-    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
-    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> snap(optional int delayMillis);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SnapSpec<T> snap(optional int delayMillis);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SpringSpec<T> spring(optional float dampingRatio, optional float stiffness, optional T? visibilityThreshold);
     method public static <T> androidx.compose.animation.core.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.TransitionDefinition<T>,kotlin.Unit> init);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.TweenSpec<T> tween(optional int durationMillis, optional int delayMillis, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> easing);
@@ -571,7 +585,7 @@
   public final class VectorizedAnimationSpecKt {
   }
 
-  public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     method public int getDelayMillis();
     method public int getDurationMillis();
     method public default long getDurationMillis(V start, V end, V startVelocity);
@@ -579,13 +593,20 @@
     property public abstract int durationMillis;
   }
 
-  public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public interface VectorizedFiniteAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  }
+
+  public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedFloatAnimationSpec(androidx.compose.animation.core.FloatAnimationSpec anim);
     method public long getDurationMillis(V start, V end, V startVelocity);
     method public V getValue(long playTime, V start, V end, V startVelocity);
     method public V getVelocity(long playTime, V start, V end, V startVelocity);
   }
 
+  public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+    ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+  }
+
   public final class VectorizedKeyframesSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> {
     ctor public VectorizedKeyframesSpec(java.util.Map<java.lang.Integer,? extends kotlin.Pair<? extends V,? extends kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float>>> keyframes, int durationMillis, int delayMillis);
     method public int getDelayMillis();
@@ -596,7 +617,7 @@
     property public int durationMillis;
   }
 
-  public final class VectorizedRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public final class VectorizedRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedRepeatableSpec(int iterations, androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
     method public long getDurationMillis(V start, V end, V startVelocity);
     method public V getValue(long playTime, V start, V end, V startVelocity);
@@ -614,7 +635,7 @@
     property public int durationMillis;
   }
 
-  public final class VectorizedSpringSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public final class VectorizedSpringSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedSpringSpec(float dampingRatio, float stiffness, V? visibilityThreshold);
     method public float getDampingRatio();
     method public float getStiffness();
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 2603144..3169b1f 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -55,7 +55,7 @@
   public final class AnimationConstants {
     field public static final int DefaultDurationMillis = 300; // 0x12c
     field public static final androidx.compose.animation.core.AnimationConstants INSTANCE;
-    field public static final int Infinite = 2147483647; // 0x7fffffff
+    field @Deprecated public static final int Infinite = 2147483647; // 0x7fffffff
   }
 
   public enum AnimationEndReason {
@@ -235,7 +235,7 @@
     method public void dispatchTime$metalava_module(long frameTimeMillis);
   }
 
-  public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
+  public interface DurationBasedAnimationSpec<T> extends androidx.compose.animation.core.FiniteAnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
 
@@ -257,6 +257,10 @@
     property public float absVelocityThreshold;
   }
 
+  public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+  }
+
   public interface FloatAnimationSpec extends androidx.compose.animation.core.AnimationSpec<java.lang.Float> {
     method public long getDurationMillis(float start, float end, float startVelocity);
     method public default float getEndVelocity(float start, float end, float startVelocity);
@@ -309,6 +313,15 @@
     property public final int duration;
   }
 
+  public final class InfiniteRepeatableSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+    ctor public InfiniteRepeatableSpec(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public androidx.compose.animation.core.DurationBasedAnimationSpec<T> getAnimation();
+    method public androidx.compose.animation.core.RepeatMode getRepeatMode();
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    property public final androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation;
+    property public final androidx.compose.animation.core.RepeatMode repeatMode;
+  }
+
   public final class IntPropKey implements androidx.compose.animation.core.PropKey<java.lang.Integer,androidx.compose.animation.core.AnimationVector1D> {
     ctor public IntPropKey(String label);
     ctor public IntPropKey();
@@ -405,18 +418,18 @@
     enum_constant public static final androidx.compose.animation.core.RepeatMode Reverse;
   }
 
-  @androidx.compose.runtime.Immutable public final class RepeatableSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class RepeatableSpec<T> implements androidx.compose.animation.core.FiniteAnimationSpec<T> {
     ctor public RepeatableSpec(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, androidx.compose.animation.core.RepeatMode repeatMode);
     method public androidx.compose.animation.core.DurationBasedAnimationSpec<T> getAnimation();
     method public int getIterations();
     method public androidx.compose.animation.core.RepeatMode getRepeatMode();
-    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
+    method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
     property public final androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation;
     property public final int iterations;
     property public final androidx.compose.animation.core.RepeatMode repeatMode;
   }
 
-  @androidx.compose.runtime.Immutable public final class SnapSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class SnapSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
     ctor public SnapSpec(int delay);
     ctor public SnapSpec();
     method public int getDelay();
@@ -443,7 +456,7 @@
   public final class SpringSimulationKt {
   }
 
-  @androidx.compose.runtime.Immutable public final class SpringSpec<T> implements androidx.compose.animation.core.AnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class SpringSpec<T> implements androidx.compose.animation.core.FiniteAnimationSpec<T> {
     ctor public SpringSpec(float dampingRatio, float stiffness, T? visibilityThreshold);
     ctor public SpringSpec();
     method public float getDampingRatio();
@@ -520,9 +533,10 @@
 
   public final class TransitionDefinitionKt {
     method public static <T> androidx.compose.animation.core.TransitionAnimation<T> createAnimation(androidx.compose.animation.core.TransitionDefinition<T>, androidx.compose.animation.core.AnimationClockObservable clock, optional T? initState);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.KeyframesSpec<T> keyframes(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig<T>,kotlin.Unit> init);
-    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
-    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.AnimationSpec<T> snap(optional int delayMillis);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
+    method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SnapSpec<T> snap(optional int delayMillis);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SpringSpec<T> spring(optional float dampingRatio, optional float stiffness, optional T? visibilityThreshold);
     method public static <T> androidx.compose.animation.core.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.TransitionDefinition<T>,kotlin.Unit> init);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.TweenSpec<T> tween(optional int durationMillis, optional int delayMillis, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> easing);
@@ -571,7 +585,7 @@
   public final class VectorizedAnimationSpecKt {
   }
 
-  public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public interface VectorizedDurationBasedAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     method public int getDelayMillis();
     method public int getDurationMillis();
     method public default long getDurationMillis(V start, V end, V startVelocity);
@@ -579,13 +593,20 @@
     property public abstract int durationMillis;
   }
 
-  public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public interface VectorizedFiniteAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> extends androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  }
+
+  public final class VectorizedFloatAnimationSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedFloatAnimationSpec(androidx.compose.animation.core.FloatAnimationSpec anim);
     method public long getDurationMillis(V start, V end, V startVelocity);
     method public V getValue(long playTime, V start, V end, V startVelocity);
     method public V getVelocity(long playTime, V start, V end, V startVelocity);
   }
 
+  public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+    ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+  }
+
   public final class VectorizedKeyframesSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> {
     ctor public VectorizedKeyframesSpec(java.util.Map<java.lang.Integer,? extends kotlin.Pair<? extends V,? extends kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float>>> keyframes, int durationMillis, int delayMillis);
     method public int getDelayMillis();
@@ -596,7 +617,7 @@
     property public int durationMillis;
   }
 
-  public final class VectorizedRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public final class VectorizedRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedRepeatableSpec(int iterations, androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
     method public long getDurationMillis(V start, V end, V startVelocity);
     method public V getValue(long playTime, V start, V end, V startVelocity);
@@ -614,7 +635,7 @@
     property public int durationMillis;
   }
 
-  public final class VectorizedSpringSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
+  public final class VectorizedSpringSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> {
     ctor public VectorizedSpringSpec(float dampingRatio, float stiffness, V? visibilityThreshold);
     method public float getDampingRatio();
     method public float getStiffness();
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt
index cc0dda0..43d0a9e 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/SuspendAnimationSamples.kt
@@ -17,14 +17,13 @@
 package androidx.compose.animation.core.samples
 
 import androidx.annotation.Sampled
-import androidx.compose.animation.core.AnimationConstants
 import androidx.compose.animation.core.AnimationState
 import androidx.compose.animation.core.RepeatMode
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.animate
 import androidx.compose.animation.core.animateTo
+import androidx.compose.animation.core.infiniteRepeatable
 import androidx.compose.animation.core.isFinished
-import androidx.compose.animation.core.repeatable
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Box
@@ -82,8 +81,7 @@
             animate(
                 initialValue = 1f,
                 targetValue = 0f,
-                animationSpec = repeatable(
-                    iterations = AnimationConstants.Infinite,
+                animationSpec = infiniteRepeatable(
                     animation = tween(1000),
                     repeatMode = RepeatMode.Reverse
                 )
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt
index a457db21..e04c380 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimatedValue.kt
@@ -185,8 +185,8 @@
         }
 
         lastFrameTime = timeMillis
-        value = anim.getValue(playtime)
         velocityVector = anim.getVelocityVector(playtime)
+        value = anim.getValue(playtime)
 
         checkFinished(playtime)
     }
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
index 8c71f54..7f7b55a 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
@@ -28,8 +28,13 @@
     const val DefaultDurationMillis: Int = 300
 
     /**
-     * Used as a iterations count for [VectorizedRepeatableSpec] to create an infinity repeating animation.
+     * Used as a iterations count for [VectorizedRepeatableSpec] to create an infinity repeating
+     * animation.
      */
+    @Deprecated(
+        "Using Infinite to specify repeatable animation iterations has been " +
+            "deprecated. Please use [InfiniteRepeatableSpec] or [infiniteRepeatable] instead."
+    )
     const val Infinite: Int = Int.MAX_VALUE
 }
 
@@ -65,6 +70,19 @@
 }
 
 /**
+ * [FiniteAnimationSpec] is the interface that all non-infinite [AnimationSpec]s implement,
+ * including: [TweenSpec], [SpringSpec], [KeyframesSpec], [RepeatableSpec], [SnapSpec], etc. By
+ * definition, [InfiniteRepeatableSpec] __does not__ implement this interface.
+ *
+ * @see [InfiniteRepeatableSpec]
+ */
+interface FiniteAnimationSpec<T> : AnimationSpec<T> {
+    override fun <V : AnimationVector> vectorize(
+        converter: TwoWayConverter<T, V>
+    ): VectorizedFiniteAnimationSpec<V>
+}
+
+/**
  * Creates a TweenSpec configured with the given duration, delay, and easing curve.
  *
  * @param durationMillis duration of the [VectorizedTweenSpec] animation.
@@ -100,7 +118,7 @@
  *  [TweenSpec], and [SnapSpec]. These duration based specs can repeated when put into a
  *  [RepeatableSpec].
  */
-interface DurationBasedAnimationSpec<T> : AnimationSpec<T> {
+interface DurationBasedAnimationSpec<T> : FiniteAnimationSpec<T> {
     override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<T, V>):
         VectorizedDurationBasedAnimationSpec<V>
 }
@@ -114,12 +132,13 @@
  * @param stiffness stiffness of the spring. [Spring.StiffnessMedium] by default.
  * @param visibilityThreshold specifies the visibility threshold
  */
+// TODO: annotate damping/stiffness with FloatRange
 @Immutable
 class SpringSpec<T>(
     val dampingRatio: Float = Spring.DampingRatioNoBouncy,
     val stiffness: Float = Spring.StiffnessMedium,
     val visibilityThreshold: T? = null
-) : AnimationSpec<T> {
+) : FiniteAnimationSpec<T> {
 
     override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<T, V>) =
         VectorizedSpringSpec(dampingRatio, stiffness, converter.convert(visibilityThreshold))
@@ -146,14 +165,18 @@
 }
 
 /**
- * [RepeatableSpec] takes another [DurationBasedAnimationSpec] and plays it [iterations] times.
+ * [RepeatableSpec] takes another [DurationBasedAnimationSpec] and plays it [iterations] times. For
+ * creating infinitely repeating animation spec, consider using [InfiniteRepeatableSpec].
  *
  * __Note__: When repeating in the [RepeatMode.Reverse] mode, it's highly recommended to have an
- * __odd__ number of iterations, or [AnimationConstants.Infinite] iterations. Otherwise, the
- * animation may jump to the end value when it finishes the last iteration.
+ * __odd__ number of iterations. Otherwise, the animation may jump to the end value when it finishes
+ * the last iteration.
  *
- * @param iterations the count of iterations. Should be at least 1. [AnimationConstants.Infinite]
- *                   can be used to have an infinity repeating animation.
+ * @see repeatable
+ * @see InfiniteRepeatableSpec
+ * @see infiniteRepeatable
+ *
+ * @param iterations the count of iterations. Should be at least 1.
  * @param animation the [AnimationSpec] to be repeated
  * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
  *                  [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
@@ -163,10 +186,10 @@
     val iterations: Int,
     val animation: DurationBasedAnimationSpec<T>,
     val repeatMode: RepeatMode = RepeatMode.Restart
-) : AnimationSpec<T> {
+) : FiniteAnimationSpec<T> {
     override fun <V : AnimationVector> vectorize(
         converter: TwoWayConverter<T, V>
-    ): VectorizedAnimationSpec<V> {
+    ): VectorizedFiniteAnimationSpec<V> {
         return VectorizedRepeatableSpec(iterations, animation.vectorize(converter), repeatMode)
     }
 
@@ -185,6 +208,42 @@
 }
 
 /**
+ * [InfiniteRepeatableSpec] repeats the provided [animation] infinite amount of times. It will
+ * never naturally finish. This means the animation will only be stopped via some form of manual
+ * cancellation. When used with transition or other animation composables, the infinite animations
+ * will stop when the composable is removed from the compose tree.
+ *
+ * For non-infinite repeating animations, consider [RepeatableSpec].
+ *
+ * @param animation the [AnimationSpec] to be repeated
+ * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
+ *                  [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
+ * @see infiniteRepeatable
+ */
+// TODO: Consider supporting repeating spring specs
+class InfiniteRepeatableSpec<T>(
+    val animation: DurationBasedAnimationSpec<T>,
+    val repeatMode: RepeatMode = RepeatMode.Restart
+) : AnimationSpec<T> {
+    override fun <V : AnimationVector> vectorize(
+        converter: TwoWayConverter<T, V>
+    ): VectorizedAnimationSpec<V> {
+        return VectorizedInfiniteRepeatableSpec(animation.vectorize(converter), repeatMode)
+    }
+
+    override fun equals(other: Any?): Boolean =
+        if (other is RepeatableSpec<*>) {
+            other.animation == this.animation && other.repeatMode == this.repeatMode
+        } else {
+            false
+        }
+
+    override fun hashCode(): Int {
+        return animation.hashCode() * 31 + repeatMode.hashCode()
+    }
+}
+
+/**
  * Repeat mode for [RepeatableSpec] and [VectorizedRepeatableSpec].
  */
 enum class RepeatMode {
@@ -207,7 +266,7 @@
  *              starts. Defaults to 0.
  */
 @Immutable
-class SnapSpec<T>(val delay: Int = 0) : AnimationSpec<T> {
+class SnapSpec<T>(val delay: Int = 0) : DurationBasedAnimationSpec<T> {
     override fun <V : AnimationVector> vectorize(
         converter: TwoWayConverter<T, V>
     ): VectorizedDurationBasedAnimationSpec<V> = VectorizedSnapSpec(delay)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionDefinition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionDefinition.kt
index 91b09ba..bc77654 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionDefinition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/TransitionDefinition.kt
@@ -17,7 +17,6 @@
 package androidx.compose.animation.core
 
 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
-import androidx.compose.animation.core.AnimationConstants.Infinite
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.util.fastFirstOrNull
 
@@ -149,11 +148,11 @@
  * [TweenSpec], [KeyframesSpec]) the amount of iterations specified by [iterations].
  *
  * The iteration count describes the amount of times the animation will run.
- * 1 means no repeat. Use [Infinite] to create an infinity repeating animation.
+ * 1 means no repeat. Recommend [infiniteRepeatable] for creating an infinity repeating animation.
  *
  * __Note__: When repeating in the [RepeatMode.Reverse] mode, it's highly recommended to have an
- * __odd__ number of iterations, or [AnimationConstants.Infinite] iterations. Otherwise, the
- * animation may jump to the end value when it finishes the last iteration.
+ * __odd__ number of iterations. Otherwise, the animation may jump to the end value when it finishes
+ * the last iteration.
  *
  * @param iterations the total count of iterations, should be greater than 1 to repeat.
  * @param animation animation that will be repeated
@@ -165,16 +164,33 @@
     iterations: Int,
     animation: DurationBasedAnimationSpec<T>,
     repeatMode: RepeatMode = RepeatMode.Restart
-): AnimationSpec<T> =
+): RepeatableSpec<T> =
     RepeatableSpec(iterations, animation, repeatMode)
 
 /**
+ * Creates a [InfiniteRepeatableSpec] that plays a [DurationBasedAnimationSpec] (e.g.
+ * [TweenSpec], [KeyframesSpec]) infinite amount of iterations.
+ *
+ * For non-infinitely repeating animations, consider [repeatable].
+ *
+ * @param animation animation that will be repeated
+ * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
+ *                  [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
+ */
+@Stable
+fun <T> infiniteRepeatable(
+    animation: DurationBasedAnimationSpec<T>,
+    repeatMode: RepeatMode = RepeatMode.Restart
+): AnimationSpec<T> =
+    InfiniteRepeatableSpec(animation, repeatMode)
+
+/**
  * Creates a Snap animation for immediately switching the animating value to the end value.
  *
  * @param delayMillis the number of milliseconds to wait before the animation runs. 0 by default.
  */
 @Stable
-fun <T> snap(delayMillis: Int = 0): AnimationSpec<T> = SnapSpec(delayMillis)
+fun <T> snap(delayMillis: Int = 0) = SnapSpec<T>(delayMillis)
 
 /**
  * [TransitionDefinition] contains all the animation related configurations that will be used in
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
index fd8d44d..bd1e235 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
@@ -17,7 +17,6 @@
 package androidx.compose.animation.core
 
 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
-import androidx.compose.animation.core.AnimationConstants.Infinite
 import kotlin.math.min
 
 /**
@@ -107,13 +106,23 @@
 }
 
 /**
+ * All the finite [VectorizedAnimationSpec]s implement this interface, including:
+ * [VectorizedKeyframesSpec], [VectorizedTweenSpec], [VectorizedRepeatableSpec],
+ * [VectorizedSnapSpec], [VectorizedSpringSpec], etc. The [VectorizedAnimationSpec] that does
+ * __not__ implement this is: [InfiniteRepeatableSpec].
+ */
+interface VectorizedFiniteAnimationSpec<V : AnimationVector> : VectorizedAnimationSpec<V>
+
+/**
  * Base class for [VectorizedAnimationSpec]s that are based on a fixed [durationMillis].
  */
-interface VectorizedDurationBasedAnimationSpec<V : AnimationVector> : VectorizedAnimationSpec<V> {
+interface VectorizedDurationBasedAnimationSpec<V : AnimationVector> :
+    VectorizedFiniteAnimationSpec<V> {
     /**
      * duration is the amount of time while animation is not yet finished.
      */
     val durationMillis: Int
+
     /**
      * delay defines the amount of time that animation can be delayed.
      */
@@ -265,19 +274,41 @@
         get() = 0
 }
 
+private const val InfiniteIterations: Int = Int.MAX_VALUE
+
 /**
  * This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it
- * [iterations] times.
+ * __infinite__ times.
  *
- * @param iterations the count of iterations. Should be at least 1. [Infinite] can
- *                   be used to have an infinity repeating animation.
  * @param animation the [VectorizedAnimationSpec] describing each repetition iteration.
+ * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
+ *                  [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
+ */
+class VectorizedInfiniteRepeatableSpec<V : AnimationVector>(
+    private val animation: VectorizedDurationBasedAnimationSpec<V>,
+    private val repeatMode: RepeatMode = RepeatMode.Restart
+) : VectorizedAnimationSpec<V> by
+    VectorizedRepeatableSpec<V>(InfiniteIterations, animation, repeatMode)
+
+/**
+ * This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it
+ * [iterations] times. For infinitely repeating animation spec, [VectorizedInfiniteRepeatableSpec]
+ * is recommended.
+ *
+ * __Note__: When repeating in the [RepeatMode.Reverse] mode, it's highly recommended to have an
+ * __odd__ number of iterations. Otherwise, the animation may jump to the end value when it finishes
+ * the last iteration.
+ *
+ * @param iterations the count of iterations. Should be at least 1.
+ * @param animation the [VectorizedAnimationSpec] describing each repetition iteration.
+ * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
+ *                  [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
  */
 class VectorizedRepeatableSpec<V : AnimationVector>(
     private val iterations: Int,
     private val animation: VectorizedDurationBasedAnimationSpec<V>,
     private val repeatMode: RepeatMode = RepeatMode.Restart
-) : VectorizedAnimationSpec<V> {
+) : VectorizedFiniteAnimationSpec<V> {
 
     init {
         if (iterations < 1) {
@@ -345,15 +376,18 @@
      * Stiffness constant for extremely stiff spring
      */
     const val StiffnessHigh = 10_000f
+
     /**
      * Stiffness constant for medium stiff spring. This is the default stiffness for spring
      * force.
      */
     const val StiffnessMedium = 1500f
+
     /**
      * Stiffness constant for a spring with low stiffness.
      */
     const val StiffnessLow = 200f
+
     /**
      * Stiffness constant for a spring with very low stiffness.
      */
@@ -364,23 +398,27 @@
      * (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
      */
     const val DampingRatioHighBouncy = 0.2f
+
     /**
      * Damping ratio for a medium bouncy spring. This is also the default damping ratio for
      * spring force. Note for under-damped springs (i.e. damping ratio < 1), the lower the
      * damping ratio, the more bouncy the spring.
      */
     const val DampingRatioMediumBouncy = 0.5f
+
     /**
      * Damping ratio for a spring with low bounciness. Note for under-damped springs
      * (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
      */
     const val DampingRatioLowBouncy = 0.75f
+
     /**
      * Damping ratio for a spring with no bounciness. This damping ratio will create a
      * critically damped spring that returns to equilibrium within the shortest amount of time
      * without oscillating.
      */
     const val DampingRatioNoBouncy = 1f
+
     /**
      * Default cutoff for rounding off physics based animations
      */
@@ -401,7 +439,7 @@
     val dampingRatio: Float,
     val stiffness: Float,
     anims: Animations
-) : VectorizedAnimationSpec<V> by VectorizedFloatAnimationSpec<V>(anims) {
+) : VectorizedFiniteAnimationSpec<V> by VectorizedFloatAnimationSpec<V>(anims) {
 
     /**
      * Creates a [VectorizedSpringSpec] that uses the same spring constants (i.e. [dampingRatio] and
@@ -482,7 +520,7 @@
  */
 class VectorizedFloatAnimationSpec<V : AnimationVector> internal constructor(
     private val anims: Animations
-) : VectorizedAnimationSpec<V> {
+) : VectorizedFiniteAnimationSpec<V> {
     private lateinit var valueVector: V
     private lateinit var velocityVector: V
     private lateinit var endVelocityVector: V
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
index 671941d..6524f1f 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
@@ -104,6 +104,25 @@
         assertEquals(100f, repeatAnim.getValue(901))
     }
 
+    @Test
+    fun testInfiniteRepeat() {
+        val repeat = infiniteRepeatable(
+            animation = TweenSpec<Float>(
+                durationMillis = 100, easing = LinearEasing
+            ),
+            repeatMode = RepeatMode.Reverse
+        )
+
+        assertEquals(
+            Int.MAX_VALUE.toLong() * 100,
+            repeat.vectorize(Float.VectorConverter).getDurationMillis(
+                AnimationVector(0f),
+                AnimationVector(100f),
+                AnimationVector(0f)
+            )
+        )
+    }
+
     private companion object {
         private val DelayDuration = 13
         private val Duration = 50
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt
index 0070e96..e0aa04d 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/InfiniteAnimationDemo.kt
@@ -16,10 +16,9 @@
 
 package androidx.compose.animation.demos
 
-import androidx.compose.animation.core.AnimationConstants
 import androidx.compose.animation.core.RepeatMode
 import androidx.compose.animation.core.animate
-import androidx.compose.animation.core.repeatable
+import androidx.compose.animation.core.infiniteRepeatable
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -42,8 +41,7 @@
         animate(
             initialValue = 1f,
             targetValue = 0f,
-            animationSpec = repeatable(
-                iterations = AnimationConstants.Infinite,
+            animationSpec = infiniteRepeatable(
                 animation = tween(1000),
                 repeatMode = RepeatMode.Reverse
             )
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt
index 9d70c16..4455664 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringBackScrollingDemo.kt
@@ -42,7 +42,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.gesture.util.VelocityTracker
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.DrawScope
@@ -54,7 +53,6 @@
 import kotlinx.coroutines.launch
 import kotlin.math.roundToInt
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun SpringBackScrollingDemo() {
     Column(Modifier.fillMaxHeight()) {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SupendAnimationDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SupendAnimationDemo.kt
index 4045fb6..36e0942 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SupendAnimationDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SupendAnimationDemo.kt
@@ -37,14 +37,12 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun SuspendAnimationDemo() {
     var animStateX by remember {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt
index 1e5ef0d..19f9abd 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SwipeToDismissDemo.kt
@@ -42,7 +42,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.gesture.util.VelocityTracker
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
@@ -80,7 +79,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 private fun Modifier.swipeToDismiss(index: Int): Modifier = composed {
     val mutatorMutex = remember { MutatorMutex() }
     var alpha by remember { mutableStateOf(1f) }
diff --git a/compose/compiler/compiler-hosted/integration-tests/lint-baseline.xml b/compose/compiler/compiler-hosted/integration-tests/lint-baseline.xml
deleted file mode 100644
index db82975..0000000
--- a/compose/compiler/compiler-hosted/integration-tests/lint-baseline.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-alpha15" client="gradle" variant="debug" version="4.2.0-alpha15">
-
-    <issue
-        id="IgnoreWithoutReason"
-        message="Test is ignored without giving any explanation"
-        errorLine1="    @Ignore"
-        errorLine2="    ~~~~~~~">
-        <location
-            file="src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt"
-            line="41"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="IgnoreWithoutReason"
-        message="Test is ignored without giving any explanation"
-        errorLine1="    @Ignore"
-        errorLine2="    ~~~~~~~">
-        <location
-            file="src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt"
-            line="63"
-            column="5"/>
-    </issue>
-
-</issues>
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt
index 0cd4d99..b0eed19 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.compiler.plugins.kotlin
 
-import android.view.View
 import androidx.compose.runtime.Composer
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.snapshots.Snapshot
@@ -42,9 +41,6 @@
         )
     }
 
-    @Suppress("UNCHECKED_CAST")
-    fun View.getComposedSet(tagId: Int): Set<String>? = getTag(tagId) as? Set<String>
-
     @OptIn(ExperimentalComposeApi::class)
     protected fun execute(block: () -> Unit) {
         val scheduler = RuntimeEnvironment.getMasterScheduler()
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/CodegenMetadataTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/CodegenMetadataTests.kt
new file mode 100644
index 0000000..bacb9b6
--- /dev/null
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/CodegenMetadataTests.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.compiler.plugins.kotlin
+
+import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.junit.Test
+
+class CodegenMetadataTests : AbstractLoweringTests() {
+
+    override fun updateConfiguration(configuration: CompilerConfiguration) {
+        super.updateConfiguration(configuration)
+        configuration.put(ComposeConfiguration.LIVE_LITERALS_ENABLED_KEY, true)
+    }
+
+    @Test
+    fun testBasicFunctionality(): Unit = ensureSetup {
+        val className = "Test_${uniqueNumber++}"
+        val fileName = "$className.kt"
+        val loader = classLoader(
+            """
+            import kotlin.reflect.full.primaryConstructor
+            import kotlin.reflect.jvm.isAccessible
+            data class MyClass(val someBoolean: Boolean? = false)
+            object Main { @JvmStatic fun main() { MyClass::class.java.kotlin.primaryConstructor!!.isAccessible = true } }
+            """,
+            fileName,
+            true
+        )
+        val main = loader.loadClass("Main").methods.single { it.name == "main" }
+        main.invoke(null)
+    }
+}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallLoweringTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallLoweringTests.kt
index bf3a820..9a43019 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallLoweringTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallLoweringTests.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.compiler.plugins.kotlin
 
+import android.view.View
 import android.widget.Button
 import android.widget.LinearLayout
 import android.widget.TextView
@@ -179,13 +180,13 @@
             """
             import androidx.compose.runtime.*
 
-            @Composable val foo get() = 123
+            val foo @Composable get() = 123
 
             class A {
-                @Composable val bar get() = 123
+                val bar @Composable get() = 123
             }
 
-            @Composable val A.bam get() = 123
+            val A.bam @Composable get() = 123
 
             @Composable fun Foo() {
             }
@@ -260,13 +261,13 @@
     fun testPropertyValues(): Unit = ensureSetup {
         compose(
             """
-            @Composable val foo get() = "123"
+            val foo @Composable get() = "123"
 
             class A {
-                @Composable val bar get() = "123"
+                val bar @Composable get() = "123"
             }
 
-            @Composable val A.bam get() = "123"
+            val A.bam @Composable get() = "123"
 
             @Composable
             fun App() {
@@ -2728,6 +2729,9 @@
     }
 }
 
+@Suppress("UNCHECKED_CAST")
+fun View.getComposedSet(tagId: Int): Set<String>? = getTag(tagId) as? Set<String>
+
 private val noParameters = { emptyMap<String, String>() }
 
 private inline fun <reified T : PsiElement> PsiElement.parentOfType(): T? = parentOfType(T::class)
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallResolverTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallResolverTests.kt
index e6b6625..ba815cf 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallResolverTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposeCallResolverTests.kt
@@ -34,13 +34,13 @@
         """
             import androidx.compose.runtime.*
 
-            @Composable val foo get() = 123
+            val foo @Composable get() = 123
 
             class A {
-                @Composable val bar get() = 123
+                val bar @Composable get() = 123
             }
 
-            @Composable val A.bam get() = 123
+            val A.bam @Composable get() = 123
 
             @Composable
             fun test() {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
index ef97c5e..fa1abfc 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
@@ -855,12 +855,11 @@
     @Test
     fun testComposableTopLevelProperty(): Unit = checkApi(
         """
-            @Composable val foo: Int get() { return 123 }
+            val foo: Int @Composable get() { return 123 }
         """,
         """
             public final class TestKt {
               public final static getFoo(Landroidx/compose/runtime/Composer;I)I
-              public static synthetic getFoo%annotations()V
             }
         """
     )
@@ -869,14 +868,13 @@
     fun testComposableProperty(): Unit = checkApi(
         """
             class Foo {
-                @Composable val foo: Int get() { return 123 }
+                val foo: Int @Composable get() { return 123 }
             }
         """,
         """
             public final class Foo {
               public <init>()V
               public final getFoo(Landroidx/compose/runtime/Composer;I)I
-              public static synthetic getFoo%annotations()V
               public final static I %stable
               static <clinit>()V
             }
@@ -942,7 +940,7 @@
     @Test
     fun testCallingProperties(): Unit = checkApi(
         """
-            @Composable val bar: Int get() { return 123 }
+            val bar: Int @Composable get() { return 123 }
 
             @Composable fun Example() {
                 bar
@@ -951,7 +949,6 @@
         """
             public final class TestKt {
               public final static getBar(Landroidx/compose/runtime/Composer;I)I
-              public static synthetic getBar%annotations()V
               final static INNERCLASS TestKt%Example%1 null null
               public final static Example(Landroidx/compose/runtime/Composer;I)V
             }
@@ -1201,8 +1198,7 @@
     @Test
     fun testDexNaming(): Unit = checkApi(
         """
-            @Composable
-            val myProperty: () -> Unit get() {
+            val myProperty: () -> Unit @Composable get() {
                 return {  }
             }
         """,
@@ -1210,7 +1206,6 @@
             public final class TestKt {
               final static INNERCLASS TestKt%myProperty%1 null null
               public final static getMyProperty(Landroidx/compose/runtime/Composer;I)Lkotlin/jvm/functions/Function0;
-              public static synthetic getMyProperty%annotations()V
             }
             final class TestKt%myProperty%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {
               <init>()V
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
index 07fa0354..6228e46 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
@@ -55,7 +55,7 @@
     @Test
     fun testCallingProperties(): Unit = composerParam(
         """
-            @Composable val bar: Int get() { return 123 }
+            val bar: Int @Composable get() { return 123 }
 
             @ComposableContract(restartable = false) @Composable fun Example() {
                 bar
@@ -289,8 +289,7 @@
     @Test
     fun testDexNaming(): Unit = composerParam(
         """
-            @Composable
-            val myProperty: () -> Unit get() {
+            val myProperty: () -> Unit @Composable get() {
                 return {  }
             }
         """,
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt
index 30e6dbf..93459df 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt
@@ -447,7 +447,7 @@
                     import androidx.compose.runtime.Composable
 
                     class Foo {
-                      @Composable val value: Int get() = 123
+                      val value: Int @Composable get() = 123
                     }
                  """
                 ),
@@ -687,7 +687,7 @@
 
                     import androidx.compose.runtime.*
 
-                    @Composable val foo: Int get() { return 123 }
+                    val foo: Int @Composable get() { return 123 }
                  """
                 ),
                 "Main" to mapOf(
@@ -740,6 +740,35 @@
     }
 
     @Test
+    fun testXModuleComposableProperty(): Unit = ensureSetup {
+        compile(
+            mapOf(
+                "library module" to mapOf(
+                    "a/Foo.kt" to """
+                    package a
+
+                    import androidx.compose.runtime.*
+
+                    val foo: () -> Unit
+                        @Composable get() = {}
+                 """
+                ),
+                "Main" to mapOf(
+                    "B.kt" to """
+                    import a.foo
+                    import androidx.compose.runtime.*
+
+                    @Composable fun Example() {
+                        val bar = foo
+                        bar()
+                    }
+                """
+                )
+            )
+        )
+    }
+
+    @Test
     fun testXModuleCtorComposableParam(): Unit = ensureSetup {
         compile(
             mapOf(
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt
index 800bd34..9e9ab11 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt
@@ -38,7 +38,7 @@
         configuration.put(ComposeConfiguration.LIVE_LITERALS_ENABLED_KEY, true)
     }
 
-    @Ignore
+    @Ignore("Live literals are currently disabled by default")
     @Test
     fun testBasicFunctionality(): Unit = ensureSetup {
         compose(
@@ -60,7 +60,7 @@
         }
     }
 
-    @Ignore
+    @Ignore("Live literals are currently disabled by default")
     @Test
     fun testObjectFieldsLoweredToStaticFields(): Unit = ensureSetup {
         validateBytecode(
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
index b05df3c..3b124e8 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
@@ -140,7 +140,7 @@
         """
         import androidx.compose.runtime.*
         @Composable fun C(): Int { return 123 }
-        @Composable val cProp: Int get() = C()
+        val cProp: Int @Composable get() = C()
     """
     )
 
@@ -157,7 +157,7 @@
         import androidx.compose.runtime.*
         @Composable fun C(): Int { return 123 }
         val ncProp: Int = <!COMPOSABLE_INVOCATION!>C<!>()
-        @Composable val <!COMPOSABLE_PROPERTY_BACKING_FIELD!>cProp<!>: Int = <!COMPOSABLE_INVOCATION!>C<!>()
+        @Composable val <!COMPOSABLE_PROPERTY_BACKING_FIELD,DEPRECATED_COMPOSABLE_PROPERTY!>cProp<!>: Int = <!COMPOSABLE_INVOCATION!>C<!>()
     """
     )
 
@@ -868,7 +868,7 @@
             """
             import androidx.compose.runtime.*;
 
-            @Composable val foo: Int get() = 123
+            val foo: Int @Composable get() = 123
 
             fun <!COMPOSABLE_EXPECTED!>App<!>() {
                 <!COMPOSABLE_INVOCATION!>foo<!>
@@ -879,7 +879,7 @@
             """
             import androidx.compose.runtime.*;
 
-            @Composable val foo: Int get() = 123
+            val foo: Int @Composable  get() = 123
 
             @Composable
             fun App() {
@@ -927,10 +927,10 @@
             import androidx.compose.runtime.*;
 
             class A {
-                @Composable val bar get() = 123
+                val bar @Composable get() = 123
             }
 
-            @Composable val A.bam get() = 123
+            val A.bam @Composable get() = 123
 
             @Composable
             fun App() {
@@ -966,7 +966,7 @@
 
             @Composable fun Foo() {}
 
-            @Composable val bam: Int get() {
+            val bam: Int @Composable get() {
                 Foo()
                 return 123
             }
@@ -1003,7 +1003,7 @@
                 val x = object {
                   val <!COMPOSABLE_EXPECTED!>a<!> get() =
                   <!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(2) }
-                  @Composable val c get() = remember { mutableStateOf(4) }
+                  val c @Composable get() = remember { mutableStateOf(4) }
                   @Composable fun bar() { Foo() }
                   fun <!COMPOSABLE_EXPECTED!>foo<!>() {
                     <!COMPOSABLE_INVOCATION!>Foo<!>()
@@ -1012,7 +1012,7 @@
                 class Bar {
                   val <!COMPOSABLE_EXPECTED!>b<!> get() =
                   <!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(6) }
-                  @Composable val c get() = remember { mutableStateOf(7) }
+                  val c @Composable get() = remember { mutableStateOf(7) }
                 }
                 fun <!COMPOSABLE_EXPECTED!>Bam<!>() {
                     <!COMPOSABLE_INVOCATION!>Foo<!>()
@@ -1036,7 +1036,7 @@
             @Composable fun App() {
                 val x = object {
                   val <!COMPOSABLE_EXPECTED!>a<!> get() = <!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(2) }
-                  @Composable val c get() = remember { mutableStateOf(4) }
+                  val c @Composable get() = remember { mutableStateOf(4) }
                   fun <!COMPOSABLE_EXPECTED!>foo<!>() {
                     <!COMPOSABLE_INVOCATION!>Foo<!>()
                   }
@@ -1044,7 +1044,7 @@
                 }
                 class Bar {
                   val <!COMPOSABLE_EXPECTED!>b<!> get() = <!COMPOSABLE_INVOCATION!>remember<!> { mutableStateOf(6) }
-                  @Composable val c get() = remember { mutableStateOf(7) }
+                  val c @Composable get() = remember { mutableStateOf(7) }
                 }
                 fun <!COMPOSABLE_EXPECTED!>Bam<!>() {
                     <!COMPOSABLE_INVOCATION!>Foo<!>()
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt
index c63dffc..4463850 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt
@@ -29,7 +29,10 @@
             import androidx.compose.runtime.Composable
 
             @Composable
-            val <!COMPOSABLE_PROPERTY_BACKING_FIELD!>foo<!>: Int = 123
+            val <!DEPRECATED_COMPOSABLE_PROPERTY,COMPOSABLE_PROPERTY_BACKING_FIELD!>foo<!>: Int = 123
+
+            val <!COMPOSABLE_PROPERTY_BACKING_FIELD!>bar<!>: Int = 123
+                @Composable get() = field
         """
         )
     }
@@ -78,7 +81,9 @@
             import androidx.compose.runtime.Composable
 
             @Composable
-            val bar: Int get() = 123
+            val <!DEPRECATED_COMPOSABLE_PROPERTY!>bar<!>: Int get() = 123
+
+            val foo: Int @Composable get() = 123
         """
         )
     }
@@ -89,9 +94,67 @@
             import androidx.compose.runtime.Composable
 
             @Composable
-            var <!COMPOSABLE_VAR!>bam<!>: Int 
+            var <!DEPRECATED_COMPOSABLE_PROPERTY, COMPOSABLE_VAR!>bam<!>: Int
                 get() { return 123 }
                 set(value) { print(value) }
+
+            var <!COMPOSABLE_VAR!>bam2<!>: Int
+                @Composable get() { return 123 }
+                set(value) { print(value) }
+
+            var <!COMPOSABLE_VAR!>bam3<!>: Int
+                @Composable get() { return 123 }
+                <!WRONG_ANNOTATION_TARGET!>@Composable<!> set(value) { print(value) }
+
+            var <!COMPOSABLE_VAR!>bam4<!>: Int
+                get() { return 123 }
+                <!WRONG_ANNOTATION_TARGET!>@Composable<!> set(value) { print(value) }
+        """
+        )
+    }
+
+    fun testPropertyGetterAllForms() {
+        doTest(
+            """
+            import androidx.compose.runtime.Composable
+
+            @Composable val <!DEPRECATED_COMPOSABLE_PROPERTY!>bar1<!>: Int get() = 123
+            val bar2: Int @Composable get() = 123
+            @get:Composable val bar3: Int get() = 123
+
+            interface Foo {
+                @Composable val <!DEPRECATED_COMPOSABLE_PROPERTY!>bar1<!>: Int get() = 123
+                val bar2: Int @Composable get() = 123
+                @get:Composable val bar3: Int get() = 123
+            }
+        """
+        )
+    }
+
+    fun testMarkedPropInOverrideMarkedGetter() {
+        doTest(
+            """
+            import androidx.compose.runtime.Composable
+            interface A {
+                val foo: Int @Composable get() = 123
+            }
+            class Impl : A {
+                @Composable override val <!DEPRECATED_COMPOSABLE_PROPERTY!>foo<!>: Int get() = 123
+            }
+        """
+        )
+    }
+
+    fun testMarkedGetterInOverrideMarkedProp() {
+        doTest(
+            """
+            import androidx.compose.runtime.Composable
+            interface A {
+                @Composable val <!DEPRECATED_COMPOSABLE_PROPERTY!>foo<!>: Int get() = 123
+            }
+            class Impl : A {
+                override val foo: Int @Composable get() = 123
+            }
         """
         )
     }
@@ -128,16 +191,31 @@
                 @Composable
                 fun composableFunction(param: Boolean): Boolean
                 @Composable
-                val composableProperty: Boolean
+                val <!DEPRECATED_COMPOSABLE_PROPERTY!>composableProperty<!>: Boolean
                 fun nonComposableFunction(param: Boolean): Boolean
                 val nonComposableProperty: Boolean
             }
 
             object FakeFoo : Foo {
                 <!CONFLICTING_OVERLOADS!>override fun composableFunction(param: Boolean)<!> = true
-                <!CONFLICTING_OVERLOADS!>override val composableProperty: Boolean<!> get() = true
+                <!CONFLICTING_OVERLOADS!>override val composableProperty: Boolean<!> <!CONFLICTING_OVERLOADS!>get()<!> = true
                 <!CONFLICTING_OVERLOADS!>@Composable override fun nonComposableFunction(param: Boolean)<!> = true
-                <!CONFLICTING_OVERLOADS!>@Composable override val nonComposableProperty: Boolean<!> get() = true
+                <!CONFLICTING_OVERLOADS!>@Composable override val <!DEPRECATED_COMPOSABLE_PROPERTY!>nonComposableProperty<!>: Boolean<!> <!CONFLICTING_OVERLOADS!>get()<!> = true
+            }
+
+            interface Bar {
+                @Composable
+                fun composableFunction(param: Boolean): Boolean
+                val composableProperty: Boolean @Composable get()
+                fun nonComposableFunction(param: Boolean): Boolean
+                val nonComposableProperty: Boolean
+            }
+
+            object FakeBar : Bar {
+                <!CONFLICTING_OVERLOADS!>override fun composableFunction(param: Boolean)<!> = true
+                <!CONFLICTING_OVERLOADS!>override val composableProperty: Boolean<!> <!CONFLICTING_OVERLOADS!>get()<!> = true
+                <!CONFLICTING_OVERLOADS!>@Composable override fun nonComposableFunction(param: Boolean)<!> = true
+                <!CONFLICTING_OVERLOADS!>override val nonComposableProperty: Boolean<!> <!CONFLICTING_OVERLOADS!>@Composable get()<!> = true
             }
         """
         )
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
index 37784c8..6bd17a8 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
@@ -68,6 +68,8 @@
 import org.jetbrains.kotlin.types.upperIfFlexible
 import org.jetbrains.kotlin.util.OperatorNameConventions
 
+internal const val COMPOSABLE_PROPERTIES = true
+
 open class ComposableCallChecker :
     CallChecker,
     AdditionalTypeChecker,
@@ -187,7 +189,11 @@
                 }
                 is KtPropertyAccessor -> {
                     val property = node.property
-                    if (!property.annotationEntries.hasComposableAnnotation(bindingContext)) {
+                    val isComposable = node
+                        .annotationEntries.hasComposableAnnotation(bindingContext)
+                    val propertyIsComposable = property
+                        .annotationEntries.hasComposableAnnotation(bindingContext)
+                    if (!(isComposable || COMPOSABLE_PROPERTIES && propertyIsComposable)) {
                         illegalCall(context, reportOn, property.nameIdentifier ?: property)
                     }
                     return
@@ -329,19 +335,32 @@
     return when (candidateDescriptor) {
         is ValueParameterDescriptor -> false
         is LocalVariableDescriptor -> false
-        is PropertyDescriptor -> candidateDescriptor.hasComposableAnnotation()
-        is PropertyGetterDescriptor ->
-            candidateDescriptor.correspondingProperty.hasComposableAnnotation()
+        is PropertyDescriptor -> {
+            val isGetter = valueArguments.isEmpty()
+            val getter = candidateDescriptor.getter
+            if (isGetter && getter != null) {
+                getter.hasComposableAnnotation() ||
+                    (COMPOSABLE_PROPERTIES && candidateDescriptor.hasComposableAnnotation())
+            } else {
+                false
+            }
+        }
+        is PropertyGetterDescriptor -> candidateDescriptor.hasComposableAnnotation() || (
+            COMPOSABLE_PROPERTIES && candidateDescriptor.correspondingProperty
+                .hasComposableAnnotation()
+            )
         else -> candidateDescriptor.hasComposableAnnotation()
     }
 }
 
 internal fun CallableDescriptor.isMarkedAsComposable(): Boolean {
     return when (this) {
-        is PropertyGetterDescriptor -> correspondingProperty.hasComposableAnnotation()
+        is PropertyGetterDescriptor -> hasComposableAnnotation() || (
+            COMPOSABLE_PROPERTIES && correspondingProperty.hasComposableAnnotation()
+            )
         is ValueParameterDescriptor -> type.hasComposableAnnotation()
         is LocalVariableDescriptor -> type.hasComposableAnnotation()
-        is PropertyDescriptor -> hasComposableAnnotation()
+        is PropertyDescriptor -> COMPOSABLE_PROPERTIES && hasComposableAnnotation()
         else -> hasComposableAnnotation()
     }
 }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt
index 8e592b6..bf9bce7 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt
@@ -26,6 +26,7 @@
 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
 import org.jetbrains.kotlin.descriptors.FunctionDescriptor
 import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
 import org.jetbrains.kotlin.descriptors.PropertyDescriptor
 import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
 import org.jetbrains.kotlin.platform.TargetPlatform
@@ -33,6 +34,7 @@
 import org.jetbrains.kotlin.psi.KtDeclaration
 import org.jetbrains.kotlin.psi.KtFunction
 import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtPropertyAccessor
 import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
 import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
 import org.jetbrains.kotlin.types.KotlinType
@@ -53,8 +55,15 @@
         context: DeclarationCheckerContext
     ) {
         when {
-            declaration is KtProperty &&
+            COMPOSABLE_PROPERTIES &&
+                declaration is KtProperty &&
                 descriptor is PropertyDescriptor -> checkProperty(declaration, descriptor, context)
+            declaration is KtPropertyAccessor &&
+                descriptor is PropertyAccessorDescriptor -> checkPropertyAccessor(
+                declaration,
+                descriptor,
+                context
+            )
             declaration is KtFunction &&
                 descriptor is FunctionDescriptor -> checkFunction(declaration, descriptor, context)
         }
@@ -112,9 +121,58 @@
         context: DeclarationCheckerContext
     ) {
         val hasComposableAnnotation = descriptor.hasComposableAnnotation()
+        val thisIsComposable = hasComposableAnnotation || descriptor
+            .getter
+            ?.hasComposableAnnotation() == true
         if (descriptor.overriddenDescriptors.isNotEmpty()) {
             val override = descriptor.overriddenDescriptors.first()
-            if (override.hasComposableAnnotation() != hasComposableAnnotation) {
+            val overrideIsComposable = override.hasComposableAnnotation() ||
+                override.getter?.hasComposableAnnotation() == true
+            if (overrideIsComposable != thisIsComposable) {
+                context.trace.report(
+                    ComposeErrors.CONFLICTING_OVERLOADS.on(
+                        declaration,
+                        listOf(descriptor, override)
+                    )
+                )
+            }
+        }
+        if (hasComposableAnnotation) {
+            context.trace.report(
+                ComposeErrors.DEPRECATED_COMPOSABLE_PROPERTY.on(
+                    declaration.nameIdentifier ?: declaration
+                )
+            )
+        }
+        if (!hasComposableAnnotation) return
+        val initializer = declaration.initializer
+        val name = declaration.nameIdentifier
+        if (initializer != null && name != null) {
+            context.trace.report(COMPOSABLE_PROPERTY_BACKING_FIELD.on(name))
+        }
+        if (descriptor.isVar && name != null) {
+            context.trace.report(COMPOSABLE_VAR.on(name))
+        }
+    }
+
+    private fun checkPropertyAccessor(
+        declaration: KtPropertyAccessor,
+        descriptor: PropertyAccessorDescriptor,
+        context: DeclarationCheckerContext
+    ) {
+        val propertyDescriptor = descriptor.correspondingProperty
+        val propertyPsi = declaration.parent as? KtProperty ?: return
+        val name = propertyPsi.nameIdentifier
+        val initializer = propertyPsi.initializer
+        val hasComposableAnnotation = descriptor.hasComposableAnnotation()
+        val propertyHasComposableAnnotation = COMPOSABLE_PROPERTIES && propertyDescriptor
+            .hasComposableAnnotation()
+        val thisComposable = hasComposableAnnotation || propertyHasComposableAnnotation
+        if (descriptor.overriddenDescriptors.isNotEmpty()) {
+            val override = descriptor.overriddenDescriptors.first()
+            val overrideComposable = override.hasComposableAnnotation() || override
+                .correspondingProperty.hasComposableAnnotation()
+            if (overrideComposable != thisComposable) {
                 context.trace.report(
                     ComposeErrors.CONFLICTING_OVERLOADS.on(
                         declaration,
@@ -124,12 +182,10 @@
             }
         }
         if (!hasComposableAnnotation) return
-        val initializer = declaration.initializer
-        val name = declaration.nameIdentifier
         if (initializer != null && name != null) {
             context.trace.report(COMPOSABLE_PROPERTY_BACKING_FIELD.on(name))
         }
-        if (descriptor.isVar && name != null) {
+        if (propertyDescriptor.isVar && name != null) {
             context.trace.report(COMPOSABLE_VAR.on(name))
         }
     }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt
index 2d62be3..e0e887a 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrorMessages.kt
@@ -90,5 +90,10 @@
             RENDER_TYPE_WITH_ANNOTATIONS,
             RENDER_TYPE_WITH_ANNOTATIONS
         )
+        MAP.put(
+            ComposeErrors.DEPRECATED_COMPOSABLE_PROPERTY,
+            "@Composable properties should be declared with the @Composable annotation " +
+                "on the getter, and not the property itself."
+        )
     }
 }
\ No newline at end of file
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt
index b416082..8ef2b83 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeErrors.kt
@@ -85,6 +85,10 @@
         )
 
     @JvmField
+    var DEPRECATED_COMPOSABLE_PROPERTY: DiagnosticFactory0<PsiElement> =
+        DiagnosticFactory0.create(Severity.WARNING)
+
+    @JvmField
     val ILLEGAL_ASSIGN_TO_UNIONTYPE =
         DiagnosticFactory2.create<KtExpression, Collection<KotlinType>, Collection<KotlinType>>(
             Severity.ERROR
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index b55b131..0a3c446 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -27,7 +27,10 @@
 import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
 import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
 import org.jetbrains.kotlin.config.CompilerConfigurationKey
+import org.jetbrains.kotlin.config.KotlinCompilerVersion
 import org.jetbrains.kotlin.extensions.internal.TypeResolutionInterceptor
 import org.jetbrains.kotlin.serialization.DescriptorSerializer
 
@@ -38,6 +41,8 @@
         CompilerConfigurationKey<Boolean>("Include source information in generated code")
     val INTRINSIC_REMEMBER_OPTIMIZATION_ENABLED_KEY =
         CompilerConfigurationKey<Boolean>("Enable optimization to treat remember as an intrinsic")
+    val SUPPRESS_KOTLIN_VERSION_COMPATIBILITY_CHECK =
+        CompilerConfigurationKey<Boolean>("Suppress Kotlin version compatibility check")
 }
 
 class ComposeCommandLineProcessor : CommandLineProcessor {
@@ -64,13 +69,21 @@
             required = false,
             allowMultipleOccurrences = false
         )
+        val SUPPRESS_KOTLIN_VERSION_CHECK_ENABLED_OPTION = CliOption(
+            "suppressKotlinVersionCompatibilityCheck",
+            "<true|false>",
+            "Suppress Kotlin version compatibility check",
+            required = false,
+            allowMultipleOccurrences = false
+        )
     }
 
     override val pluginId = PLUGIN_ID
     override val pluginOptions = listOf(
         LIVE_LITERALS_ENABLED_OPTION,
         SOURCE_INFORMATION_ENABLED_OPTION,
-        INTRINSIC_REMEMBER_OPTIMIZATION_ENABLED_OPTION
+        INTRINSIC_REMEMBER_OPTIMIZATION_ENABLED_OPTION,
+        SUPPRESS_KOTLIN_VERSION_CHECK_ENABLED_OPTION
     )
 
     override fun processOption(
@@ -90,6 +103,10 @@
             ComposeConfiguration.INTRINSIC_REMEMBER_OPTIMIZATION_ENABLED_KEY,
             value == "true"
         )
+        SUPPRESS_KOTLIN_VERSION_CHECK_ENABLED_OPTION -> configuration.put(
+            ComposeConfiguration.SUPPRESS_KOTLIN_VERSION_COMPATIBILITY_CHECK,
+            value == "true"
+        )
         else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
     }
 }
@@ -112,6 +129,26 @@
             project: Project,
             configuration: CompilerConfiguration
         ) {
+            val KOTLIN_VERSION_EXPECTATION = "1.4.20"
+            KotlinCompilerVersion.getVersion()?.let { version ->
+                val suppressKotlinVersionCheck = configuration.get(
+                    ComposeConfiguration.SUPPRESS_KOTLIN_VERSION_COMPATIBILITY_CHECK,
+                    false
+                )
+                if (!suppressKotlinVersionCheck && version != KOTLIN_VERSION_EXPECTATION) {
+                    val msgCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
+                    msgCollector?.report(
+                        CompilerMessageSeverity.ERROR,
+                        "This version (${VersionChecker.compilerVersion}) of the Compose" +
+                            " Compiler requires Kotlin version $KOTLIN_VERSION_EXPECTATION but" +
+                            " you appear to be using Kotlin version $version which is not known" +
+                            " to be compatible.  Please fix your configuration (or" +
+                            " `suppressKotlinVersionCompatibilityCheck` but don't say I didn't" +
+                            " warn you!)."
+                    )
+                }
+            }
+
             val liveLiteralsEnabled = configuration.get(
                 ComposeConfiguration.LIVE_LITERALS_ENABLED_KEY,
                 false
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index b12654e..2d8e6da 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -23,31 +23,33 @@
 
 class VersionChecker(val context: IrPluginContext) {
 
-    /**
-     * A table of version ints to version strings. This should be updated every time
-     * ComposeVersion.kt is updated.
-     */
-    private val versionTable = mapOf(
-        1600 to "0.1.0-dev16",
-        1700 to "1.0.0-alpha06",
-        1800 to "1.0.0-alpha07",
-        1900 to "1.0.0-alpha08",
-        2000 to "1.0.0-alpha09"
-    )
+    companion object {
+        /**
+         * A table of version ints to version strings. This should be updated every time
+         * ComposeVersion.kt is updated.
+         */
+        private val versionTable = mapOf(
+            1600 to "0.1.0-dev16",
+            1700 to "1.0.0-alpha06",
+            1800 to "1.0.0-alpha07",
+            1900 to "1.0.0-alpha08",
+            2000 to "1.0.0-alpha09"
+        )
 
-    /**
-     * The minimum version int that this compiler is guaranteed to be compatible with. Typically
-     * this will match the version int that is in ComposeVersion.kt in the runtime.
-     */
-    private val minimumRuntimeVersionInt: Int = 2000
+        /**
+         * The minimum version int that this compiler is guaranteed to be compatible with. Typically
+         * this will match the version int that is in ComposeVersion.kt in the runtime.
+         */
+        private val minimumRuntimeVersionInt: Int = 2000
 
-    /**
-     * The maven version string of this compiler. This string should be updated before/after every
-     * release.
-     */
-    private val compilerVersion: String = "1.0.0-alpha09"
-    private val minimumRuntimeVersion: String
-        get() = versionTable[minimumRuntimeVersionInt] ?: "unknown"
+        /**
+         * The maven version string of this compiler. This string should be updated before/after every
+         * release.
+         */
+        val compilerVersion: String = "1.0.0-alpha09"
+        private val minimumRuntimeVersion: String
+            get() = versionTable[minimumRuntimeVersionInt] ?: "unknown"
+    }
 
     fun check() {
         val versionClass = context.referenceClass(ComposeFqNames.ComposeVersion)
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
index d45ddd1..4bdb3e6 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
@@ -88,6 +88,10 @@
         return super.visitFunction(declaration).also { it.copyMetadataFrom(declaration) }
     }
 
+    override fun visitConstructor(declaration: IrConstructor): IrConstructor {
+        return super.visitConstructor(declaration).also { it.copyMetadataFrom(declaration) }
+    }
+
     override fun visitSimpleFunction(declaration: IrSimpleFunction): IrSimpleFunction {
         return super.visitSimpleFunction(declaration).also {
             it.correspondingPropertySymbol = declaration.correspondingPropertySymbol
@@ -284,6 +288,7 @@
             is IrPropertyImpl -> metadata = owner.metadata
             is IrFunction -> metadata = owner.metadata
             is IrClassImpl -> metadata = owner.metadata
+            else -> throw Error("Unknown type: $this")
         }
     }
 
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt
index d9cdd91..e4c97a5 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt
@@ -356,7 +356,16 @@
         ).also { fn ->
             newDescriptor.bind(fn)
             if (this is IrSimpleFunction) {
-                fn.correspondingPropertySymbol = correspondingPropertySymbol
+                val propertySymbol = correspondingPropertySymbol
+                if (propertySymbol != null) {
+                    fn.correspondingPropertySymbol = propertySymbol
+                    if (propertySymbol.owner.getter == this) {
+                        propertySymbol.owner.getter = fn
+                    }
+                    if (propertySymbol.owner.setter == this) {
+                        propertySymbol.owner.setter = this
+                    }
+                }
             }
             fn.parent = parent
             fn.typeParameters = this.typeParameters.map {
diff --git a/compose/desktop/desktop/build.gradle b/compose/desktop/desktop/build.gradle
index 5a631af..328931fc 100644
--- a/compose/desktop/desktop/build.gradle
+++ b/compose/desktop/desktop/build.gradle
@@ -21,6 +21,7 @@
 import androidx.build.SupportConfigKt
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
+import static androidx.build.AndroidXPlugin.BUILD_ON_SERVER_TASK
 import static androidx.build.dependencies.DependenciesKt.*
 
 plugins {
@@ -124,6 +125,7 @@
     }
 }
 
-rootProject.tasks.getByName("buildOnServer").configure {
-    dependsOn(":compose:desktop:desktop:jar")
+def projectPath = project.path
+rootProject.tasks.named(BUILD_ON_SERVER_TASK).configure {
+    dependsOn("$projectPath:jvmJar")
 }
\ No newline at end of file
diff --git a/compose/desktop/desktop/samples/build.gradle b/compose/desktop/desktop/samples/build.gradle
index 12c04c9..f81a739 100644
--- a/compose/desktop/desktop/samples/build.gradle
+++ b/compose/desktop/desktop/samples/build.gradle
@@ -18,6 +18,7 @@
 
 import androidx.build.SupportConfigKt
 
+import static androidx.build.AndroidXPlugin.BUILD_ON_SERVER_TASK
 import static androidx.build.dependencies.DependenciesKt.*
 
 plugins {
@@ -91,3 +92,8 @@
 task run {
     dependsOn("run1")
 }
+
+def projectPath = project.path
+rootProject.tasks.named(BUILD_ON_SERVER_TASK).configure {
+    dependsOn("$projectPath:jvmJar")
+}
\ No newline at end of file
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
index 23699a4..441f608 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
@@ -46,7 +46,7 @@
 import androidx.compose.foundation.text.appendInlineContent
 import androidx.compose.material.BottomAppBar
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Checkbox
 import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.material.ExtendedFloatingActionButton
@@ -133,7 +133,7 @@
                     IconButton(
                         onClick = {}
                     ) {
-                        Icon(Icons.Filled.Menu, Modifier.size(ButtonConstants.DefaultIconSize))
+                        Icon(Icons.Filled.Menu, Modifier.size(ButtonDefaults.IconSize))
                     }
                 }
             },
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
index 163625a..2dc8318 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/popupexample/AppContent.kt
@@ -36,7 +36,7 @@
 import androidx.compose.material.DropdownMenuItem
 import androidx.compose.material.Text
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Checkbox
 import androidx.compose.material.RadioButton
 import androidx.compose.material.Surface
@@ -317,7 +317,7 @@
     val buttonHover = remember { mutableStateOf(false) }
     Button(
         onClick = onClick,
-        colors = ButtonConstants.defaultButtonColors(
+        colors = ButtonDefaults.buttonColors(
             backgroundColor =
                 if (buttonHover.value)
                     Color(color.red / 1.3f, color.green / 1.3f, color.blue / 1.3f)
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt
index fb477e1..bfb6073 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.kt
@@ -22,31 +22,31 @@
 import androidx.compose.foundation.background
 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.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.Text
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Button
 import androidx.compose.material.Surface
+import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
 import java.awt.BorderLayout
 import java.awt.Dimension
 import java.awt.event.ActionEvent
 import java.awt.event.ActionListener
-import javax.swing.JFrame
 import javax.swing.JButton
+import javax.swing.JFrame
 import javax.swing.WindowConstants
 
 val northClicks = mutableStateOf(0)
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index 540542a..7f48fdd 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -93,7 +93,7 @@
   public final class BoxKt {
     method @androidx.compose.runtime.Composable public static inline void Box(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void Box(androidx.compose.ui.Modifier modifier);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.node.LayoutNode.MeasureBlocks rememberMeasureBlocks(androidx.compose.ui.Alignment alignment);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.node.MeasureBlocks rememberMeasureBlocks(androidx.compose.ui.Alignment alignment);
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface BoxScope {
@@ -121,8 +121,8 @@
 
   public final class ColumnKt {
     method @androidx.compose.runtime.Composable public static inline void Column(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.node.LayoutNode.MeasureBlocks columnMeasureBlocks(androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, androidx.compose.ui.Alignment.Horizontal horizontalAlignment);
-    field @kotlin.PublishedApi internal static final androidx.compose.ui.node.LayoutNode.MeasureBlocks DefaultColumnMeasureBlocks;
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.node.MeasureBlocks columnMeasureBlocks(androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, androidx.compose.ui.Alignment.Horizontal horizontalAlignment);
+    field @kotlin.PublishedApi internal static final androidx.compose.ui.node.MeasureBlocks DefaultColumnMeasureBlocks;
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface ColumnScope {
@@ -417,13 +417,13 @@
   }
 
   public final class RowColumnImplKt {
-    method @kotlin.PublishedApi internal static androidx.compose.ui.node.LayoutNode.MeasureBlocks rowColumnMeasureBlocks-GZ6WFlY(androidx.compose.foundation.layout.LayoutOrientation orientation, kotlin.jvm.functions.Function5<? super java.lang.Integer,? super int[],? super androidx.compose.ui.unit.LayoutDirection,? super androidx.compose.ui.unit.Density,? super int[],kotlin.Unit> arrangement, float arrangementSpacing, androidx.compose.foundation.layout.SizeMode crossAxisSize, androidx.compose.foundation.layout.CrossAxisAlignment crossAxisAlignment);
+    method @kotlin.PublishedApi internal static androidx.compose.ui.node.MeasureBlocks rowColumnMeasureBlocks-GZ6WFlY(androidx.compose.foundation.layout.LayoutOrientation orientation, kotlin.jvm.functions.Function5<? super java.lang.Integer,? super int[],? super androidx.compose.ui.unit.LayoutDirection,? super androidx.compose.ui.unit.Density,? super int[],kotlin.Unit> arrangement, float arrangementSpacing, androidx.compose.foundation.layout.SizeMode crossAxisSize, androidx.compose.foundation.layout.CrossAxisAlignment crossAxisAlignment);
   }
 
   public final class RowKt {
     method @androidx.compose.runtime.Composable public static inline void Row(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.node.LayoutNode.MeasureBlocks rowMeasureBlocks(androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, androidx.compose.ui.Alignment.Vertical verticalAlignment);
-    field @kotlin.PublishedApi internal static final androidx.compose.ui.node.LayoutNode.MeasureBlocks DefaultRowMeasureBlocks;
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.node.MeasureBlocks rowMeasureBlocks(androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, androidx.compose.ui.Alignment.Vertical verticalAlignment);
+    field @kotlin.PublishedApi internal static final androidx.compose.ui.node.MeasureBlocks DefaultRowMeasureBlocks;
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface RowScope {
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 0d9b4e1..af73d79 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -118,7 +118,6 @@
 
 tasks.withType(KotlinCompile).configureEach {
     kotlinOptions {
-        freeCompilerArgs += ["-XXLanguage:-NewInference"]
         useIR = true
     }
 }
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt
index 7a9fe53..089ff61 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt
@@ -70,7 +70,7 @@
         }
         assertTrue(positionedLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(root.width, root.height), alignSize.value)
@@ -111,7 +111,7 @@
         }
         assertTrue(positionedLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(root.width, root.height), alignSize.value)
@@ -157,7 +157,7 @@
         }
         assertTrue(positionedLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -231,7 +231,7 @@
         }
         assertTrue(positionedLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(size, size), alignSize.value)
@@ -472,7 +472,7 @@
         }
         positionedLatch.await(1, TimeUnit.SECONDS)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(childSizeIpx, childSizeIpx), childSize.value)
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutPaddingTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutPaddingTest.kt
index 06c69bc..cd5004b 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutPaddingTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutPaddingTest.kt
@@ -298,7 +298,7 @@
             }
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val rootWidth = root.width
@@ -361,7 +361,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootWidth = root.width
 
@@ -451,7 +451,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val innerSize = (size - paddingPx * 2)
@@ -499,7 +499,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val paddingLeft = left.toIntPx()
@@ -553,7 +553,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(0, 0), childSize)
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt
index 2fdccc7..722150b 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt
@@ -36,7 +36,7 @@
 import androidx.compose.ui.node.Ref
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.AmbientDensity
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
@@ -95,22 +95,22 @@
         activityTestRule.runOnUiThread(runnable)
     }
 
-    internal fun findOwnerView(): View {
-        return findOwner(activity).view
+    internal fun findComposeView(): View {
+        return findViewRootForTest(activity).view
     }
 
-    internal fun findOwner(activity: Activity): AndroidOwner {
+    internal fun findViewRootForTest(activity: Activity): ViewRootForTest {
         val contentViewGroup = activity.findViewById<ViewGroup>(android.R.id.content)
-        return findOwner(contentViewGroup)!!
+        return findViewRootForTest(contentViewGroup)!!
     }
 
-    internal fun findOwner(parent: ViewGroup): AndroidOwner? {
+    internal fun findViewRootForTest(parent: ViewGroup): ViewRootForTest? {
         for (index in 0 until parent.childCount) {
             val child = parent.getChildAt(index)
-            if (child is AndroidOwner) {
+            if (child is ViewRootForTest) {
                 return child
             } else if (child is ViewGroup) {
-                val owner = findOwner(child)
+                val owner = findViewRootForTest(child)
                 if (owner != null) {
                     return owner
                 }
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
index bd823de..b9a80bf 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
@@ -109,7 +109,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(size, size), childSize[0])
@@ -161,7 +161,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootWidth = root.width
 
@@ -218,7 +218,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(childrenWidth, childrenHeight), childSize[0])
@@ -263,7 +263,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(size, size), childSize[0])
@@ -315,7 +315,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootHeight = root.height
 
@@ -369,7 +369,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(childrenWidth, childrenHeight), childSize[0])
@@ -624,7 +624,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(size, root.height), childSize[0])
@@ -682,7 +682,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootHeight = root.height
 
@@ -755,7 +755,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootHeight = root.height
 
@@ -976,7 +976,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(IntSize(root.width, size), childSize[0])
@@ -1035,7 +1035,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootWidth = root.width
 
@@ -1104,7 +1104,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootWidth = root.width
 
@@ -1286,7 +1286,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1316,7 +1316,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1355,7 +1355,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1385,7 +1385,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1415,7 +1415,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1448,7 +1448,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1490,7 +1490,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1523,7 +1523,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1556,7 +1556,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1589,7 +1589,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1638,7 +1638,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1756,7 +1756,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1786,7 +1786,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1825,7 +1825,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1855,7 +1855,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1885,7 +1885,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1918,7 +1918,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1960,7 +1960,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -1993,7 +1993,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -2026,7 +2026,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -2059,7 +2059,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -2109,7 +2109,7 @@
         }
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -2244,7 +2244,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(Offset(0f, 0f), childPosition[0])
@@ -2290,7 +2290,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(Offset((root.width - size.toFloat() * 3), 0f), childPosition[0])
@@ -2336,7 +2336,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val extraSpace = root.width - size * 3
@@ -2392,7 +2392,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width - size.toFloat() * 3f) / 4f
@@ -2447,7 +2447,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width - size.toFloat() * 3) / 2
@@ -2500,7 +2500,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width.toFloat() - size * 3) / 3
@@ -2705,7 +2705,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(Offset(0f, 0f), childPosition[0])
@@ -2751,7 +2751,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(Offset(0f, (root.height - size.toFloat() * 3)), childPosition[0])
@@ -2797,7 +2797,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val extraSpace = root.height - size * 3f
@@ -2856,7 +2856,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.height - size.toFloat() * 3) / 4
@@ -2920,7 +2920,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.height - size.toFloat() * 3f) / 2f
@@ -2973,7 +2973,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.height - size.toFloat() * 3f) / 3f
@@ -4047,7 +4047,7 @@
         }
 
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootWidth = root.width
 
@@ -4096,7 +4096,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val extraSpace = root.width - size * 3
@@ -4152,7 +4152,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width - size.toFloat() * 3f) / 4f
@@ -4207,7 +4207,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width - size.toFloat() * 3) / 2
@@ -4260,7 +4260,7 @@
 
         calculateChildPositions(childPosition, parentLayoutCoordinates, childLayoutCoordinates)
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width.toFloat() - size * 3) / 3
@@ -4388,7 +4388,7 @@
         }
 
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootWidth = root.width
 
@@ -4475,7 +4475,7 @@
         }
 
         assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
         val rootWidth = root.width
 
@@ -4535,7 +4535,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(Offset(0f, 0f), childPosition[0])
@@ -4590,7 +4590,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(Offset(0f, 0f), childPosition[0])
@@ -4642,7 +4642,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -4703,7 +4703,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         assertEquals(
@@ -4761,7 +4761,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val extraSpace = root.width - size * 3
@@ -4832,7 +4832,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val extraSpace = root.width - size * 3
@@ -4900,7 +4900,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width - size.toFloat() * 3f) / 4f
@@ -4969,7 +4969,7 @@
                 childLayoutCoordinates
             )
 
-            val root = findOwnerView()
+            val root = findComposeView()
             waitForDraw(root)
 
             val gap = (root.width - size.toFloat() * 3f) / 4f
@@ -5034,7 +5034,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width - size.toFloat() * 3) / 2
@@ -5101,7 +5101,7 @@
                 childLayoutCoordinates
             )
 
-            val root = findOwnerView()
+            val root = findComposeView()
             waitForDraw(root)
 
             val gap = (root.width - size.toFloat() * 3) / 2
@@ -5163,7 +5163,7 @@
             childLayoutCoordinates
         )
 
-        val root = findOwnerView()
+        val root = findComposeView()
         waitForDraw(root)
 
         val gap = (root.width.toFloat() - size * 3) / 3
@@ -5233,7 +5233,7 @@
                 childLayoutCoordinates
             )
 
-            val root = findOwnerView()
+            val root = findComposeView()
             waitForDraw(root)
 
             val gap = (root.width.toFloat() - size * 3) / 3
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
index 9589035..36f9646 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
@@ -27,14 +27,14 @@
 import kotlin.math.roundToInt
 
 /**
- * Used to specify the arrangement of the layout's children in [Row] or [Column] in the main axis
- * direction (horizontal and vertical, respectively).
+ * Used to specify the arrangement of the layout's children in layouts like [Row] or [Column] in
+ * the main axis direction (horizontal and vertical, respectively).
  */
 @Immutable
 @OptIn(InternalLayoutApi::class)
 object Arrangement {
     /**
-     * Used to specify the horizontal arrangement of the layout's children in a [Row].
+     * Used to specify the horizontal arrangement of the layout's children in layouts like [Row].
      */
     @InternalLayoutApi
     @Immutable
@@ -45,7 +45,7 @@
         val spacing get() = 0.dp
 
         /**
-         * Horizontally places the layout children inside the [Row].
+         * Horizontally places the layout children.
          *
          * @param totalSize Available space that can be occupied by the children.
          * @param size An array of sizes of all children.
@@ -64,7 +64,7 @@
     }
 
     /**
-     * Used to specify the vertical arrangement of the layout's children in a [Column].
+     * Used to specify the vertical arrangement of the layout's children in layouts like [Column].
      */
     @InternalLayoutApi
     @Immutable
@@ -75,7 +75,7 @@
         val spacing get() = 0.dp
 
         /**
-         * Vertically places the layout children inside the [Column].
+         * Vertically places the layout children.
          *
          * @param totalSize Available space that can be occupied by the children.
          * @param size An array of sizes of all children.
@@ -91,8 +91,9 @@
     }
 
     /**
-     * Used to specify the horizontal arrangement of the layout's children in a [Row], or
-     * the vertical arrangement of the layout's children in a [Column].
+     * Used to specify the horizontal arrangement of the layout's children in horizontal layouts
+     * like [Row], or the vertical arrangement of the layout's children in vertical layouts like
+     * [Column].
      */
     @InternalLayoutApi
     @Immutable
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Box.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Box.kt
index bff5c98..44c0b52 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Box.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Box.kt
@@ -27,8 +27,7 @@
 import androidx.compose.ui.layout.MeasuringIntrinsicsMeasureBlocks
 import androidx.compose.ui.layout.ParentDataModifier
 import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.node.MeasureBlocks
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.platform.NoInspectorInfo
@@ -57,7 +56,6 @@
  * @param content The content of the [Box].
  */
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class)
 inline fun Box(
     modifier: Modifier = Modifier,
     contentAlignment: Alignment = Alignment.TopStart,
@@ -83,8 +81,7 @@
     }
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
-internal val DefaultBoxMeasureBlocks: LayoutNode.MeasureBlocks =
+internal val DefaultBoxMeasureBlocks: MeasureBlocks =
     boxMeasureBlocks(Alignment.TopStart)
 
 internal fun boxMeasureBlocks(alignment: Alignment) =
@@ -189,12 +186,10 @@
  * @param modifier The modifier to be applied to the layout.
  */
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class)
 fun Box(modifier: Modifier) {
     Layout({}, measureBlocks = EmptyBoxMeasureBlocks, modifier = modifier)
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal val EmptyBoxMeasureBlocks = MeasuringIntrinsicsMeasureBlocks { _, constraints ->
     layout(constraints.minWidth, constraints.minHeight) {}
 }
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
index 44482f2..7e7cb6f 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
@@ -25,7 +25,6 @@
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.Measured
 import androidx.compose.ui.layout.VerticalAlignmentLine
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.util.annotation.FloatRange
 
@@ -66,7 +65,7 @@
  * @see [androidx.compose.foundation.lazy.LazyColumn]
  */
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class, InternalLayoutApi::class)
+@OptIn(InternalLayoutApi::class)
 inline fun Column(
     modifier: Modifier = Modifier,
     verticalArrangement: Arrangement.Vertical = Arrangement.Top,
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
index 2db44b37..e1f4aec 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
@@ -26,7 +26,6 @@
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Measured
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.util.annotation.FloatRange
 
@@ -67,7 +66,7 @@
  * @see [androidx.compose.foundation.lazy.LazyRow]
  */
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class, InternalLayoutApi::class)
+@OptIn(InternalLayoutApi::class)
 inline fun Row(
     modifier: Modifier = Modifier,
     horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index c13016b..bfa199b 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -28,8 +28,7 @@
 import androidx.compose.ui.layout.ParentDataModifier
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.measureBlocksOf
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.node.MeasureBlocks
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.unit.Constraints
@@ -43,14 +42,13 @@
 import kotlin.math.sign
 
 @PublishedApi
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal fun rowColumnMeasureBlocks(
     orientation: LayoutOrientation,
     arrangement: (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit,
     arrangementSpacing: Dp,
     crossAxisSize: SizeMode,
     crossAxisAlignment: CrossAxisAlignment
-): LayoutNode.MeasureBlocks {
+): MeasureBlocks {
     fun Placeable.mainAxisSize() =
         if (orientation == LayoutOrientation.Horizontal) width else height
 
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 338bc24..542f765 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -232,18 +232,18 @@
   }
 
   public final class DragGestureDetectorKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitHorizontalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitHorizontalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitVerticalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitVerticalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectVerticalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onVerticalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? drag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? horizontalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? verticalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? awaitDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitHorizontalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitHorizontalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitVerticalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitVerticalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectVerticalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onVerticalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? drag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? horizontalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? verticalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
   }
 
   public final class DraggableKt {
@@ -251,7 +251,7 @@
   }
 
   public final class ForEachGestureKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public final class GestureCancellationException extends java.util.concurrent.CancellationException {
@@ -265,7 +265,7 @@
     method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
     method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
     method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onRotate, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onPan, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public interface PressGestureScope extends androidx.compose.ui.unit.Density {
@@ -293,9 +293,9 @@
   }
 
   public final class TapGestureDetectorKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.HandlePointerInputScope, optional boolean requireUnconsumed, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, kotlin.jvm.functions.Function0<kotlin.Unit> onTap, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.HandlePointerInputScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.HandlePointerInputScope, optional boolean requireUnconsumed, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, kotlin.jvm.functions.Function0<kotlin.Unit> onTap, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.HandlePointerInputScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
   }
 
   public final class ZoomableController {
@@ -320,15 +320,15 @@
   }
 
   public final class LazyDslKt {
-    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
   }
 
   public final class LazyForKt {
-    method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   public final class LazyGridKt {
@@ -343,6 +343,9 @@
   public final class LazyListKt {
   }
 
+  public final class LazyListMeasureKt {
+  }
+
   public interface LazyListScope {
     method public void item(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public <T> void items(java.util.List<? extends T> items, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
@@ -462,8 +465,8 @@
   }
 
   public final class BasicTextFieldKt {
-    method @androidx.compose.runtime.Composable public static void BasicTextField-3T4ULwA(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor);
-    method @androidx.compose.runtime.Composable public static void BasicTextField-kNtltWE(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor);
+    method @androidx.compose.runtime.Composable public static void BasicTextField-3Gdz-6o(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+    method @androidx.compose.runtime.Composable public static void BasicTextField-UWLcGC4(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
   }
 
   public final class BasicTextKt {
@@ -472,7 +475,7 @@
   }
 
   public final class CoreTextFieldKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-lVEleRQ(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
+    method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-Q2a5TJk(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
   }
 
   public final class CoreTextKt {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 338bc24..542f765 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -232,18 +232,18 @@
   }
 
   public final class DragGestureDetectorKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitHorizontalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitHorizontalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitVerticalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitVerticalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectVerticalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onVerticalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? drag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? horizontalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? verticalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? awaitDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitHorizontalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitHorizontalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitVerticalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitVerticalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectVerticalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onVerticalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? drag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? horizontalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? verticalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
   }
 
   public final class DraggableKt {
@@ -251,7 +251,7 @@
   }
 
   public final class ForEachGestureKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public final class GestureCancellationException extends java.util.concurrent.CancellationException {
@@ -265,7 +265,7 @@
     method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
     method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
     method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onRotate, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onPan, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public interface PressGestureScope extends androidx.compose.ui.unit.Density {
@@ -293,9 +293,9 @@
   }
 
   public final class TapGestureDetectorKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.HandlePointerInputScope, optional boolean requireUnconsumed, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, kotlin.jvm.functions.Function0<kotlin.Unit> onTap, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.HandlePointerInputScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.HandlePointerInputScope, optional boolean requireUnconsumed, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, kotlin.jvm.functions.Function0<kotlin.Unit> onTap, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.HandlePointerInputScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
   }
 
   public final class ZoomableController {
@@ -320,15 +320,15 @@
   }
 
   public final class LazyDslKt {
-    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
   }
 
   public final class LazyForKt {
-    method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   public final class LazyGridKt {
@@ -343,6 +343,9 @@
   public final class LazyListKt {
   }
 
+  public final class LazyListMeasureKt {
+  }
+
   public interface LazyListScope {
     method public void item(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public <T> void items(java.util.List<? extends T> items, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
@@ -462,8 +465,8 @@
   }
 
   public final class BasicTextFieldKt {
-    method @androidx.compose.runtime.Composable public static void BasicTextField-3T4ULwA(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor);
-    method @androidx.compose.runtime.Composable public static void BasicTextField-kNtltWE(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor);
+    method @androidx.compose.runtime.Composable public static void BasicTextField-3Gdz-6o(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+    method @androidx.compose.runtime.Composable public static void BasicTextField-UWLcGC4(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
   }
 
   public final class BasicTextKt {
@@ -472,7 +475,7 @@
   }
 
   public final class CoreTextFieldKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-lVEleRQ(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
+    method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-Q2a5TJk(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
   }
 
   public final class CoreTextKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 338bc24..542f765 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -232,18 +232,18 @@
   }
 
   public final class DragGestureDetectorKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitHorizontalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitHorizontalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitVerticalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitVerticalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectVerticalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onVerticalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? drag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? horizontalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? verticalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? awaitDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitHorizontalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitHorizontalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitVerticalDragOrCancellation-3UZYup8(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitVerticalTouchSlopOrCancellation-s7qLkbw(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onTouchSlopReached, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? detectDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectHorizontalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onHorizontalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectVerticalDragGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragEnd, optional kotlin.jvm.functions.Function0<kotlin.Unit> onDragCancel, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputChange,? super java.lang.Float,kotlin.Unit> onVerticalDrag, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? drag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? horizontalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
+    method public static suspend Object? verticalDrag-xpXNQDM(androidx.compose.ui.input.pointer.HandlePointerInputScope, long pointerId, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.pointer.PointerInputChange,kotlin.Unit> onDrag, kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
   }
 
   public final class DraggableKt {
@@ -251,7 +251,7 @@
   }
 
   public final class ForEachGestureKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public final class GestureCancellationException extends java.util.concurrent.CancellationException {
@@ -265,7 +265,7 @@
     method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
     method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
     method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onRotate, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onPan, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public interface PressGestureScope extends androidx.compose.ui.unit.Density {
@@ -293,9 +293,9 @@
   }
 
   public final class TapGestureDetectorKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.HandlePointerInputScope, optional boolean requireUnconsumed, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, kotlin.jvm.functions.Function0<kotlin.Unit> onTap, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.HandlePointerInputScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? awaitFirstDown(androidx.compose.ui.input.pointer.HandlePointerInputScope, optional boolean requireUnconsumed, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
+    method public static suspend Object? detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleTap, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongPress, optional kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onPress, kotlin.jvm.functions.Function0<kotlin.Unit> onTap, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.HandlePointerInputScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
   }
 
   public final class ZoomableController {
@@ -320,15 +320,15 @@
   }
 
   public final class LazyDslKt {
-    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyListScope,kotlin.Unit> content);
   }
 
   public final class LazyForKt {
-    method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   public final class LazyGridKt {
@@ -343,6 +343,9 @@
   public final class LazyListKt {
   }
 
+  public final class LazyListMeasureKt {
+  }
+
   public interface LazyListScope {
     method public void item(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public <T> void items(java.util.List<? extends T> items, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
@@ -462,8 +465,8 @@
   }
 
   public final class BasicTextFieldKt {
-    method @androidx.compose.runtime.Composable public static void BasicTextField-3T4ULwA(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor);
-    method @androidx.compose.runtime.Composable public static void BasicTextField-kNtltWE(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor);
+    method @androidx.compose.runtime.Composable public static void BasicTextField-3Gdz-6o(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+    method @androidx.compose.runtime.Composable public static void BasicTextField-UWLcGC4(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
   }
 
   public final class BasicTextKt {
@@ -472,7 +475,7 @@
   }
 
   public final class CoreTextFieldKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-lVEleRQ(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
+    method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-Q2a5TJk(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
   }
 
   public final class CoreTextKt {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index 33ca738..b44a23a 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -35,6 +35,7 @@
         ComposableDemo("Multiple-interaction InteractionState") {
             MultipleInteractionStateSample()
         },
-        DemoCategory("Suspending Gesture Detectors", CoroutineGestureDemos)
+        DemoCategory("Suspending Gesture Detectors", CoroutineGestureDemos),
+        ComposableDemo("NestedScroll") { NestedScrollDemo() },
     )
 )
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
index f804404..ec4fd27 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
@@ -18,8 +18,11 @@
 
 import androidx.compose.foundation.Interaction
 import androidx.compose.foundation.InteractionState
+import androidx.compose.foundation.ScrollableColumn
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ExperimentalLayout
@@ -28,6 +31,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.preferredWidth
 import androidx.compose.foundation.layout.size
@@ -38,6 +42,7 @@
 import androidx.compose.foundation.lazy.LazyRowFor
 import androidx.compose.foundation.lazy.LazyRowForIndexed
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.material.AmbientContentColor
@@ -49,6 +54,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.savedinstancestate.savedInstanceState
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -73,6 +79,9 @@
     ComposableDemo("Rtl list") { RtlListDemo() },
     ComposableDemo("LazyColumn DSL") { LazyColumnScope() },
     ComposableDemo("LazyRow DSL") { LazyRowScope() },
+    ComposableDemo("Arrangements") { LazyListArrangements() },
+    ComposableDemo("Reverse scroll direction") { ReverseLayout() },
+    ComposableDemo("Nested lazy lists") { NestedLazyDemo() },
     PagingDemos
 )
 
@@ -185,7 +194,7 @@
 }
 
 @Composable
-fun Button(modifier: Modifier, onClick: () -> Unit, content: @Composable () -> Unit) {
+fun Button(modifier: Modifier = Modifier, onClick: () -> Unit, content: @Composable () -> Unit) {
     Box(
         modifier
             .clickable(onClick = onClick)
@@ -297,4 +306,163 @@
             }
         }
     }
-}
\ No newline at end of file
+}
+
+@Composable
+private fun LazyListArrangements() {
+    var count by remember { mutableStateOf(3) }
+    var arrangement by remember { mutableStateOf(6) }
+    Column {
+        Row {
+            Button(onClick = { count-- }) {
+                Text("--")
+            }
+            Button(onClick = { count++ }) {
+                Text("++")
+            }
+            Button(
+                onClick = {
+                    arrangement++
+                    if (arrangement == Arrangements.size) {
+                        arrangement = 0
+                    }
+                }
+            ) {
+                Text("Next")
+            }
+            Text("$arrangement ${Arrangements[arrangement]}")
+        }
+        Row {
+            val item = @Composable {
+                Box(
+                    Modifier
+                        .height(200.dp)
+                        .fillMaxWidth()
+                        .background(Color.Red)
+                        .border(1.dp, Color.Cyan)
+                )
+            }
+            ScrollableColumn(
+                verticalArrangement = Arrangements[arrangement],
+                modifier = Modifier.weight(1f).fillMaxHeight()
+            ) {
+                (1..count).forEach {
+                    item()
+                }
+            }
+            LazyColumn(
+                verticalArrangement = Arrangements[arrangement],
+                modifier = Modifier.weight(1f).fillMaxHeight()
+            ) {
+                items((1..count).toList()) {
+                    item()
+                }
+            }
+        }
+    }
+}
+
+private val Arrangements = listOf(
+    Arrangement.Center,
+    Arrangement.Top,
+    Arrangement.Bottom,
+    Arrangement.SpaceAround,
+    Arrangement.SpaceBetween,
+    Arrangement.SpaceEvenly,
+    Arrangement.spacedBy(40.dp),
+    Arrangement.spacedBy(40.dp, Alignment.Bottom),
+)
+
+@Composable
+fun ReverseLayout() {
+    Column {
+        val scrollState = rememberScrollState()
+        val lazyState = rememberLazyListState()
+        var count by remember { mutableStateOf(3) }
+        var reverse by remember { mutableStateOf(true) }
+        Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+            Button(onClick = { count -= 5 }) {
+                Text("--")
+            }
+            Button(onClick = { count += 5 }) {
+                Text("++")
+            }
+            Button(onClick = { reverse = !reverse }) {
+                Text("=!")
+            }
+            Text("Scroll=${scrollState.value.toInt()}")
+            Text(
+                "Lazy=${lazyState.firstVisibleItemIndex}; " +
+                    "${lazyState.firstVisibleItemScrollOffset}"
+            )
+        }
+        Row {
+            val item1 = @Composable { index: Int ->
+                Text(
+                    "$index",
+                    Modifier
+                        .height(200.dp)
+                        .fillMaxWidth()
+                        .background(Color.Red)
+                        .border(1.dp, Color.Cyan)
+                )
+            }
+            val item2 = @Composable { index: Int ->
+                Text("After $index")
+            }
+            ScrollableColumn(
+                reverseScrollDirection = reverse,
+                verticalArrangement = if (reverse) Arrangement.Bottom else Arrangement.Top,
+                scrollState = scrollState,
+                modifier = Modifier.weight(1f).fillMaxHeight()
+            ) {
+                if (reverse) {
+                    (count downTo 1).forEach {
+                        item2(it)
+                        item1(it)
+                    }
+                } else {
+                    (1..count).forEach {
+                        item1(it)
+                        item2(it)
+                    }
+                }
+            }
+            LazyColumn(
+                reverseLayout = reverse,
+                state = lazyState,
+                modifier = Modifier.weight(1f).fillMaxHeight()
+            ) {
+                items((1..count).toList()) {
+                    item1(it)
+                    item2(it)
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun NestedLazyDemo() {
+    val item = @Composable { index: Int ->
+        Box(
+            Modifier.padding(16.dp).size(200.dp).background(Color.LightGray),
+            contentAlignment = Alignment.Center
+        ) {
+            var state by savedInstanceState { 0 }
+            Button(onClick = { state++ }) {
+                Text("Index=$index State=$state")
+            }
+        }
+    }
+    LazyColumn {
+        item {
+            LazyRowFor(List(100) { it }) {
+                item(it)
+            }
+        }
+        items(List(100) { it }) {
+            item(it)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/NestedScrollDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/NestedScrollDemos.kt
new file mode 100644
index 0000000..b4fdf7b
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/NestedScrollDemos.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos
+
+import androidx.compose.foundation.ScrollableColumn
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Text
+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 androidx.compose.ui.unit.sp
+
+@Composable
+fun NestedScrollDemo() {
+    ScrollableColumn(
+        Modifier.fillMaxSize().background(Color.Red),
+        contentPadding = PaddingValues(30.dp)
+    ) {
+        repeat(6) { outerOuterIndex ->
+            LazyColumn(
+                modifier = Modifier.fillMaxSize().border(3.dp, Color.Black).height(350.dp)
+                    .background(Color.Yellow),
+                contentPadding = PaddingValues(60.dp)
+            ) {
+                repeat(3) { outerIndex ->
+                    item {
+                        ScrollableColumn(
+                            Modifier.fillMaxSize().border(3.dp, Color.Blue).height(150.dp)
+                                .background(Color.White),
+                            contentPadding = PaddingValues(30.dp)
+                        ) {
+                            repeat(6) { innerIndex ->
+                                Box(
+                                    Modifier
+                                        .height(38.dp)
+                                        .fillMaxWidth()
+                                        .background(Color.Magenta)
+                                        .border(2.dp, Color.Yellow),
+                                    contentAlignment = Alignment.Center
+                                ) {
+                                    Text(
+                                        text = "$outerOuterIndex : $outerIndex : $innerIndex",
+                                        fontSize = 24.sp
+                                    )
+                                }
+                            }
+                        }
+                    }
+
+                    item {
+                        Spacer(Modifier.height(5.dp))
+                    }
+                }
+            }
+            Spacer(Modifier.height(5.dp))
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
index 3076180..a16da6a 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.demos
 
+import android.graphics.Matrix
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
@@ -46,13 +47,12 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.consumeAllChanges
@@ -62,7 +62,9 @@
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import kotlin.math.atan2
 import kotlin.math.roundToInt
+import kotlin.math.sqrt
 import kotlin.random.Random
 
 val CoroutineGestureDemos = listOf(
@@ -104,7 +106,6 @@
 /**
  * Gesture detector for tap, double-tap, and long-press.
  */
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun CoroutineTapDemo() {
     var tapHue by remember { mutableStateOf(randomHue()) }
@@ -209,7 +210,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun TouchSlopDragGestures() {
     Column {
@@ -287,7 +287,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun OrientationLockDragGestures() {
     var size by remember { mutableStateOf(IntSize.Zero) }
@@ -332,7 +331,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun Drag2DGestures() {
     var size by remember { mutableStateOf(IntSize.Zero) }
@@ -362,24 +360,41 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun MultitouchArea(
     text: String,
     gestureDetector: suspend PointerInputScope.(
-        (angle: Float) -> Unit,
-        (zoom: Float) -> Unit,
-        (pan: Offset) -> Unit
+        (centroid: Offset, pan: Offset, zoom: Float, angle: Float) -> Unit,
     ) -> Unit
 ) {
+    val matrix by remember { mutableStateOf(Matrix()) }
     var angle by remember { mutableStateOf(0f) }
     var zoom by remember { mutableStateOf(1f) }
-    val offsetX = remember { mutableStateOf(0f) }
-    val offsetY = remember { mutableStateOf(0f) }
+    var offsetX by remember { mutableStateOf(0f) }
+    var offsetY by remember { mutableStateOf(0f) }
 
-    Box(Modifier.fillMaxSize()) {
+    Box(
+        Modifier.fillMaxSize().pointerInput {
+            gestureDetector { centroid, pan, gestureZoom, gestureAngle ->
+                val anchorX = centroid.x - size.width / 2f
+                val anchorY = centroid.y - size.height / 2f
+                matrix.postRotate(gestureAngle, anchorX, anchorY)
+                matrix.postScale(gestureZoom, gestureZoom, anchorX, anchorY)
+                matrix.postTranslate(pan.x, pan.y)
+
+                val v = FloatArray(9)
+                matrix.getValues(v)
+                offsetX = v[Matrix.MTRANS_X]
+                offsetY = v[Matrix.MTRANS_Y]
+                val scaleX = v[Matrix.MSCALE_X]
+                val skewY = v[Matrix.MSKEW_Y]
+                zoom = sqrt(scaleX * scaleX + skewY * skewY)
+                angle = atan2(v[Matrix.MSKEW_X], v[Matrix.MSCALE_X]) * (-180 / Math.PI.toFloat())
+            }
+        }
+    ) {
         Box(
-            Modifier.offset({ offsetX.value }, { offsetY.value })
+            Modifier.offset({ offsetX }, { offsetY })
                 .graphicsLayer(
                     scaleX = zoom,
                     scaleY = zoom,
@@ -408,15 +423,6 @@
                             drawRect(color = color, topLeft = topLeft, size = rectangleSize)
                         }
                     }
-                }.pointerInput {
-                    gestureDetector(
-                        { angle += it },
-                        { zoom *= it },
-                        {
-                            offsetX.value += it.x
-                            offsetY.value += it.y
-                        }
-                    )
                 }
                 .fillMaxSize()
         )
@@ -428,17 +434,14 @@
  * This is a multi-touch gesture detector, including pan, zoom, and rotation.
  * The user can pan, zoom, and rotate once touch slop has been reached.
  */
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun MultitouchGestureDetector() {
     MultitouchArea(
         "Zoom, Pan, and Rotate"
-    ) { onRotate, onZoom, onPan ->
+    ) {
         detectMultitouchGestures(
             panZoomLock = false,
-            onRotate = onRotate,
-            onZoom = onZoom,
-            onPan = onPan
+            onGesture = it
         )
     }
 }
@@ -448,17 +451,14 @@
  * It is common to want to lean toward zoom over rotation, so this gesture detector will
  * lock into zoom if the first unless the rotation passes touch slop first.
  */
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 fun MultitouchLockGestureDetector() {
     MultitouchArea(
         "Zoom, Pan, and Rotate Locking to Zoom"
-    ) { onRotate, onZoom, onPan ->
+    ) {
         detectMultitouchGestures(
             panZoomLock = true,
-            onRotate = onRotate,
-            onZoom = onZoom,
-            onPan = onPan
+            onGesture = it
         )
     }
 }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
index ed21e47..68d298b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
@@ -16,13 +16,17 @@
 
 package androidx.compose.foundation.demos.text
 
+import androidx.compose.foundation.Interaction
+import androidx.compose.foundation.InteractionState
 import androidx.compose.foundation.ScrollableColumn
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.material.Text
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.savedinstancestate.savedInstanceState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -32,6 +36,7 @@
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.OffsetMap
 import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.input.TransformedText
 import androidx.compose.ui.text.input.VisualTransformation
 import androidx.compose.ui.text.intl.LocaleList
@@ -231,6 +236,8 @@
                 style = TextStyle(fontSize = fontSize8)
             )
         }
+        TagLine(tag = "TextField InteractionState")
+        InteractionStateTextField()
     }
 }
 
@@ -274,4 +281,24 @@
             content()
         }
     }
+}
+
+@Composable
+private fun InteractionStateTextField() {
+    val state = savedInstanceState(saver = TextFieldValue.Saver) { TextFieldValue() }
+    val interactionState = remember { InteractionState() }
+
+    Column(demoTextFieldModifiers) {
+        Text("Pressed?: ${interactionState.contains(Interaction.Pressed)}", fontSize = fontSize4)
+        Text("Focused?: ${interactionState.contains(Interaction.Focused)}", fontSize = fontSize4)
+        Text("Dragged?: ${interactionState.contains(Interaction.Dragged)}", fontSize = fontSize4)
+        BasicTextField(
+            modifier = Modifier.fillMaxWidth(),
+            value = state.value,
+            singleLine = true,
+            interactionState = interactionState,
+            onValueChange = { state.value = it },
+            textStyle = TextStyle(fontSize = fontSize8)
+        )
+    }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt
index 4cd2ade..85f8cb8 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt
@@ -48,7 +48,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.consumePositionChange
 import androidx.compose.ui.input.pointer.pointerInput
@@ -57,7 +56,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun AwaitHorizontalDragOrCancellationSample() {
@@ -102,7 +100,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun HorizontalDragSample() {
@@ -146,7 +143,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun DetectHorizontalDragGesturesSample() {
@@ -174,7 +170,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun AwaitVerticalDragOrCancellationSample() {
@@ -219,7 +214,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun VerticalDragSample() {
@@ -263,7 +257,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun DetectVerticalDragGesturesSample() {
@@ -291,7 +284,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun AwaitDragOrCancellationSample() {
@@ -348,7 +340,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun DragSample() {
@@ -404,7 +395,6 @@
     }
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun DetectDragGesturesSample() {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MultitouchGestureSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MultitouchGestureSamples.kt
index 8410bae..02da81a 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MultitouchGestureSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MultitouchGestureSamples.kt
@@ -37,30 +37,32 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun DetectMultitouchGestures() {
     var angle by remember { mutableStateOf(0f) }
     var zoom by remember { mutableStateOf(1f) }
-    val offsetX = remember { mutableStateOf(0f) }
-    val offsetY = remember { mutableStateOf(0f) }
+    var offsetX by remember { mutableStateOf(0f) }
+    var offsetY by remember { mutableStateOf(0f) }
     Box(
-        Modifier.offset({ offsetX.value }, { offsetY.value })
-            .graphicsLayer(scaleX = zoom, scaleY = zoom, rotationZ = angle)
+        Modifier.offset({ offsetX }, { offsetY })
+            .graphicsLayer(
+                scaleX = zoom,
+                scaleY = zoom,
+                rotationZ = angle
+            )
             .background(Color.Blue)
             .pointerInput {
                 detectMultitouchGestures(
-                    onRotate = { angle += it },
-                    onZoom = { zoom *= it },
-                    onPan = {
-                        offsetX.value += it.x
-                        offsetY.value += it.y
+                    onGesture = { _, pan, gestureZoom, gestureRotate ->
+                        angle += gestureRotate
+                        zoom *= gestureZoom
+                        offsetX += pan.x
+                        offsetY += pan.y
                     }
                 )
             }
@@ -68,7 +70,6 @@
     )
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun CalculateRotation() {
@@ -93,7 +94,6 @@
     )
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun CalculateZoom() {
@@ -117,7 +117,6 @@
     )
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun CalculatePan() {
@@ -145,7 +144,6 @@
     )
 }
 
-@OptIn(ExperimentalPointerInput::class)
 @Composable
 @Sampled
 fun CalculateCentroidSize() {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
index 64dc751..591300c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
@@ -23,6 +23,8 @@
 import androidx.compose.foundation.layout.preferredSizeIn
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.test.R
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
@@ -50,7 +52,11 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.platform.AmbientDensity
+import androidx.compose.ui.res.imageResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.test.performClick
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -516,4 +522,40 @@
             Assert.assertEquals(Color.Red.toArgb(), getPixel(50, height - 1))
         }
     }
+
+    @Test
+    fun testPainterResourceWithImage() {
+        val testTag = "testTag"
+        var imageColor = Color.Black
+
+        rule.setContent {
+            val painterId = remember {
+                mutableStateOf(R.drawable.ic_vector_square_asset_test)
+            }
+            with(imageResource(R.drawable.ic_image_test)) {
+                // Sample the actual color of the image to make sure we are loading it properly
+                // when we toggle the state of the resource id
+                imageColor = toPixelMap()[width / 2, height / 2]
+            }
+            Image(
+                painterResource(painterId.value),
+                contentScale = ContentScale.FillBounds,
+                modifier = Modifier.testTag(testTag).clickable {
+                    if (painterId.value == R.drawable.ic_vector_square_asset_test) {
+                        painterId.value = R.drawable.ic_image_test
+                    } else {
+                        painterId.value = R.drawable.ic_vector_square_asset_test
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Red }
+
+        rule.onNodeWithTag(testTag).performClick()
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(testTag).captureToImage().assertPixels { imageColor }
+    }
 }
\ No newline at end of file
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 d4a9fa4..6b3288e 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
@@ -32,6 +32,10 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollSource
+import androidx.compose.ui.gesture.nestedscroll.nestedScroll
 import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
@@ -48,6 +52,7 @@
 import androidx.compose.ui.test.swipe
 import androidx.compose.ui.test.swipeWithVelocity
 import androidx.compose.ui.test.up
+import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.milliseconds
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -511,7 +516,6 @@
                 Box(
                     contentAlignment = Alignment.Center,
                     modifier = Modifier
-                        .testTag(scrollableBoxTag)
                         .preferredSize(300.dp)
                         .scrollable(
                             controller = outerState,
@@ -519,15 +523,89 @@
                         )
                 ) {
                     Box(
-                        modifier = Modifier.preferredSize(300.dp).scrollable(
-                            controller = innerState,
-                            orientation = Orientation.Horizontal
-                        )
+                        modifier = Modifier.testTag(scrollableBoxTag)
+                            .preferredSize(300.dp)
+                            .scrollable(
+                                controller = innerState,
+                                orientation = Orientation.Horizontal
+                            )
                     )
                 }
             }
         }
         rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            this.swipeWithVelocity(
+                start = this.center,
+                end = Offset(this.center.x + 200f, this.center.y),
+                duration = 300.milliseconds,
+                endVelocity = 0f
+            )
+        }
+        val lastEqualDrag = rule.runOnIdle {
+            assertThat(innerDrag).isGreaterThan(0f)
+            assertThat(outerDrag).isGreaterThan(0f)
+            // we consumed half delta in child, so exactly half should go to the parent
+            assertThat(outerDrag).isEqualTo(innerDrag)
+            innerDrag
+        }
+        advanceClockWhileAwaitersExist(clock)
+        advanceClockWhileAwaitersExist(clock)
+        rule.runOnIdle {
+            // values should be the same since no fling
+            assertThat(innerDrag).isEqualTo(lastEqualDrag)
+            assertThat(outerDrag).isEqualTo(lastEqualDrag)
+        }
+    }
+
+    @Test
+    @OptIn(ExperimentalTesting::class)
+    fun scrollable_nestedFling() = runBlockingWithManualClock { clock ->
+        var innerDrag = 0f
+        var outerDrag = 0f
+        val animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
+        val outerState = ScrollableController(
+            consumeScrollDelta = {
+                outerDrag += it
+                it
+            },
+            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            animationClock = animationClock
+        )
+        val innerState = ScrollableController(
+            consumeScrollDelta = {
+                innerDrag += it / 2
+                it / 2
+            },
+            flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+            animationClock = animationClock
+        )
+
+        rule.setContent {
+            Box {
+                Box(
+                    contentAlignment = Alignment.Center,
+                    modifier = Modifier
+                        .preferredSize(300.dp)
+                        .scrollable(
+                            controller = outerState,
+                            orientation = Orientation.Horizontal
+                        )
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .testTag(scrollableBoxTag)
+                            .preferredSize(300.dp)
+                            .scrollable(
+                                controller = innerState,
+                                orientation = Orientation.Horizontal
+                            )
+                    )
+                }
+            }
+        }
+
+        // swipe again with velocity
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
             this.swipe(
                 start = this.center,
                 end = Offset(this.center.x + 200f, this.center.y),
@@ -541,16 +619,234 @@
             assertThat(outerDrag).isEqualTo(innerDrag)
             innerDrag
         }
+        // advance clocks, triggering fling
         advanceClockWhileAwaitersExist(clock)
         advanceClockWhileAwaitersExist(clock)
-        // and nothing should change as we don't do nested fling
         rule.runOnIdle {
-            assertThat(outerDrag).isEqualTo(lastEqualDrag)
+            assertThat(innerDrag).isGreaterThan(lastEqualDrag)
+            assertThat(outerDrag).isGreaterThan(lastEqualDrag)
         }
     }
 
     @Test
     @OptIn(ExperimentalTesting::class)
+    fun scrollable_nestedScrollAbove_respectsPreConsumption() =
+        runBlockingWithManualClock { clock ->
+            var value = 0f
+            var lastReceivedPreScrollAvailable = 0f
+            val preConsumeFraction = 0.7f
+            val animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
+            val controller = ScrollableController(
+                consumeScrollDelta = {
+                    val expected = lastReceivedPreScrollAvailable * (1 - preConsumeFraction)
+                    assertThat(it - expected).isWithin(0.01f)
+                    value += it
+                    it
+                },
+                flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+                animationClock = animationClock
+            )
+            val preConsumingParent = object : NestedScrollConnection {
+                override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                    lastReceivedPreScrollAvailable = available.x
+                    return available * preConsumeFraction
+                }
+
+                override fun onPreFling(available: Velocity): Velocity {
+                    // consume all velocity
+                    return available
+                }
+            }
+
+            rule.setContent {
+                Box {
+                    Box(
+                        contentAlignment = Alignment.Center,
+                        modifier = Modifier
+                            .preferredSize(300.dp)
+                            .nestedScroll(preConsumingParent)
+                    ) {
+                        Box(
+                            modifier = Modifier.preferredSize(300.dp)
+                                .testTag(scrollableBoxTag)
+                                .scrollable(
+                                    controller = controller,
+                                    orientation = Orientation.Horizontal
+                                )
+                        )
+                    }
+                }
+            }
+
+            rule.onNodeWithTag(scrollableBoxTag).performGesture {
+                this.swipe(
+                    start = this.center,
+                    end = Offset(this.center.x + 200f, this.center.y),
+                    duration = 300.milliseconds
+                )
+            }
+
+            val preFlingValue = rule.runOnIdle { value }
+            advanceClockWhileAwaitersExist(clock)
+            advanceClockWhileAwaitersExist(clock)
+            rule.runOnIdle {
+                // if scrollable respects prefling consumption, it should fling 0px since we
+                // preconsume all
+                assertThat(preFlingValue).isEqualTo(value)
+            }
+        }
+
+    @Test
+    @OptIn(ExperimentalTesting::class)
+    fun scrollable_nestedScrollAbove_proxiesPostCycles() =
+        runBlockingWithManualClock { clock ->
+            var value = 0f
+            var expectedLeft = 0f
+            val velocityFlung = 5000f
+            val animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
+            val controller = ScrollableController(
+                consumeScrollDelta = {
+                    val toConsume = it * 0.345f
+                    value += toConsume
+                    expectedLeft = it - toConsume
+                    toConsume
+                },
+                flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+                animationClock = animationClock
+            )
+            val parent = object : NestedScrollConnection {
+                override fun onPostScroll(
+                    consumed: Offset,
+                    available: Offset,
+                    source: NestedScrollSource
+                ): Offset {
+                    // we should get in post scroll as much as left in controller callback
+                    assertThat(available.x).isEqualTo(expectedLeft)
+                    return available
+                }
+
+                override fun onPostFling(
+                    consumed: Velocity,
+                    available: Velocity,
+                    onFinished: (Velocity) -> Unit
+                ) {
+                    assertThat(available.pixelsPerSecond)
+                        .isEqualTo(
+                            Offset(x = velocityFlung, y = 0f) - consumed.pixelsPerSecond
+                        )
+                    onFinished.invoke(available)
+                }
+            }
+
+            rule.setContent {
+                Box {
+                    Box(
+                        contentAlignment = Alignment.Center,
+                        modifier = Modifier
+                            .preferredSize(300.dp)
+                            .nestedScroll(parent)
+                    ) {
+                        Box(
+                            modifier = Modifier.preferredSize(300.dp)
+                                .testTag(scrollableBoxTag)
+                                .scrollable(
+                                    controller = controller,
+                                    orientation = Orientation.Horizontal
+                                )
+                        )
+                    }
+                }
+            }
+
+            rule.onNodeWithTag(scrollableBoxTag).performGesture {
+                this.swipeWithVelocity(
+                    start = this.center,
+                    end = Offset(this.center.x + 500f, this.center.y),
+                    duration = 300.milliseconds,
+                    endVelocity = velocityFlung
+                )
+            }
+
+            advanceClockWhileAwaitersExist(clock)
+            advanceClockWhileAwaitersExist(clock)
+
+            // all assertions in callback above
+        }
+
+    @Test
+    @OptIn(ExperimentalTesting::class)
+    fun scrollable_nestedScrollBelow_listensDispatches() =
+        runBlockingWithManualClock { clock ->
+            var value = 0f
+            var expectedConsumed = 0f
+            val animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
+            val controller = ScrollableController(
+                consumeScrollDelta = {
+                    expectedConsumed = it * 0.3f
+                    value += expectedConsumed
+                    expectedConsumed
+                },
+                flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
+                animationClock = animationClock
+            )
+            val child = object : NestedScrollConnection {}
+            val dispatcher = NestedScrollDispatcher()
+
+            rule.setContent {
+                Box {
+                    Box(
+                        modifier = Modifier.preferredSize(300.dp)
+
+                            .scrollable(
+                                controller = controller,
+                                orientation = Orientation.Horizontal
+                            )
+                    ) {
+                        Box(
+                            Modifier.preferredSize(200.dp)
+                                .testTag(scrollableBoxTag)
+                                .nestedScroll(child, dispatcher)
+                        )
+                    }
+                }
+            }
+
+            val lastValueBeforeFling = rule.runOnIdle {
+                val preScrollConsumed = dispatcher
+                    .dispatchPreScroll(Offset(20f, 20f), NestedScrollSource.Drag)
+                // scrollable is not interested in pre scroll
+                assertThat(preScrollConsumed).isEqualTo(Offset.Zero)
+
+                val consumed = dispatcher.dispatchPostScroll(
+                    Offset(20f, 20f),
+                    Offset(50f, 50f),
+                    NestedScrollSource.Drag
+                )
+                assertThat(consumed.x - expectedConsumed).isWithin(0.001f)
+
+                val preFlingConsumed = dispatcher
+                    .dispatchPreFling(Velocity(Offset(50f, 50f)))
+                // scrollable won't participate in the pre fling
+                assertThat(preFlingConsumed).isEqualTo(Velocity.Zero)
+
+                dispatcher.dispatchPostFling(
+                    Velocity(Offset(1000f, 1000f)),
+                    Velocity(Offset(2000f, 2000f))
+                )
+                value
+            }
+
+            advanceClockWhileAwaitersExist(clock)
+            advanceClockWhileAwaitersExist(clock)
+
+            rule.runOnIdle {
+                // catch that scrollable caught our post fling and flung
+                assertThat(value).isGreaterThan(lastValueBeforeFling)
+            }
+        }
+
+    @Test
+    @OptIn(ExperimentalTesting::class)
     fun scrollable_interactionState() = runBlocking {
         val interactionState = InteractionState()
         var total = 0f
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldScrollTest.kt
index 3afb9ea..3ee3b91 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldScrollTest.kt
@@ -112,6 +112,7 @@
                         remember { scrollerPosition },
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef
                     )
             )
@@ -142,6 +143,7 @@
                         remember { scrollerPosition },
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef,
                     )
             )
@@ -172,6 +174,7 @@
                         remember { scrollerPosition },
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef,
                     )
             )
@@ -203,6 +206,7 @@
                         remember { scrollerPosition },
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef,
                     )
             )
@@ -231,6 +235,7 @@
                         remember { scrollerPosition },
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef,
                     )
             )
@@ -271,6 +276,7 @@
                                 remember { scrollerPosition },
                                 value,
                                 VisualTransformation.None,
+                                remember { InteractionState() },
                                 textLayoutResultRef
                             )
                     )
@@ -316,6 +322,7 @@
                                 remember { scrollerPosition },
                                 value,
                                 VisualTransformation.None,
+                                remember { InteractionState() },
                                 textLayoutResultRef
                             )
                     )
@@ -354,6 +361,7 @@
                         remember { scrollerPosition },
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef
                     )
             )
@@ -398,6 +406,7 @@
                         remember { scrollerPosition },
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef
                     )
             )
@@ -450,6 +459,7 @@
                         scrollerPosition,
                         value,
                         VisualTransformation.None,
+                        remember { InteractionState() },
                         textLayoutResultRef
                     )
             )
@@ -482,7 +492,12 @@
         val value = TextFieldValue()
         rule.setContent {
             val modifier = Modifier.textFieldScroll(
-                orientation, position, value, VisualTransformation.None, Ref()
+                orientation,
+                position,
+                value,
+                VisualTransformation.None,
+                remember { InteractionState() },
+                Ref()
             ) as InspectableValue
             assertThat(modifier.nameFallback).isEqualTo("textFieldScroll")
             assertThat(modifier.valueOverride).isNull()
@@ -491,6 +506,7 @@
                 "scrollerPosition",
                 "textFieldValue",
                 "visualTransformation",
+                "interactionState",
                 "textLayoutResult"
             )
         }
@@ -532,6 +548,7 @@
                             remember { textFieldScrollPosition },
                             value,
                             VisualTransformation.None,
+                            remember { InteractionState() },
                             textLayoutResultRef
                         ),
                     textStyle = TextStyle(fontSize = 20.sp)
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
new file mode 100644
index 0000000..994dba7
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyArrangementsTest.kt
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.InternalLayoutApi
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Providers
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.AmbientLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(InternalLayoutApi::class)
+class LazyArrangementsTest {
+
+    private val ContainerTag = "ContainerTag"
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private var itemSize: Dp = Dp.Infinity
+    private var containerSize: Dp = Dp.Infinity
+
+    @Before
+    fun before() {
+        with(rule.density) {
+            itemSize = 50.toDp()
+        }
+        containerSize = itemSize * 5
+    }
+
+    // cases when we have not enough items to fill min constraints:
+
+    @Test
+    fun column_defaultArrangementIsTop() {
+        rule.setContent {
+            LazyColumn(
+                modifier = Modifier.size(containerSize)
+            ) {
+                items((0..1).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        assertArrangementForTwoItems(Arrangement.Top)
+    }
+
+    @Test
+    fun column_centerArrangement() {
+        composeColumnWith(Arrangement.Center)
+        assertArrangementForTwoItems(Arrangement.Center)
+    }
+
+    @Test
+    fun column_bottomArrangement() {
+        composeColumnWith(Arrangement.Bottom)
+        assertArrangementForTwoItems(Arrangement.Bottom)
+    }
+
+    @Test
+    fun column_spacedArrangementNotFillingViewport() {
+        val arrangement = Arrangement.spacedBy(10.dp)
+        composeColumnWith(arrangement)
+        assertArrangementForTwoItems(arrangement)
+    }
+
+    @Test
+    fun row_defaultArrangementIsStart() {
+        rule.setContent {
+            LazyRow(
+                modifier = Modifier.size(containerSize)
+            ) {
+                items((0..1).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        assertArrangementForTwoItems(Arrangement.Start, LayoutDirection.Ltr)
+    }
+
+    @Test
+    fun row_centerArrangement() {
+        composeRowWith(Arrangement.Center, LayoutDirection.Ltr)
+        assertArrangementForTwoItems(Arrangement.Center, LayoutDirection.Ltr)
+    }
+
+    @Test
+    fun row_endArrangement() {
+        composeRowWith(Arrangement.End, LayoutDirection.Ltr)
+        assertArrangementForTwoItems(Arrangement.End, LayoutDirection.Ltr)
+    }
+
+    @Test
+    fun row_spacedArrangementNotFillingViewport() {
+        val arrangement = Arrangement.spacedBy(10.dp)
+        composeRowWith(arrangement, LayoutDirection.Ltr)
+        assertArrangementForTwoItems(arrangement, LayoutDirection.Ltr)
+    }
+
+    @Test
+    fun row_rtl_startArrangement() {
+        composeRowWith(Arrangement.Center, LayoutDirection.Rtl)
+        assertArrangementForTwoItems(Arrangement.Center, LayoutDirection.Rtl)
+    }
+
+    @Test
+    fun row_rtl_endArrangement() {
+        composeRowWith(Arrangement.End, LayoutDirection.Rtl)
+        assertArrangementForTwoItems(Arrangement.End, LayoutDirection.Rtl)
+    }
+
+    @Test
+    fun row_rtl_spacedArrangementNotFillingViewport() {
+        val arrangement = Arrangement.spacedBy(10.dp)
+        composeRowWith(arrangement, LayoutDirection.Rtl)
+        assertArrangementForTwoItems(arrangement, LayoutDirection.Rtl)
+    }
+
+    // wrap content and spacing
+
+    @Test
+    fun column_spacing_affects_wrap_content() {
+        rule.setContent {
+            LazyColumn(
+                verticalArrangement = Arrangement.spacedBy(itemSize),
+                modifier = Modifier.testTag(ContainerTag)
+            ) {
+                items((0..1).toList()) {
+                    Box(Modifier.size(itemSize))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .assertWidthIsEqualTo(itemSize)
+            .assertHeightIsEqualTo(itemSize * 3)
+    }
+
+    @Test
+    fun row_spacing_affects_wrap_content() {
+        rule.setContent {
+            LazyRow(
+                horizontalArrangement = Arrangement.spacedBy(itemSize),
+                modifier = Modifier.testTag(ContainerTag)
+            ) {
+                items((0..1).toList()) {
+                    Box(Modifier.size(itemSize))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .assertWidthIsEqualTo(itemSize * 3)
+            .assertHeightIsEqualTo(itemSize)
+    }
+
+    // spacing added when we have enough items to fill the viewport
+
+    @Test
+    fun column_spacing_scrolledToTheTop() {
+        rule.setContent {
+            LazyColumn(
+                verticalArrangement = Arrangement.spacedBy(itemSize),
+                modifier = Modifier.size(itemSize * 3.5f)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertTopPositionInRootIsEqualTo(0.dp)
+
+        rule.onNodeWithTag("1")
+            .assertTopPositionInRootIsEqualTo(itemSize * 2)
+    }
+
+    @Test
+    fun column_spacing_scrolledToTheBottom() {
+        rule.setContent {
+            LazyColumn(
+                verticalArrangement = Arrangement.spacedBy(itemSize),
+                modifier = Modifier.size(itemSize * 3.5f).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(y = itemSize * 2, density = rule.density)
+
+        rule.onNodeWithTag("1")
+            .assertTopPositionInRootIsEqualTo(itemSize * 0.5f)
+
+        rule.onNodeWithTag("2")
+            .assertTopPositionInRootIsEqualTo(itemSize * 2.5f)
+    }
+
+    @Test
+    fun row_spacing_scrolledToTheStart() {
+        rule.setContent {
+            LazyRow(
+                horizontalArrangement = Arrangement.spacedBy(itemSize),
+                modifier = Modifier.size(itemSize * 3.5f)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(itemSize * 2)
+    }
+
+    @Test
+    fun row_spacing_scrolledToTheEnd() {
+        rule.setContent {
+            LazyRow(
+                horizontalArrangement = Arrangement.spacedBy(itemSize),
+                modifier = Modifier.size(itemSize * 3.5f).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(x = itemSize * 2, density = rule.density)
+
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(itemSize * 0.5f)
+
+        rule.onNodeWithTag("2")
+            .assertLeftPositionInRootIsEqualTo(itemSize * 2.5f)
+    }
+
+    // with reverseLayout == true
+
+    @Test
+    fun column_defaultArrangementIsBottomWithReverseLayout() {
+        rule.setContent {
+            LazyColumn(
+                reverseLayout = true,
+                modifier = Modifier.size(containerSize)
+            ) {
+                items((0..1).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        assertArrangementForTwoItems(Arrangement.Bottom, reversedItemsOrder = true)
+    }
+
+    @Test
+    fun row_defaultArrangementIsEndWithReverseLayout() {
+        rule.setContent {
+            LazyRow(
+                reverseLayout = true,
+                modifier = Modifier.size(containerSize)
+            ) {
+                items((0..1).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+
+        assertArrangementForTwoItems(
+            Arrangement.End, LayoutDirection.Ltr, reversedItemsOrder = true
+        )
+    }
+
+    fun composeColumnWith(arrangement: Arrangement.Vertical) {
+        rule.setContent {
+            LazyColumn(
+                verticalArrangement = arrangement,
+                modifier = Modifier.size(containerSize)
+            ) {
+                items((0..1).toList()) {
+                    Box(Modifier.size(itemSize).testTag(it.toString()))
+                }
+            }
+        }
+    }
+
+    fun composeRowWith(arrangement: Arrangement.Horizontal, layoutDirection: LayoutDirection) {
+        rule.setContent {
+            Providers(AmbientLayoutDirection provides layoutDirection) {
+                LazyRow(
+                    horizontalArrangement = arrangement,
+                    modifier = Modifier.size(containerSize)
+                ) {
+                    items((0..1).toList()) {
+                        Box(Modifier.size(itemSize).testTag(it.toString()))
+                    }
+                }
+            }
+        }
+    }
+
+    fun assertArrangementForTwoItems(
+        arrangement: Arrangement.Vertical,
+        reversedItemsOrder: Boolean = false
+    ) {
+        with(rule.density) {
+            val sizes = IntArray(2) { itemSize.toIntPx() }
+            val outPositions = IntArray(2) { 0 }
+            arrangement.arrange(containerSize.toIntPx(), sizes, this, outPositions)
+
+            outPositions.forEachIndexed { index, position ->
+                val realIndex = if (reversedItemsOrder) if (index == 0) 1 else 0 else index
+                rule.onNodeWithTag("$realIndex")
+                    .assertTopPositionInRootIsEqualTo(position.toDp())
+            }
+        }
+    }
+
+    fun assertArrangementForTwoItems(
+        arrangement: Arrangement.Horizontal,
+        layoutDirection: LayoutDirection,
+        reversedItemsOrder: Boolean = false
+    ) {
+        with(rule.density) {
+            val sizes = IntArray(2) { itemSize.toIntPx() }
+            val outPositions = IntArray(2) { 0 }
+            arrangement.arrange(containerSize.toIntPx(), sizes, layoutDirection, this, outPositions)
+
+            outPositions.forEachIndexed { index, position ->
+                val realIndex = if (reversedItemsOrder) if (index == 0) 1 else 0 else index
+                val expectedPosition = if (layoutDirection == LayoutDirection.Ltr) {
+                    position.toDp()
+                } else {
+                    containerSize - position.toDp() - itemSize
+                }
+                rule.onNodeWithTag("$realIndex")
+                    .assertLeftPositionInRootIsEqualTo(expectedPosition)
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
index 4d6ba65..bbd257b 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.ExponentialDecay
 import androidx.compose.animation.core.ManualAnimationClock
+import androidx.compose.animation.core.snap
 import androidx.compose.foundation.animation.FlingConfig
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
@@ -1040,9 +1041,72 @@
         }
     }
 
+    @Test
+    fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
+        val items = (0..1).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        val itemSizeMinusOne = with(rule.density) { 29.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumnFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.height(itemSizeMinusOne).testTag(LazyColumnForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.width(30.dp).height(itemSizeMinusOne)
+                    } else {
+                        Modifier.width(20.dp).height(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyColumnForTag)
+            .assertWidthIsEqualTo(20.dp)
+    }
+
+    @Test
+    fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
+        val items = (0..2).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumnFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.height(itemSize * 1.75f).testTag(LazyColumnForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.width(30.dp).height(itemSize / 2)
+                    } else if (it == 1) {
+                        Modifier.width(20.dp).height(itemSize / 2)
+                    } else {
+                        Modifier.width(20.dp).height(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyColumnForTag)
+            .assertWidthIsEqualTo(30.dp)
+    }
+
     private fun SemanticsNodeInteraction.assertTopPositionIsAlmost(expected: Dp) {
         getUnclippedBoundsInRoot().top.assertIsEqualTo(expected, tolerance = 1.dp)
     }
+
+    private fun LazyListState.scrollBy(offset: Dp) {
+        runBlocking {
+            smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
+        }
+    }
 }
 
 data class NotStable(val count: Int)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
new file mode 100644
index 0000000..5dc3b6f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.onDispose
+import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+
+class LazyItemStateRestoration {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun visibleItemsStateRestored() {
+        val restorationTester = StateRestorationTester(rule)
+        var counter0 = 1
+        var counter1 = 10
+        var counter2 = 100
+        var realState = arrayOf(0, 0, 0)
+        restorationTester.setContent {
+            LazyColumn {
+                item {
+                    realState[0] = rememberSavedInstanceState { counter0++ }
+                    Box(Modifier.size(1.dp))
+                }
+                items((1..2).toList()) {
+                    if (it == 1) {
+                        realState[1] = rememberSavedInstanceState { counter1++ }
+                    } else {
+                        realState[2] = rememberSavedInstanceState { counter2++ }
+                    }
+                    Box(Modifier.size(1.dp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState[0]).isEqualTo(1)
+            assertThat(realState[1]).isEqualTo(10)
+            assertThat(realState[2]).isEqualTo(100)
+            realState = arrayOf(0, 0, 0)
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        rule.runOnIdle {
+            assertThat(realState[0]).isEqualTo(1)
+            assertThat(realState[1]).isEqualTo(10)
+            assertThat(realState[2]).isEqualTo(100)
+        }
+    }
+
+    @Test
+    fun itemsStateRestoredWhenWeScrolledBackToIt() {
+        val restorationTester = StateRestorationTester(rule)
+        var counter0 = 1
+        lateinit var state: LazyListState
+        var itemDisposed = false
+        var realState = 0
+        restorationTester.setContent {
+            LazyColumn(
+                Modifier.size(20.dp),
+                state = rememberLazyListState().also { state = it }
+            ) {
+                items((0..1).toList()) {
+                    if (it == 0) {
+                        realState = rememberSavedInstanceState { counter0++ }
+                        onDispose {
+                            itemDisposed = true
+                        }
+                    }
+                    Box(Modifier.size(30.dp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState).isEqualTo(1)
+            runBlocking {
+                state.snapToItemIndex(1, 5)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(itemDisposed).isEqualTo(true)
+            realState = 0
+            runBlocking {
+                state.snapToItemIndex(0, 0)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun itemsStateRestoredWhenWeScrolledRestoredAndScrolledBackTo() {
+        val restorationTester = StateRestorationTester(rule)
+        var counter0 = 1
+        var counter1 = 10
+        lateinit var state: LazyListState
+        var realState = arrayOf(0, 0)
+        restorationTester.setContent {
+            LazyColumn(
+                Modifier.size(20.dp),
+                state = rememberLazyListState().also { state = it }
+            ) {
+                items((0..1).toList()) {
+                    if (it == 0) {
+                        realState[0] = rememberSavedInstanceState { counter0++ }
+                    } else {
+                        realState[1] = rememberSavedInstanceState { counter1++ }
+                    }
+                    Box(Modifier.size(30.dp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState[0]).isEqualTo(1)
+            runBlocking {
+                state.snapToItemIndex(1, 5)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState[1]).isEqualTo(10)
+            realState = arrayOf(0, 0)
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        rule.runOnIdle {
+            assertThat(realState[1]).isEqualTo(10)
+            runBlocking {
+                state.snapToItemIndex(0, 0)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState[0]).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun nestedLazy_itemsStateRestoredWhenWeScrolledBackToIt() {
+        val restorationTester = StateRestorationTester(rule)
+        var counter0 = 1
+        lateinit var state: LazyListState
+        var itemDisposed = false
+        var realState = 0
+        restorationTester.setContent {
+            LazyColumn(
+                Modifier.size(20.dp),
+                state = rememberLazyListState().also { state = it }
+            ) {
+                items((0..1).toList()) {
+                    if (it == 0) {
+                        LazyRow {
+                            item {
+                                realState = rememberSavedInstanceState { counter0++ }
+                                onDispose {
+                                    itemDisposed = true
+                                }
+                                Box(Modifier.size(30.dp))
+                            }
+                        }
+                    } else {
+                        Box(Modifier.size(30.dp))
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState).isEqualTo(1)
+            runBlocking {
+                state.snapToItemIndex(1, 5)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(itemDisposed).isEqualTo(true)
+            realState = 0
+            runBlocking {
+                state.snapToItemIndex(0, 0)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(realState).isEqualTo(1)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsReverseLayoutTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsReverseLayoutTest.kt
new file mode 100644
index 0000000..9f02efa
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsReverseLayoutTest.kt
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.InternalLayoutApi
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Providers
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.AmbientLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(InternalLayoutApi::class)
+class LazyListsReverseLayoutTest {
+
+    private val ContainerTag = "ContainerTag"
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private var itemSize: Dp = Dp.Infinity
+
+    @Before
+    fun before() {
+        with(rule.density) {
+            itemSize = 50.toDp()
+        }
+    }
+
+    @Test
+    fun column_emitTwoElementsAsOneItem_positionedReversed() {
+        rule.setContent {
+            LazyColumn(
+                reverseLayout = true
+            ) {
+                item {
+                    Box(Modifier.size(itemSize).testTag("0"))
+                    Box(Modifier.size(itemSize).testTag("1"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("1")
+            .assertTopPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("0")
+            .assertTopPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun column_emitTwoItems_positionedReversed() {
+        rule.setContent {
+            LazyColumn(
+                reverseLayout = true
+            ) {
+                item {
+                    Box(Modifier.size(itemSize).testTag("0"))
+                }
+                item {
+                    Box(Modifier.size(itemSize).testTag("1"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("1")
+            .assertTopPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("0")
+            .assertTopPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun column_initialScrollPositionIs0() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumn(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun column_scrollInWrongDirectionDoesNothing() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumn(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        // we scroll down and as the scrolling is reversed it shouldn't affect anything
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(y = itemSize, density = rule.density)
+
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag("1")
+            .assertTopPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("0")
+            .assertTopPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun column_scrollForwardHalfWay() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumn(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(y = -itemSize * 0.5f, density = rule.density)
+
+        val scrolled = rule.runOnIdle {
+            assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+            with(rule.density) { state.firstVisibleItemScrollOffset.toDp() }
+        }
+
+        rule.onNodeWithTag("2")
+            .assertTopPositionInRootIsEqualTo(-itemSize + scrolled)
+        rule.onNodeWithTag("1")
+            .assertTopPositionInRootIsEqualTo(scrolled)
+        rule.onNodeWithTag("0")
+            .assertTopPositionInRootIsEqualTo(itemSize + scrolled)
+    }
+
+    @Test
+    fun column_scrollForwardTillTheEnd() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumn(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..3).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        // we scroll a bit more than it is possible just to make sure we would stop correctly
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(y = -itemSize * 2.2f, density = rule.density)
+
+        rule.runOnIdle {
+            with(rule.density) {
+                val realOffset = state.firstVisibleItemScrollOffset.toDp() +
+                    itemSize * state.firstVisibleItemIndex
+                assertThat(realOffset).isEqualTo(itemSize * 2)
+            }
+        }
+
+        rule.onNodeWithTag("3")
+            .assertTopPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("2")
+            .assertTopPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun row_emitTwoElementsAsOneItem_positionedReversed() {
+        rule.setContent {
+            LazyRow(
+                reverseLayout = true
+            ) {
+                item {
+                    Box(Modifier.size(itemSize).testTag("0"))
+                    Box(Modifier.size(itemSize).testTag("1"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun row_emitTwoItems_positionedReversed() {
+        rule.setContent {
+            LazyRow(
+                reverseLayout = true
+            ) {
+                item {
+                    Box(Modifier.size(itemSize).testTag("0"))
+                }
+                item {
+                    Box(Modifier.size(itemSize).testTag("1"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun row_initialScrollPositionIs0() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRow(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun row_scrollInWrongDirectionDoesNothing() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRow(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        // we scroll down and as the scrolling is reversed it shouldn't affect anything
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(x = itemSize, density = rule.density)
+
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun row_scrollForwardHalfWay() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRow(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..2).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(x = -itemSize * 0.5f, density = rule.density)
+
+        val scrolled = rule.runOnIdle {
+            assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+            with(rule.density) { state.firstVisibleItemScrollOffset.toDp() }
+        }
+
+        rule.onNodeWithTag("2")
+            .assertLeftPositionInRootIsEqualTo(-itemSize + scrolled)
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(scrolled)
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(itemSize + scrolled)
+    }
+
+    @Test
+    fun row_scrollForwardTillTheEnd() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRow(
+                reverseLayout = true,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+            ) {
+                items((0..3).toList()) {
+                    Box(Modifier.size(itemSize).testTag("$it"))
+                }
+            }
+        }
+
+        // we scroll a bit more than it is possible just to make sure we would stop correctly
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(x = -itemSize * 2.2f, density = rule.density)
+
+        rule.runOnIdle {
+            with(rule.density) {
+                val realOffset = state.firstVisibleItemScrollOffset.toDp() +
+                    itemSize * state.firstVisibleItemIndex
+                assertThat(realOffset).isEqualTo(itemSize * 2)
+            }
+        }
+
+        rule.onNodeWithTag("3")
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+        rule.onNodeWithTag("2")
+            .assertLeftPositionInRootIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun row_rtl_emitTwoElementsAsOneItem_positionedReversed() {
+        rule.setContent {
+            Providers(AmbientLayoutDirection provides LayoutDirection.Rtl) {
+                LazyRow(
+                    reverseLayout = true
+                ) {
+                    item {
+                        Box(Modifier.size(itemSize).testTag("0"))
+                        Box(Modifier.size(itemSize).testTag("1"))
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(itemSize)
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+    }
+
+    @Test
+    fun row_rtl_emitTwoItems_positionedReversed() {
+        rule.setContent {
+            Providers(AmbientLayoutDirection provides LayoutDirection.Rtl) {
+                LazyRow(
+                    reverseLayout = true
+                ) {
+                    item {
+                        Box(Modifier.size(itemSize).testTag("0"))
+                    }
+                    item {
+                        Box(Modifier.size(itemSize).testTag("1"))
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(itemSize)
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(0.dp)
+    }
+
+    @Test
+    fun row_rtl_scrollForwardHalfWay() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            Providers(AmbientLayoutDirection provides LayoutDirection.Rtl) {
+                LazyRow(
+                    reverseLayout = true,
+                    state = rememberLazyListState().also { state = it },
+                    modifier = Modifier.size(itemSize * 2).testTag(ContainerTag)
+                ) {
+                    items((0..2).toList()) {
+                        Box(Modifier.size(itemSize).testTag("$it"))
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .scrollBy(x = itemSize * 0.5f, density = rule.density)
+
+        val scrolled = rule.runOnIdle {
+            assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+            with(rule.density) { state.firstVisibleItemScrollOffset.toDp() }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertLeftPositionInRootIsEqualTo(-scrolled)
+        rule.onNodeWithTag("1")
+            .assertLeftPositionInRootIsEqualTo(itemSize - scrolled)
+        rule.onNodeWithTag("2")
+            .assertLeftPositionInRootIsEqualTo(itemSize * 2 - scrolled)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt
index 6b5902a..769791b 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt
@@ -16,12 +16,14 @@
 
 package androidx.compose.foundation.lazy
 
-import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberScrollableController
+import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.gesture.TouchSlop
 import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.down
@@ -34,6 +36,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,15 +49,28 @@
     @get:Rule
     val rule = createComposeRule()
 
+    var expectedDragOffset = Float.MAX_VALUE
+
+    @Before
+    fun test() {
+        expectedDragOffset = with(rule.density) {
+            TouchSlop.toPx() + 20
+        }
+    }
+
     @Test
     fun column_nestedScrollingBackwardInitially() {
         val items = (1..3).toList()
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Vertical) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Vertical,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyColumnFor(
                     items = items,
@@ -83,9 +99,13 @@
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Vertical) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Vertical,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyColumnFor(
                     items = items,
@@ -109,12 +129,12 @@
             .performGesture {
                 draggedOffset = 0f
                 down(Offset(x = 10f, y = 10f))
-                moveBy(Offset(x = 0f, y = 50f))
+                moveBy(Offset(x = 0f, y = expectedDragOffset))
                 up()
             }
 
         rule.runOnIdle {
-            Truth.assertThat(draggedOffset).isEqualTo(50f)
+            Truth.assertThat(draggedOffset).isEqualTo(expectedDragOffset)
         }
     }
 
@@ -124,9 +144,13 @@
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Vertical) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Vertical,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyColumnFor(
                     items = items,
@@ -140,12 +164,12 @@
         rule.onNodeWithTag(LazyTag)
             .performGesture {
                 down(Offset(x = 10f, y = 10f))
-                moveBy(Offset(x = 0f, y = -50f))
+                moveBy(Offset(x = 0f, y = -expectedDragOffset))
                 up()
             }
 
         rule.runOnIdle {
-            Truth.assertThat(draggedOffset).isEqualTo(-50f)
+            Truth.assertThat(draggedOffset).isEqualTo(-expectedDragOffset)
         }
     }
 
@@ -155,9 +179,13 @@
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Vertical) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Vertical,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyColumnFor(
                     items = items,
@@ -176,12 +204,12 @@
             .performGesture {
                 draggedOffset = 0f
                 down(Offset(x = 10f, y = 10f))
-                moveBy(Offset(x = 0f, y = -50f))
+                moveBy(Offset(x = 0f, y = -expectedDragOffset))
                 up()
             }
 
         rule.runOnIdle {
-            Truth.assertThat(draggedOffset).isEqualTo(-50f)
+            Truth.assertThat(draggedOffset).isEqualTo(-expectedDragOffset)
         }
     }
 
@@ -191,9 +219,13 @@
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Horizontal) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Horizontal,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyRowFor(
                     items = items,
@@ -207,12 +239,12 @@
         rule.onNodeWithTag(LazyTag)
             .performGesture {
                 down(Offset(x = 10f, y = 10f))
-                moveBy(Offset(x = 100f, y = 0f))
+                moveBy(Offset(x = expectedDragOffset, y = 0f))
                 up()
             }
 
         rule.runOnIdle {
-            Truth.assertThat(draggedOffset).isEqualTo(100f)
+            Truth.assertThat(draggedOffset).isEqualTo(expectedDragOffset)
         }
     }
 
@@ -222,9 +254,13 @@
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Horizontal) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Horizontal,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyRowFor(
                     items = items,
@@ -248,12 +284,12 @@
             .performGesture {
                 draggedOffset = 0f
                 down(Offset(x = 10f, y = 10f))
-                moveBy(Offset(x = 50f, y = 0f))
+                moveBy(Offset(x = expectedDragOffset, y = 0f))
                 up()
             }
 
         rule.runOnIdle {
-            Truth.assertThat(draggedOffset).isEqualTo(50f)
+            Truth.assertThat(draggedOffset).isEqualTo(expectedDragOffset)
         }
     }
 
@@ -263,9 +299,13 @@
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Horizontal) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Horizontal,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyRowFor(
                     items = items,
@@ -279,12 +319,12 @@
         rule.onNodeWithTag(LazyTag)
             .performGesture {
                 down(Offset(x = 10f, y = 10f))
-                moveBy(Offset(x = -50f, y = 0f))
+                moveBy(Offset(x = -expectedDragOffset, y = 0f))
                 up()
             }
 
         rule.runOnIdle {
-            Truth.assertThat(draggedOffset).isEqualTo(-50f)
+            Truth.assertThat(draggedOffset).isEqualTo(-expectedDragOffset)
         }
     }
 
@@ -294,9 +334,13 @@
         var draggedOffset = 0f
         rule.setContent {
             Box(
-                Modifier.draggable(Orientation.Horizontal) {
-                    draggedOffset += it
-                }
+                Modifier.scrollable(
+                    orientation = Orientation.Horizontal,
+                    controller = rememberScrollableController {
+                        draggedOffset += it
+                        it
+                    }
+                )
             ) {
                 LazyRowFor(
                     items = items,
@@ -315,12 +359,12 @@
             .performGesture {
                 draggedOffset = 0f
                 down(Offset(x = 10f, y = 10f))
-                moveBy(Offset(x = -50f, y = 0f))
+                moveBy(Offset(x = -expectedDragOffset, y = 0f))
                 up()
             }
 
         rule.runOnIdle {
-            Truth.assertThat(draggedOffset).isEqualTo(-50f)
+            Truth.assertThat(draggedOffset).isEqualTo(-expectedDragOffset)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
index 6540372..f4d3fd4 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.animation.core.snap
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
@@ -836,4 +837,67 @@
                 .that(redrawCount[1]).isEqualTo(2)
         }
     }
+
+    @Test
+    fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
+        val items = (0..1).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        val itemSizeMinusOne = with(rule.density) { 29.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRowFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.width(itemSizeMinusOne).testTag(LazyRowForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.height(30.dp).width(itemSizeMinusOne)
+                    } else {
+                        Modifier.height(20.dp).width(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyRowForTag)
+            .assertHeightIsEqualTo(20.dp)
+    }
+
+    @Test
+    fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
+        val items = (0..2).toList()
+        val itemSize = with(rule.density) { 30.toDp() }
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyRowFor(
+                items = items,
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.width(itemSize * 1.75f).testTag(LazyRowForTag)
+            ) {
+                Spacer(
+                    if (it == 0) {
+                        Modifier.height(30.dp).width(itemSize / 2)
+                    } else if (it == 1) {
+                        Modifier.height(20.dp).width(itemSize / 2)
+                    } else {
+                        Modifier.height(20.dp).width(itemSize)
+                    }
+                )
+            }
+        }
+
+        state.scrollBy(itemSize)
+
+        rule.onNodeWithTag(LazyRowForTag)
+            .assertHeightIsEqualTo(30.dp)
+    }
+
+    private fun LazyListState.scrollBy(offset: Dp) {
+        runBlocking {
+            smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
+        }
+    }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldInteractionsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldInteractionsTest.kt
new file mode 100644
index 0000000..8050c7c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldInteractionsTest.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import androidx.compose.foundation.Interaction
+import androidx.compose.foundation.InteractionState
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.ExperimentalFocus
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focusRequester
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.cancel
+import androidx.compose.ui.test.center
+import androidx.compose.ui.test.down
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.moveBy
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performGesture
+import androidx.compose.ui.test.up
+import androidx.compose.ui.text.InternalTextApi
+import androidx.compose.ui.text.input.TextFieldValue
+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 org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(InternalTextApi::class, ExperimentalFocus::class)
+class TextFieldInteractionsTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    val testTag = "textField"
+
+    @Test
+    fun coreTextField_interaction_pressed() {
+        val state = mutableStateOf(TextFieldValue(""))
+        val interactionState = InteractionState()
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(testTag),
+                value = state.value,
+                onValueChange = { state.value = it },
+                interactionState = interactionState
+            )
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Pressed)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                down(center)
+            }
+        assertThat(interactionState.value).contains(Interaction.Pressed)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                up()
+            }
+        assertThat(interactionState.value).doesNotContain(Interaction.Pressed)
+    }
+
+    @Test
+    fun coreTextField_interaction_pressed_removedWhenCancelled() {
+        val state = mutableStateOf(TextFieldValue(""))
+        val interactionState = InteractionState()
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(testTag),
+                value = state.value,
+                onValueChange = { state.value = it },
+                interactionState = interactionState
+            )
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Pressed)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                down(center)
+            }
+        assertThat(interactionState.value).contains(Interaction.Pressed)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                cancel()
+            }
+        assertThat(interactionState.value).doesNotContain(Interaction.Pressed)
+    }
+
+    @Test
+    fun coreTextField_interaction_focused() {
+        val state = mutableStateOf(TextFieldValue(""))
+        val interactionState = InteractionState()
+        val otherRequester = FocusRequester()
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(testTag),
+                value = state.value,
+                onValueChange = { state.value = it },
+                interactionState = interactionState
+            )
+            Box(
+                modifier = Modifier.size(10.dp).focusRequester(otherRequester).focusable(),
+            )
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Focused)
+        rule.onNodeWithTag(testTag)
+            .performClick()
+        assertThat(interactionState.value).contains(Interaction.Focused)
+        rule.runOnIdle {
+            // request focus on the box so TextField will lose it
+            otherRequester.requestFocus()
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Focused)
+    }
+
+    @Test
+    fun coreTextField_interaction_horizontally_dragged() {
+        val state = mutableStateOf(TextFieldValue("test ".repeat(100)))
+        val interactionState = InteractionState()
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(testTag),
+                value = state.value,
+                singleLine = true,
+                onValueChange = { state.value = it },
+                interactionState = interactionState
+            )
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                down(center)
+                moveBy(Offset(x = 100f, y = 0f))
+            }
+        assertThat(interactionState.value).contains(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                up()
+            }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+    }
+
+    @Test
+    fun coreTextField_interaction_dragged_horizontally_cancelled() {
+        val state = mutableStateOf(TextFieldValue("test ".repeat(100)))
+        val interactionState = InteractionState()
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.testTag(testTag),
+                value = state.value,
+                singleLine = true,
+                onValueChange = { state.value = it },
+                interactionState = interactionState
+            )
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                down(center)
+                moveBy(Offset(x = 100f, y = 0f))
+            }
+        assertThat(interactionState.value).contains(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                cancel()
+            }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+    }
+
+    @Test
+    fun coreTextField_interaction_vertically_dragged() {
+        val state = mutableStateOf(TextFieldValue("test\n".repeat(10)))
+        val interactionState = InteractionState()
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.size(50.dp).testTag(testTag),
+                value = state.value,
+                maxLines = 3,
+                onValueChange = { state.value = it },
+                interactionState = interactionState
+            )
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                down(center)
+                moveBy(Offset(x = 0f, y = 150f))
+            }
+        assertThat(interactionState.value).contains(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                up()
+            }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+    }
+
+    @Test
+    fun coreTextField_interaction_dragged_vertically_cancelled() {
+        val state = mutableStateOf(TextFieldValue("test\n".repeat(10)))
+        val interactionState = InteractionState()
+        rule.setContent {
+            BasicTextField(
+                modifier = Modifier.size(50.dp).testTag(testTag),
+                value = state.value,
+                maxLines = 3,
+                onValueChange = { state.value = it },
+                interactionState = interactionState
+            )
+        }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                down(center)
+                moveBy(Offset(x = 0f, y = 150f))
+            }
+        assertThat(interactionState.value).contains(Interaction.Dragged)
+        rule.onNodeWithTag(testTag)
+            .performGesture {
+                cancel()
+            }
+        assertThat(interactionState.value).doesNotContain(Interaction.Dragged)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
index 036f2c4..c338a45 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.selection.Selection
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.InternalTextApi
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.TextDelegate
@@ -60,7 +61,10 @@
     style = FontStyle.Normal
 )
 
-@OptIn(InternalTextApi::class)
+@OptIn(
+    InternalTextApi::class,
+    ExperimentalTextApi::class
+)
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 class MultiWidgetSelectionDelegateTest {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt
index 1570a3a..119d458 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt
@@ -17,7 +17,7 @@
 package androidx.compose.foundation.text.selection
 
 import android.view.View
-import androidx.compose.ui.node.Owner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.test.junit4.ComposeTestRule
 import androidx.compose.ui.window.isPopupLayout
 import androidx.test.espresso.Espresso
@@ -34,7 +34,7 @@
 ) {
     // Make sure that current measurement/drawing is finished
     runOnIdle { }
-    Espresso.onView(CoreMatchers.instanceOf(Owner::class.java))
+    Espresso.onView(CoreMatchers.instanceOf(ViewRootForTest::class.java))
         .inRoot(DoubleSelectionHandleMatcher(index))
         .check(ViewAssertions.matches(viewMatcher))
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/res/drawable-hdpi/ic_image_test.png b/compose/foundation/foundation/src/androidAndroidTest/res/drawable-hdpi/ic_image_test.png
new file mode 100644
index 0000000..4eb583c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/res/drawable-hdpi/ic_image_test.png
Binary files differ
diff --git a/car/app/app/src/androidTest/AndroidManifest.xml b/compose/foundation/foundation/src/androidAndroidTest/res/drawable/ic_vector_square_asset_test.xml
similarity index 66%
copy from car/app/app/src/androidTest/AndroidManifest.xml
copy to compose/foundation/foundation/src/androidAndroidTest/res/drawable/ic_vector_square_asset_test.xml
index 3bc2684..13c4896 100644
--- a/car/app/app/src/androidTest/AndroidManifest.xml
+++ b/compose/foundation/foundation/src/androidAndroidTest/res/drawable/ic_vector_square_asset_test.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   Copyright 2020 The Android Open Source Project
 
@@ -14,6 +13,13 @@
   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.car.app">
-</manifest>
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="#FF0000"
+        android:pathData="L24,0,24,24,0,24z"/>
+</vector>
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingCalculator.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingCalculator.kt
index bb7cbcf..6bb3c64 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingCalculator.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/animation/AndroidFlingCalculator.kt
@@ -123,7 +123,7 @@
         fun velocity(time: Long): Float {
             val splinePos = if (duration > 0) time / duration.toFloat() else 1f
             return AndroidFlingSpline.flingPosition(splinePos).velocityCoefficient *
-                distance / duration * 1000.0f
+                sign(initialVelocity) * distance / duration * 1000.0f
         }
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
index 25d8998..1477c75 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
@@ -17,8 +17,8 @@
 package androidx.compose.foundation.gestures
 
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.HandlePointerInputScope
+import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
@@ -36,7 +36,8 @@
 /**
  * Waits for drag motion to pass [touch slop][ViewConfiguration.touchSlop], using [pointerId] as
  * the pointer to examine. If [pointerId] is raised, another pointer from those that are down
- * will be chosen to lead the gesture, and if none are down, `null` is returned.
+ * will be chosen to lead the gesture, and if none are down, `null` is returned. If [pointerId]
+ * is not down when [awaitTouchSlopOrCancellation] is called, then `null` is returned.
 
  * [onTouchSlopReached] is called after [ViewConfiguration.touchSlop] motion in the any direction
  * with the change that caused the motion beyond touch slop and the [Offset] beyond touch slop that
@@ -54,11 +55,13 @@
  * @see awaitHorizontalTouchSlopOrCancellation
  * @see awaitVerticalTouchSlopOrCancellation
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.awaitTouchSlopOrCancellation(
     pointerId: PointerId,
     onTouchSlopReached: (change: PointerInputChange, overSlop: Offset) -> Unit
 ): PointerInputChange? {
+    if (currentEvent.isPointerUp(pointerId)) {
+        return null // The pointer has already been lifted, so the gesture is canceled
+    }
     var offset = Offset.Zero
     val touchSlop = viewConfiguration.touchSlop
 
@@ -119,7 +122,6 @@
  * @see horizontalDrag
  * @see verticalDrag
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.drag(
     pointerId: PointerId,
     onDrag: (PointerInputChange) -> Unit
@@ -144,7 +146,8 @@
  * that is down will be used, if available, so the returned [PointerInputChange.id] may
  * differ from [pointerId]. If the position change in the any direction has been
  * consumed by the [PointerEventPass.Main] pass, then the drag is considered canceled and `null`
- * is returned.
+ * is returned.  If [pointerId] is not down when [awaitDragOrCancellation] is called, then
+ * `null` is returned.
  *
  * Example Usage:
  * @sample androidx.compose.foundation.samples.AwaitDragOrCancellationSample
@@ -153,10 +156,12 @@
  * @see awaitHorizontalDragOrCancellation
  * @see drag
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.awaitDragOrCancellation(
     pointerId: PointerId,
 ): PointerInputChange? {
+    if (currentEvent.isPointerUp(pointerId)) {
+        return null // The pointer has already been lifted, so the gesture is canceled
+    }
     val change = awaitDragOrUp(pointerId) { it.positionChangedIgnoreConsumed() }
     return if (change.anyPositionChangeConsumed()) null else change
 }
@@ -175,7 +180,6 @@
  * @see detectVerticalDragGestures
  * @see detectHorizontalDragGestures
  */
-@ExperimentalPointerInput
 suspend fun PointerInputScope.detectDragGestures(
     onDragEnd: () -> Unit = { },
     onDragCancel: () -> Unit = { },
@@ -207,7 +211,9 @@
  * Waits for vertical drag motion to pass [touch slop][ViewConfiguration.touchSlop], using
  * [pointerId] as the pointer to examine. If [pointerId] is raised, another pointer from
  * those that are down will be chosen to lead the gesture, and if none are down, `null` is returned.
-
+ * If [pointerId] is not down when [awaitVerticalTouchSlopOrCancellation] is called, then `null`
+ * is returned.
+ *
  * [onTouchSlopReached] is called after [ViewConfiguration.touchSlop] motion in the vertical
  * direction with the change that caused the motion beyond touch slop and the pixels beyond touch
  * slop. [onTouchSlopReached] should consume the position change if it accepts the motion.
@@ -224,7 +230,6 @@
  * @see awaitHorizontalTouchSlopOrCancellation
  * @see awaitTouchSlopOrCancellation
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.awaitVerticalTouchSlopOrCancellation(
     pointerId: PointerId,
     onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
@@ -254,7 +259,6 @@
  * @see horizontalDrag
  * @see drag
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.verticalDrag(
     pointerId: PointerId,
     onDrag: (PointerInputChange) -> Unit
@@ -272,7 +276,8 @@
  * that is down will be used, if available, so the returned [PointerInputChange.id] may
  * differ from [pointerId]. If the position change in the vertical direction has been
  * consumed by the [PointerEventPass.Main] pass, then the drag is considered canceled and `null` is
- * returned.
+ * returned. If [pointerId] is not down when [awaitVerticalDragOrCancellation] is called, then
+ * `null` is returned.
  *
  * Example Usage:
  * @sample androidx.compose.foundation.samples.AwaitVerticalDragOrCancellationSample
@@ -281,10 +286,12 @@
  * @see awaitDragOrCancellation
  * @see verticalDrag
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.awaitVerticalDragOrCancellation(
     pointerId: PointerId,
 ): PointerInputChange? {
+    if (currentEvent.isPointerUp(pointerId)) {
+        return null // The pointer has already been lifted, so the gesture is canceled
+    }
     val change = awaitDragOrUp(pointerId) { it.positionChangeIgnoreConsumed().y != 0f }
     return if (change.consumed.positionChange.y != 0f) null else change
 }
@@ -307,7 +314,6 @@
  * @see detectDragGestures
  * @see detectHorizontalDragGestures
  */
-@ExperimentalPointerInput
 suspend fun PointerInputScope.detectVerticalDragGestures(
     onDragEnd: () -> Unit = { },
     onDragCancel: () -> Unit = { },
@@ -341,7 +347,8 @@
  * direction with the change that caused the motion beyond touch slop and the pixels beyond touch
  * slop. [onTouchSlopReached] should consume the position change if it accepts the motion.
  * If it does, then the method returns that [PointerInputChange]. If not, touch slop detection will
- * continue.
+ * continue. If [pointerId] is not down when [awaitHorizontalTouchSlopOrCancellation] is called,
+ * then `null` is returned.
  *
  * @return The [PointerInputChange] that was consumed in [onTouchSlopReached] or `null` if all
  * pointers are raised before touch slop is detected or another gesture consumed the position
@@ -353,7 +360,6 @@
  * @see awaitVerticalTouchSlopOrCancellation
  * @see awaitTouchSlopOrCancellation
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.awaitHorizontalTouchSlopOrCancellation(
     pointerId: PointerId,
     onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
@@ -380,7 +386,6 @@
  * @see verticalDrag
  * @see drag
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.horizontalDrag(
     pointerId: PointerId,
     onDrag: (PointerInputChange) -> Unit
@@ -398,7 +403,8 @@
  * that is down will be used, if available, so the returned [PointerInputChange.id] may
  * differ from [pointerId]. If the position change in the horizontal direction has been
  * consumed by the [PointerEventPass.Main] pass, then the drag is considered canceled and `null`
- * is returned.
+ * is returned. If [pointerId] is not down when [awaitHorizontalDragOrCancellation] is called,
+ * then `null` is returned.
  *
  * Example Usage:
  * @sample androidx.compose.foundation.samples.AwaitHorizontalDragOrCancellationSample
@@ -407,10 +413,12 @@
  * @see awaitVerticalDragOrCancellation
  * @see awaitDragOrCancellation
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.awaitHorizontalDragOrCancellation(
     pointerId: PointerId,
 ): PointerInputChange? {
+    if (currentEvent.isPointerUp(pointerId)) {
+        return null // The pointer has already been lifted, so the gesture is canceled
+    }
     val change = awaitDragOrUp(pointerId) { it.positionChangeIgnoreConsumed().x != 0f }
     return if (change.consumed.positionChange.x != 0f) null else change
 }
@@ -433,7 +441,6 @@
  * @see detectVerticalDragGestures
  * @see detectDragGestures
  */
-@ExperimentalPointerInput
 suspend fun PointerInputScope.detectHorizontalDragGestures(
     onDragEnd: () -> Unit = { },
     onDragCancel: () -> Unit = { },
@@ -468,13 +475,15 @@
  * @return `true` when the gesture ended with all pointers up and `false` when the gesture
  * was canceled.
  */
-@ExperimentalPointerInput
 private suspend inline fun HandlePointerInputScope.drag(
     pointerId: PointerId,
     onDrag: (PointerInputChange) -> Unit,
     motionFromChange: (PointerInputChange) -> Float,
     motionConsumed: (PointerInputChange) -> Boolean
 ): Boolean {
+    if (currentEvent.isPointerUp(pointerId)) {
+        return false // The pointer has already been lifted, so the gesture is canceled
+    }
     var pointer = pointerId
     while (true) {
         val change = awaitDragOrUp(pointer) { motionFromChange(it) != 0f }
@@ -499,7 +508,6 @@
  * returned. When a drag is detected, that [PointerInputChange] is returned. A drag is
  * only detected when [hasDragged] returns `true`.
  */
-@ExperimentalPointerInput
 private suspend inline fun HandlePointerInputScope.awaitDragOrUp(
     pointerId: PointerId,
     hasDragged: (PointerInputChange) -> Boolean
@@ -526,7 +534,8 @@
  * Waits for drag motion along one axis based on [getDragDirectionValue] to pass touch slop,
  * using [pointerId] as the pointer to examine. If [pointerId] is raised, another pointer
  * from those that are down will be chosen to lead the gesture, and if none are down,
- * `null` is returned.
+ * `null` is returned. If [pointerId] is not down when [awaitTouchSlopOrCancellation] is called,
+ * then `null` is returned.
  *
  * When touch slop is detected, [onTouchSlopReached] is called with the change and the distance
  * beyond the touch slop. [getDragDirectionValue] should return the position change in the direction
@@ -544,7 +553,6 @@
  * `null` if all pointers are raised or the position change was consumed by another gesture
  * detector.
  */
-@ExperimentalPointerInput
 private suspend inline fun HandlePointerInputScope.awaitTouchSlopOrCancellation(
     pointerId: PointerId,
     onTouchSlopReached: (PointerInputChange, Float) -> Unit,
@@ -552,6 +560,9 @@
     consumeMotion: (PointerInputChange, Float) -> Unit,
     getCrossDirectionValue: (Offset) -> Float
 ): PointerInputChange? {
+    if (currentEvent.isPointerUp(pointerId)) {
+        return null // The pointer has already been lifted, so the gesture is canceled
+    }
     val touchSlop = viewConfiguration.touchSlop
     var pointer: PointerId = pointerId
     var totalPositionChange = 0f
@@ -617,3 +628,6 @@
         }
     }
 }
+
+private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean =
+    changes.firstOrNull { it.id == pointerId }?.current?.down != true
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt
index b116305..d545ee0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt
@@ -15,7 +15,6 @@
  */
 package androidx.compose.foundation.gestures
 
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.HandlePointerInputScope
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerInputScope
@@ -37,7 +36,6 @@
  * exits if [isActive] is `false`.
  */
 @OptIn(InternalCoroutinesApi::class, ExperimentalStdlibApi::class)
-@ExperimentalPointerInput
 suspend fun PointerInputScope.forEachGesture(block: suspend PointerInputScope.() -> Unit) {
     while (isActive) {
         try {
@@ -59,13 +57,12 @@
  * Returns `true` if the current state of the pointer events has all pointers up and `false`
  * if any of the pointers are down.
  */
-@ExperimentalPointerInput
-internal fun HandlePointerInputScope.allPointersUp(): Boolean = !currentPointers.fastAny { it.down }
+internal fun HandlePointerInputScope.allPointersUp(): Boolean =
+    !currentEvent.changes.fastAny { it.current.down }
 
 /**
  * Waits for all pointers to be up before returning.
  */
-@ExperimentalPointerInput
 internal suspend fun PointerInputScope.awaitAllPointersUp() {
     handlePointerInput { awaitAllPointersUp() }
 }
@@ -73,7 +70,6 @@
 /**
  * Waits for all pointers to be up before returning.
  */
-@ExperimentalPointerInput
 internal suspend fun HandlePointerInputScope.awaitAllPointersUp() {
     if (!allPointersUp()) {
         do {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetector.kt
index 0c4efef..ccf797d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetector.kt
@@ -17,7 +17,6 @@
 package androidx.compose.foundation.gestures
 
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
@@ -32,9 +31,6 @@
 import kotlin.math.abs
 import kotlin.math.atan2
 
-private val NoRotateZoom: (Float) -> Unit = { }
-private val NoPan: (Offset) -> Unit = { }
-
 /**
  * A gesture detector for rotationg, panning, and zoom. Once touch slop has been reached, the
  * user can use rotation, panning and zoom gestures. [onRotate] will be called when rotation
@@ -51,12 +47,9 @@
  * Example Usage:
  * @sample androidx.compose.foundation.samples.DetectMultitouchGestures
  */
-@ExperimentalPointerInput
 suspend fun PointerInputScope.detectMultitouchGestures(
     panZoomLock: Boolean = false,
-    onRotate: (rotation: Float) -> Unit = NoRotateZoom,
-    onZoom: (zoom: Float) -> Unit = NoRotateZoom,
-    onPan: (pan: Offset) -> Unit = NoPan
+    onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit
 ) {
     forEachGesture {
         handlePointerInput {
@@ -94,21 +87,21 @@
                             lockedToPanZoom = panZoomLock && rotationMotion < touchSlop
                         }
                     }
+
                     if (pastTouchSlop) {
+                        val centroid = event.calculateCentroid(useCurrent = false)
+                        val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
+                        if (effectiveRotation != 0f ||
+                            zoomChange != 1f ||
+                            panChange != Offset.Zero
+                        ) {
+                            onGesture(centroid, panChange, zoomChange, effectiveRotation)
+                        }
                         event.changes.fastForEach {
                             if (it.positionChanged()) {
                                 it.consumeAllChanges()
                             }
                         }
-                        if (!lockedToPanZoom && rotationChange != 0f) {
-                            onRotate(rotationChange)
-                        }
-                        if (zoomChange != 1f) {
-                            onZoom(zoomChange)
-                        }
-                        if (panChange != Offset.Zero) {
-                            onPan(panChange)
-                        }
                     }
                 }
             } while (!canceled && event.changes.fastAny { it.current.down })
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 830e5bc..6949e0b 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
@@ -35,17 +35,25 @@
 import androidx.compose.runtime.AtomicReference
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.dispatch.withFrameMillis
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.onDispose
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.gesture.Direction
 import androidx.compose.ui.gesture.ScrollCallback
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollSource
+import androidx.compose.ui.gesture.nestedscroll.nestedScroll
 import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
 import androidx.compose.ui.platform.AmbientAnimationClock
 import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.minus
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.sync.Mutex
@@ -258,24 +266,85 @@
     }
 
     private val animatedFloat =
-        DeltaAnimatedFloat(0f, clocksProxy, consumeScrollDelta)
+        DeltaAnimatedFloat(0f, clocksProxy) {
+            dispatchScroll(it.reverseIfNeeded(), NestedScrollSource.Fling)
+        }
 
-    /**
-     * current position for scrollable
-     */
-    internal var value: Float
-        get() = animatedFloat.value
-        set(value) = animatedFloat.snapTo(value)
+    private var orientation by mutableStateOf(Orientation.Vertical)
+    private var reverseDirection by mutableStateOf(false)
 
-    internal fun fling(velocity: Float, onScrollEnd: (Float) -> Unit) {
+    // this is not good, should be gone when we have sync (suspend) animation and scroll
+    internal fun update(orientation: Orientation, reverseDirection: Boolean) {
+        this.orientation = orientation
+        this.reverseDirection = reverseDirection
+    }
+
+    internal val nestedScrollDispatcher = NestedScrollDispatcher()
+
+    internal val nestedScrollConnection = object : NestedScrollConnection {
+        override fun onPostScroll(
+            consumed: Offset,
+            available: Offset,
+            source: NestedScrollSource
+        ): Offset = performDeltaConsumption(available)
+
+        override fun onPostFling(
+            consumed: Velocity,
+            available: Velocity,
+            onFinished: (Velocity) -> Unit
+        ) {
+            performFlingInternal(available.pixelsPerSecond) { leftAfterUs ->
+                onFinished.invoke(available - Velocity(leftAfterUs))
+            }
+        }
+    }
+
+    internal fun dispatchScroll(scrollDelta: Float, source: NestedScrollSource) {
+        val scrollOffset = scrollDelta.toOffset()
+        val preConsumedByParent = nestedScrollDispatcher.dispatchPreScroll(scrollOffset, source)
+
+        val scrollAvailable = scrollOffset - preConsumedByParent
+        val consumed = performDeltaConsumption(scrollAvailable)
+        val leftForParent = scrollAvailable - consumed
+        nestedScrollDispatcher.dispatchPostScroll(consumed, leftForParent, source)
+    }
+
+    private fun performDeltaConsumption(delta: Offset): Offset {
+        // reverse once for users if needed and then back to the original axis system
+        return consumeScrollDelta(delta.toFloat().reverseIfNeeded()).reverseIfNeeded().toOffset()
+    }
+
+    internal fun dispatchFling(velocity: Float, onScrollEnd: (Float) -> Unit) {
+        val consumedByParent =
+            nestedScrollDispatcher.dispatchPreFling(Velocity(velocity.toOffset()))
+        val available = velocity.toOffset() - consumedByParent.pixelsPerSecond
+        performFlingInternal(available) { velocityLeft ->
+            // when notifying users code -- reverse if needed to obey their setting
+            onScrollEnd(velocityLeft.toFloat().reverseIfNeeded())
+            nestedScrollDispatcher.dispatchPostFling(
+                Velocity(available - velocityLeft),
+                Velocity(velocityLeft)
+            )
+        }
+    }
+
+    private fun performFlingInternal(velocity: Offset, onScrollEnd: (Offset) -> Unit) {
         animatedFloat.fling(
             config = flingConfig,
-            startVelocity = velocity,
+            startVelocity = velocity.toFloat().reverseIfNeeded(),
             onAnimationEnd = { _, _, velocityLeft ->
-                onScrollEnd(velocityLeft)
+                onScrollEnd(velocityLeft.reverseIfNeeded().toOffset())
             }
         )
     }
+
+    private fun Float.toOffset(): Offset =
+        if (orientation == Orientation.Horizontal) Offset(this, 0f) else Offset(0f, this)
+
+    private fun Offset.toFloat(): Float =
+        if (orientation == Orientation.Horizontal) this.x else this.y
+
+    private fun Float.reverseIfNeeded(): Float = if (reverseDirection) this * -1 else this
 }
 
 /**
@@ -315,6 +384,7 @@
     onScrollStopped: (velocity: Float) -> Unit = {}
 ): Modifier = composed(
     factory = {
+        controller.update(orientation, reverseDirection)
         onDispose {
             controller.stopAnimation()
             controller.interactionState?.removeInteraction(Interaction.Dragged)
@@ -333,10 +403,9 @@
             override fun onScroll(scrollDistance: Float): Float {
                 if (!enabled) return 0f
                 controller.stopFlingAnimation()
-                val toConsume = if (reverseDirection) scrollDistance * -1 else scrollDistance
-                val consumed = controller.consumeScrollDelta(toConsume)
-                controller.value = controller.value + consumed
-                return if (reverseDirection) consumed * -1 else consumed
+                controller.dispatchScroll(scrollDistance, NestedScrollSource.Drag)
+                // consume everything since we handle nested scrolling separately
+                return scrollDistance
             }
 
             override fun onCancel() {
@@ -349,8 +418,8 @@
             override fun onStop(velocity: Float) {
                 controller.interactionState?.removeInteraction(Interaction.Dragged)
                 if (enabled) {
-                    controller.fling(
-                        velocity = if (reverseDirection) velocity * -1 else velocity,
+                    controller.dispatchFling(
+                        velocity = velocity,
                         onScrollEnd = onScrollStopped
                     )
                 }
@@ -365,7 +434,7 @@
         ).mouseScrollable(
             scrollCallback,
             orientation
-        )
+        ).nestedScroll(controller.nestedScrollConnection, controller.nestedScrollDispatcher)
     },
     inspectorInfo = debugInspectorInfo {
         name = "scrollable"
@@ -398,7 +467,7 @@
 private class DeltaAnimatedFloat(
     initial: Float,
     clock: AnimationClockObservable,
-    private val onDelta: (Float) -> Float
+    private val onDelta: (Float) -> Unit
 ) : AnimatedFloat(clock, Spring.DefaultDisplacementThreshold) {
 
     override var value = initial
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
index d9f141b..ca356ef 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
@@ -17,7 +17,6 @@
 package androidx.compose.foundation.gestures
 
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.HandlePointerInputScope
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
@@ -86,7 +85,6 @@
  * the gestures are considered canceled. [onDoubleTap], [onLongPress], and [onTap] will not be
  * called after a gesture has been canceled.
  */
-@ExperimentalPointerInput
 suspend fun PointerInputScope.detectTapGestures(
     onDoubleTap: (() -> Unit)? = null,
     onLongPress: (() -> Unit)? = null,
@@ -186,7 +184,6 @@
  * Reads events until the first down is received. If [requireUnconsumed] is `true` and the first
  * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored.
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.awaitFirstDown(
     requireUnconsumed: Boolean = true
 ): PointerInputChange {
@@ -208,7 +205,6 @@
  * pass. If the gesture was not canceled, the final up change is returned or `null` if the
  * event was canceled.
  */
-@ExperimentalPointerInput
 suspend fun HandlePointerInputScope.waitForUpOrCancellation(): PointerInputChange? {
     while (true) {
         val event = awaitPointerEvent(PointerEventPass.Main)
@@ -233,7 +229,6 @@
 /**
  * Consumes all event changes in the [PointerEventPass.Initial] until all pointers are up.
  */
-@ExperimentalPointerInput
 private suspend fun PointerInputScope.consumeAllEventsUntilUp() {
     handlePointerInput {
         if (!allPointersUp()) {
@@ -251,7 +246,6 @@
  * not detected within [ViewConfiguration.doubleTapTimeout] of [upTime], `null` is returned.
  * Otherwise, the down event is returned.
  */
-@ExperimentalPointerInput
 private suspend fun PointerInputScope.detectSecondTapDown(
     upTime: Uptime
 ): PointerInputChange? {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/DataIndex.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/DataIndex.kt
new file mode 100644
index 0000000..9c34dd5
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/DataIndex.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+/**
+ * Represents an index in the list of items of lazy list.
+ */
+@Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_FEATURE_WARNING")
+internal inline class DataIndex(val value: Int) {
+    inline operator fun inc(): DataIndex = DataIndex(value + 1)
+    inline operator fun dec(): DataIndex = DataIndex(value - 1)
+    inline operator fun plus(i: Int): DataIndex = DataIndex(value + i)
+    inline operator fun minus(i: Int): DataIndex = DataIndex(value - i)
+    inline operator fun minus(i: DataIndex): DataIndex = DataIndex(value - i.value)
+    inline operator fun compareTo(other: DataIndex): Int = value - other.value
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index e3ebd07..9da735e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.InternalLayoutApi
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -170,17 +172,28 @@
  * @param modifier the modifier to apply to this layout
  * @param state the state object to be used to control or observe the list's state
  * @param contentPadding a padding around the whole content. This will add padding for the
- * content after it has been clipped, which is not possible via [modifier] param. Note that it is
- * **not** a padding applied for each item's content
+ * content after it has been clipped, which is not possible via [modifier] param. You can use it
+ * to add a padding before the first item or after the last one. If you want to add a spacing
+ * between each item use [horizontalArrangement].
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the end to the start and [LazyListState.firstVisibleItemIndex] == 0 will mean
+ * the first item is located at the end.
+ * @param horizontalArrangement The horizontal arrangement of the layout's children. This allows
+ * to add a spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
  * @param verticalAlignment the vertical alignment applied to the items
  * @param content a block which describes the content. Inside this block you can use methods like
  * [LazyListScope.item] to add a single item or [LazyListScope.items] to add a list of items.
  */
+@OptIn(InternalLayoutApi::class)
 @Composable
 fun LazyRow(
     modifier: Modifier = Modifier,
     state: LazyListState = rememberLazyListState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    horizontalArrangement: Arrangement.Horizontal =
+        if (!reverseLayout) Arrangement.Start else Arrangement.End,
     verticalAlignment: Alignment.Vertical = Alignment.Top,
     content: LazyListScope.() -> Unit
 ) {
@@ -193,7 +206,9 @@
         state = state,
         contentPadding = contentPadding,
         verticalAlignment = verticalAlignment,
-        isVertical = false
+        horizontalArrangement = horizontalArrangement,
+        isVertical = false,
+        reverseLayout = reverseLayout
     ) { index ->
         scope.contentFor(index, this)
     }
@@ -207,20 +222,31 @@
  *
  * @sample androidx.compose.foundation.samples.LazyColumnSample
  *
- * @param modifier the modifier to apply to this layout
- * @param state the state object to be used to control or observe the list's state
- * @param contentPadding a padding around the whole content. This will add padding for the
- * content after it has been clipped, which is not possible via [modifier] param. Note that it is
- * **not** a padding applied for each item's content
- * @param horizontalAlignment the horizontal alignment applied to the items
+ * @param modifier the modifier to apply to this layout.
+ * @param state the state object to be used to control or observe the list's state.
+ * @param contentPadding a padding around the whole content. This will add padding for the.
+ * content after it has been clipped, which is not possible via [modifier] param. You can use it
+ * to add a padding before the first item or after the last one. If you want to add a spacing
+ * between each item use [verticalArrangement].
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the bottom to the top and [LazyListState.firstVisibleItemIndex] == 0 will mean
+ * we scrolled to the bottom.
+ * @param verticalArrangement The vertical arrangement of the layout's children. This allows
+ * to add a spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
+ * @param horizontalAlignment the horizontal alignment applied to the items.
  * @param content a block which describes the content. Inside this block you can use methods like
  * [LazyListScope.item] to add a single item or [LazyListScope.items] to add a list of items.
  */
+@OptIn(InternalLayoutApi::class)
 @Composable
 fun LazyColumn(
     modifier: Modifier = Modifier,
     state: LazyListState = rememberLazyListState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    verticalArrangement: Arrangement.Vertical =
+        if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
     horizontalAlignment: Alignment.Horizontal = Alignment.Start,
     content: LazyListScope.() -> Unit
 ) {
@@ -233,7 +259,9 @@
         state = state,
         contentPadding = contentPadding,
         horizontalAlignment = horizontalAlignment,
-        isVertical = true
+        verticalArrangement = verticalArrangement,
+        isVertical = true,
+        reverseLayout = reverseLayout
     ) { index ->
         scope.contentFor(index, this)
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
index 2031273..2986425 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.InternalLayoutApi
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.runtime.Composable
@@ -34,9 +36,16 @@
  * @param items the backing list of data to display
  * @param modifier the modifier to apply to this layout
  * @param state the state object to be used to control or observe the list's state
- * @param contentPadding a padding around the whole content. This will add padding for the
- * content after it has been clipped, which is not possible via [modifier] param. Note that it is
- * **not** a padding applied for each item's content
+ * @param contentPadding a padding around the whole content. This will add padding for the.
+ * content after it has been clipped, which is not possible via [modifier] param. You can use it
+ * to add a padding before the first item or after the last one. If you want to add a spacing
+ * between each item use [verticalArrangement].
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the bottom to the top and [LazyListState.firstVisibleItemIndex] == 0 will mean
+ * we scrolled to the bottom.
+ * @param verticalArrangement The vertical arrangement of the layout's children. This allows
+ * to add a spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
  * @param horizontalAlignment the horizontal alignment applied to the items
  * @param itemContent emits the UI for an item from [items] list. May emit any number of components,
  * which will be stacked vertically. Note that [LazyColumnFor] can start scrolling incorrectly
@@ -44,12 +53,16 @@
  * content asynchronously please reserve some space for the item, for example using [Spacer].
  * Use [LazyColumnForIndexed] if you need to have both index and item params.
  */
+@OptIn(InternalLayoutApi::class)
 @Composable
 fun <T> LazyColumnFor(
     items: List<T>,
     modifier: Modifier = Modifier,
     state: LazyListState = rememberLazyListState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    verticalArrangement: Arrangement.Vertical =
+        if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
     horizontalAlignment: Alignment.Horizontal = Alignment.Start,
     itemContent: @Composable LazyItemScope.(T) -> Unit
 ) {
@@ -57,7 +70,9 @@
         modifier = modifier,
         state = state,
         contentPadding = contentPadding,
-        horizontalAlignment = horizontalAlignment
+        horizontalAlignment = horizontalAlignment,
+        verticalArrangement = verticalArrangement,
+        reverseLayout = reverseLayout
     ) {
         items(items, itemContent)
     }
@@ -76,9 +91,16 @@
  * @param items the backing list of data to display
  * @param modifier the modifier to apply to this layout
  * @param state the state object to be used to control or observe the list's state
- * @param contentPadding a padding around the whole content. This will add padding for the
- * content after it has been clipped, which is not possible via [modifier] param. Note that it is
- * **not** a padding applied for each item's content
+ * @param contentPadding a padding around the whole content. This will add padding for the.
+ * content after it has been clipped, which is not possible via [modifier] param. You can use it
+ * to add a padding before the first item or after the last one. If you want to add a spacing
+ * between each item use [verticalArrangement].
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the bottom to the top and [LazyListState.firstVisibleItemIndex] == 0 will mean
+ * we scrolled to the bottom.
+ * @param verticalArrangement The vertical arrangement of the layout's children. This allows
+ * to add a spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
  * @param horizontalAlignment the horizontal alignment applied to the items
  * @param itemContent emits the UI for an item from [items] list. It has two params: first one is
  * an index in the [items] list, and the second one is the item at this index from [items] list.
@@ -87,12 +109,16 @@
  * recompose with the real content, so even if you load the content asynchronously please reserve
  * some space for the item, for example using [Spacer].
  */
+@OptIn(InternalLayoutApi::class)
 @Composable
 fun <T> LazyColumnForIndexed(
     items: List<T>,
     modifier: Modifier = Modifier,
     state: LazyListState = rememberLazyListState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    verticalArrangement: Arrangement.Vertical =
+        if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
     horizontalAlignment: Alignment.Horizontal = Alignment.Start,
     itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
 ) {
@@ -100,7 +126,9 @@
         modifier = modifier,
         state = state,
         contentPadding = contentPadding,
-        horizontalAlignment = horizontalAlignment
+        horizontalAlignment = horizontalAlignment,
+        verticalArrangement = verticalArrangement,
+        reverseLayout = reverseLayout
     ) {
         itemsIndexed(items, itemContent)
     }
@@ -114,25 +142,36 @@
  *
  * @sample androidx.compose.foundation.samples.LazyRowForSample
  *
- * @param items the backing list of data to display
- * @param modifier the modifier to apply to this layout
- * @param state the state object to be used to control or observe the list's state
+ * @param items the backing list of data to display.
+ * @param modifier the modifier to apply to this layout.
+ * @param state the state object to be used to control or observe the list's state.
  * @param contentPadding a padding around the whole content. This will add padding for the
- * content after it has been clipped, which is not possible via [modifier] param. Note that it is
- * **not** a padding applied for each item's content
- * @param verticalAlignment the vertical alignment applied to the items
+ * content after it has been clipped, which is not possible via [modifier] param. You can use it
+ * to add a padding before the first item or after the last one. If you want to add a spacing
+ * between each item use [horizontalArrangement].
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the end to the start and [LazyListState.firstVisibleItemIndex] == 0 will mean
+ * the first item is located at the end.
+ * @param horizontalArrangement The horizontal arrangement of the layout's children. This allows
+ * to add a spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
+ * @param verticalAlignment the vertical alignment applied to the items.
  * @param itemContent emits the UI for an item from [items] list. May emit any number of components,
  * which will be stacked horizontally. Note that [LazyRowFor] can start scrolling incorrectly
  * if you emit nothing and then lazily recompose with the real content, so even if you load the
  * content asynchronously please reserve some space for the item, for example using [Spacer].
  * Use [LazyRowForIndexed] if you need to have both index and item params.
  */
+@OptIn(InternalLayoutApi::class)
 @Composable
 fun <T> LazyRowFor(
     items: List<T>,
     modifier: Modifier = Modifier,
     state: LazyListState = rememberLazyListState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    horizontalArrangement: Arrangement.Horizontal =
+        if (!reverseLayout) Arrangement.Start else Arrangement.End,
     verticalAlignment: Alignment.Vertical = Alignment.Top,
     itemContent: @Composable LazyItemScope.(T) -> Unit
 ) {
@@ -141,6 +180,8 @@
         state = state,
         contentPadding = contentPadding,
         verticalAlignment = verticalAlignment,
+        horizontalArrangement = horizontalArrangement,
+        reverseLayout = reverseLayout
     ) {
         items(items, itemContent)
     }
@@ -155,13 +196,20 @@
  *
  * @sample androidx.compose.foundation.samples.LazyRowForIndexedSample
  *
- * @param items the backing list of data to display
- * @param modifier the modifier to apply to this layout
- * @param state the state object to be used to control or observe the list's state
+ * @param items the backing list of data to display.
+ * @param modifier the modifier to apply to this layout.
+ * @param state the state object to be used to control or observe the list's state.
  * @param contentPadding a padding around the whole content. This will add padding for the
- * content after it has been clipped, which is not possible via [modifier] param. Note that it is
- * **not** a padding applied for each item's content
- * @param verticalAlignment the vertical alignment applied to the items
+ * content after it has been clipped, which is not possible via [modifier] param. You can use it
+ * to add a padding before the first item or after the last one. If you want to add a spacing
+ * between each item use [horizontalArrangement].
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the end to the start and [LazyListState.firstVisibleItemIndex] == 0 will mean
+ * the first item is located at the end.
+ * @param horizontalArrangement The horizontal arrangement of the layout's children. This allows
+ * to add a spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
+ * @param verticalAlignment the vertical alignment applied to the items.
  * @param itemContent emits the UI for an item from [items] list. It has two params: first one is
  * an index in the [items] list, and the second one is the item at this index from [items] list.
  * May emit any number of components, which will be stacked horizontally. Note that
@@ -169,12 +217,16 @@
  * recompose with the real content, so even if you load the content asynchronously please reserve
  * some space for the item, for example using [Spacer].
  */
+@OptIn(InternalLayoutApi::class)
 @Composable
 fun <T> LazyRowForIndexed(
     items: List<T>,
     modifier: Modifier = Modifier,
     state: LazyListState = rememberLazyListState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    horizontalArrangement: Arrangement.Horizontal =
+        if (!reverseLayout) Arrangement.Start else Arrangement.End,
     verticalAlignment: Alignment.Vertical = Alignment.Top,
     itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
 ) {
@@ -183,6 +235,8 @@
         state = state,
         contentPadding = contentPadding,
         verticalAlignment = verticalAlignment,
+        horizontalArrangement = horizontalArrangement,
+        reverseLayout = reverseLayout
     ) {
         itemsIndexed(items, itemContent)
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
index a8f98a8..eb74118 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
@@ -16,10 +16,12 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.Constraints
@@ -52,7 +54,10 @@
         modifier = modifier,
         state = state,
         contentPadding = contentPadding,
-        isVertical = true
+        isVertical = true,
+        horizontalAlignment = Alignment.Start,
+        verticalArrangement = Arrangement.Top,
+        reverseLayout = false
     ) { rowIndex ->
         {
             GridRow {
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 b084605..9b1791a 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
@@ -16,11 +16,16 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.assertNotNestingScrollableContainers
 import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.InternalLayoutApi
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.savedinstancestate.ExperimentalRestorableStateHolder
+import androidx.compose.runtime.savedinstancestate.rememberRestorableStateHolder
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
@@ -29,21 +34,40 @@
 import androidx.compose.ui.platform.AmbientLayoutDirection
 import androidx.compose.ui.unit.LayoutDirection
 
+@OptIn(InternalLayoutApi::class)
 @Composable
 internal fun LazyList(
+    /** The total size of the list */
     itemsCount: Int,
-    modifier: Modifier = Modifier,
+    /** Modifier to be applied for the inner layout */
+    modifier: Modifier,
+    /** State controlling the scroll position */
     state: LazyListState,
+    /** The inner padding to be added for the whole content(nor for each individual item) */
     contentPadding: PaddingValues,
-    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
-    verticalAlignment: Alignment.Vertical = Alignment.Top,
+    /** reverse the direction of scrolling and layout */
+    reverseLayout: Boolean,
+    /** The layout orientation of the list */
     isVertical: Boolean,
-    itemContentFactory: LazyItemScope.(Int) -> @Composable () -> Unit
+    /** The alignment to align items horizontally. Required when isVertical is true */
+    horizontalAlignment: Alignment.Horizontal? = null,
+    /** The vertical arrangement for items. Required when isVertical is true */
+    verticalArrangement: Arrangement.Vertical? = null,
+    /** The alignment to align items vertically. Required when isVertical is false */
+    verticalAlignment: Alignment.Vertical? = null,
+    /** The horizontal arrangement for items. Required when isVertical is false */
+    horizontalArrangement: Arrangement.Horizontal? = null,
+    /** The factory defining the content for an item on the given position in the list */
+    itemContent: LazyItemScope.(Int) -> @Composable () -> Unit
 ) {
-    val reverseDirection = AmbientLayoutDirection.current == LayoutDirection.Rtl && !isVertical
+    val isRtl = AmbientLayoutDirection.current == LayoutDirection.Rtl
+    // reverse scroll by default, to have "natural" gesture that goes reversed to layout
+    // if rtl and horizontal, do not reverse to make it right-to-left
+    val reverseScrollDirection = if (!isVertical && isRtl) reverseLayout else !reverseLayout
 
-    val cachingItemContentFactory = remember { CachingItemContentFactory(itemContentFactory) }
-    cachingItemContentFactory.itemContentFactory = itemContentFactory
+    val restorableItemContent = wrapWithStateRestoration(itemContent)
+    val cachingItemContentFactory = remember { CachingItemContentFactory(restorableItemContent) }
+    cachingItemContentFactory.itemContentFactory = restorableItemContent
 
     val startContentPadding = if (isVertical) contentPadding.top else contentPadding.start
     val endContentPadding = if (isVertical) contentPadding.bottom else contentPadding.end
@@ -51,27 +75,89 @@
         modifier
             .scrollable(
                 orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
-                // reverse scroll by default, to have "natural" gesture that goes reversed to layout
-                reverseDirection = !reverseDirection,
+                reverseDirection = reverseScrollDirection,
                 controller = state.scrollableController
             )
             .clipToBounds()
             .padding(contentPadding)
             .then(state.remeasurementModifier)
     ) { constraints ->
+        constraints.assertNotNestingScrollableContainers(isVertical)
+
         // this will update the scope object if the constrains have been changed
         cachingItemContentFactory.updateItemScope(this, constraints)
 
-        state.measure(
-            this,
+        val startContentPaddingPx = startContentPadding.toIntPx()
+        val endContentPaddingPx = endContentPadding.toIntPx()
+        val mainAxisMaxSize = (if (isVertical) constraints.maxHeight else constraints.maxWidth)
+        val spaceBetweenItemsDp = if (isVertical) {
+            requireNotNull(verticalArrangement).spacing
+        } else {
+            requireNotNull(horizontalArrangement).spacing
+        }
+        val spaceBetweenItems = spaceBetweenItemsDp.toIntPx()
+
+        val itemProvider = LazyMeasuredItemProvider(
             constraints,
             isVertical,
-            horizontalAlignment,
-            verticalAlignment,
-            startContentPadding.toIntPx(),
-            endContentPadding.toIntPx(),
-            itemsCount,
+            this,
             cachingItemContentFactory
+        ) { index, placeables ->
+            // we add spaceBetweenItems as an extra size for all items apart from the last one so
+            // the lazy list measuring logic will take it into account.
+            val extraMainAxisSize = if (index.value == itemsCount - 1) 0 else spaceBetweenItems
+            LazyMeasuredItem(
+                placeables = placeables,
+                isVertical = isVertical,
+                horizontalAlignment = horizontalAlignment,
+                verticalAlignment = verticalAlignment,
+                layoutDirection = layoutDirection,
+                startContentPadding = startContentPaddingPx,
+                endContentPadding = endContentPaddingPx,
+                extraMainAxisSize = extraMainAxisSize
+            )
+        }
+
+        val measureResult = measureLazyList(
+            itemsCount,
+            itemProvider,
+            mainAxisMaxSize,
+            startContentPaddingPx,
+            endContentPaddingPx,
+            state.firstVisibleItemIndexNonObservable,
+            state.firstVisibleItemScrollOffsetNonObservable,
+            state.scrollToBeConsumed
         )
+
+        state.applyMeasureResult(measureResult)
+
+        layoutLazyList(
+            constraints,
+            isVertical,
+            verticalArrangement,
+            horizontalArrangement,
+            measureResult,
+            reverseLayout
+        )
+    }
+}
+
+/**
+ * Converts item content factory to another one which adds auto state restoration functionality.
+ */
+@OptIn(ExperimentalRestorableStateHolder::class)
+@Composable
+internal fun wrapWithStateRestoration(
+    itemContentFactory: LazyItemScope.(Int) -> @Composable () -> Unit
+): LazyItemScope.(Int) -> @Composable () -> Unit {
+    val restorableStateHolder = rememberRestorableStateHolder<Any>()
+    return remember(itemContentFactory) {
+        { index ->
+            val content = itemContentFactory(index)
+            // we just wrap our original lambda with the one which auto restores the state
+            // currently we use index in the list as a key for the restoration, but in the future
+            // we will use the user provided key
+            (@Composable { restorableStateHolder.RestorableStateProvider(index, content) })
+        }
     }
 }
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
new file mode 100644
index 0000000..88f8478
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.InternalLayoutApi
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.util.fastForEach
+import kotlin.math.abs
+import kotlin.math.roundToInt
+import kotlin.math.sign
+
+/**
+ * Measures and calculates the positions for the currently visible items. The result is produced
+ * as a [LazyListMeasureResult] which contains all the calculations.
+ */
+internal fun measureLazyList(
+    itemsCount: Int,
+    itemProvider: LazyMeasuredItemProvider,
+    mainAxisMaxSize: Int,
+    startContentPadding: Int,
+    endContentPadding: Int,
+    firstVisibleItemIndex: DataIndex,
+    firstVisibleItemScrollOffset: Int,
+    scrollToBeConsumed: Float
+): LazyListMeasureResult {
+    require(startContentPadding >= 0)
+    require(endContentPadding >= 0)
+    if (itemsCount <= 0) {
+        // empty data set. reset the current scroll and report zero size
+        return LazyListMeasureResult(
+            mainAxisSize = 0,
+            crossAxisSize = 0,
+            items = emptyList(),
+            itemsScrollOffset = 0,
+            firstVisibleItemIndex = DataIndex(0),
+            firstVisibleItemScrollOffset = 0,
+            canScrollForward = false,
+            consumedScroll = 0f
+        )
+    } else {
+        var currentFirstItemIndex = firstVisibleItemIndex
+        var currentFirstItemScrollOffset = firstVisibleItemScrollOffset
+        if (currentFirstItemIndex.value >= itemsCount) {
+            // the data set has been updated and now we have less items that we were
+            // scrolled to before
+            currentFirstItemIndex = DataIndex(itemsCount - 1)
+            currentFirstItemScrollOffset = 0
+        }
+
+        // represents the real amount of scroll we applied as a result of this measure pass.
+        var scrollDelta = scrollToBeConsumed.roundToInt()
+
+        // applying the whole requested scroll offset. we will figure out if we can't consume
+        // all of it later
+        currentFirstItemScrollOffset -= scrollDelta
+
+        // if the current scroll offset is less than minimally possible
+        if (currentFirstItemIndex == DataIndex(0) && currentFirstItemScrollOffset < 0) {
+            scrollDelta += currentFirstItemScrollOffset
+            currentFirstItemScrollOffset = 0
+        }
+
+        // saving it into the field as we first go backward and after that want to go forward
+        // again from the initial position
+        val goingForwardInitialIndex = currentFirstItemIndex
+        var goingForwardInitialScrollOffset = currentFirstItemScrollOffset
+
+        // this will contain all the MeasuredItems representing the visible items
+        val visibleItems = mutableListOf<LazyMeasuredItem>()
+
+        // include the start padding so we compose items in the padding area. in the end we
+        // will remove it back from the currentFirstItemScrollOffset calculation
+        currentFirstItemScrollOffset -= startContentPadding
+
+        // define min and max offsets (min offset currently includes startPadding)
+        val minOffset = -startContentPadding
+        val maxOffset = mainAxisMaxSize
+
+        // max of cross axis sizes of all visible items
+        var maxCrossAxis = 0
+
+        // we had scrolled backward or we compose items in the start padding area, which means
+        // items before current firstItemScrollOffset should be visible. compose them and update
+        // firstItemScrollOffset
+        while (currentFirstItemScrollOffset < 0 && currentFirstItemIndex > DataIndex(0)) {
+            val previous = DataIndex(currentFirstItemIndex.value - 1)
+            val measuredItem = itemProvider.getAndMeasure(previous)
+            visibleItems.add(0, measuredItem)
+            maxCrossAxis = maxOf(maxCrossAxis, measuredItem.crossAxisSize)
+            currentFirstItemScrollOffset += measuredItem.mainAxisSize
+            currentFirstItemIndex = previous
+        }
+        // if we were scrolled backward, but there were not enough items before. this means
+        // not the whole scroll was consumed
+        if (currentFirstItemScrollOffset < minOffset) {
+            scrollDelta += currentFirstItemScrollOffset
+            goingForwardInitialScrollOffset += currentFirstItemScrollOffset
+            currentFirstItemScrollOffset = minOffset
+        }
+
+        // remembers the composed MeasuredItem which we are not currently placing as they are out
+        // of screen. it is possible we will need to place them if the remaining items will
+        // not fill the whole viewport and we will need to scroll back
+        var notUsedButComposedItems: MutableList<LazyMeasuredItem>? = null
+
+        // composing visible items starting from goingForwardInitialIndex until we fill the
+        // whole viewport
+        var index = goingForwardInitialIndex
+        val maxMainAxis = maxOffset + endContentPadding
+        var mainAxisUsed = -goingForwardInitialScrollOffset
+        while (mainAxisUsed <= maxMainAxis && index.value < itemsCount) {
+            val measuredItem = itemProvider.getAndMeasure(index)
+            mainAxisUsed += measuredItem.mainAxisSize
+
+            if (mainAxisUsed < minOffset) {
+                // this item is offscreen and will not be placed. advance firstVisibleItemIndex
+                currentFirstItemIndex = index + 1
+                currentFirstItemScrollOffset -= measuredItem.mainAxisSize
+                // but remember the corresponding placeables in case we will be forced to
+                // scroll back as there were not enough items to fill the viewport
+                if (notUsedButComposedItems == null) {
+                    notUsedButComposedItems = mutableListOf()
+                }
+                notUsedButComposedItems.add(measuredItem)
+            } else {
+                maxCrossAxis = maxOf(maxCrossAxis, measuredItem.crossAxisSize)
+                visibleItems.add(measuredItem)
+            }
+
+            index++
+        }
+
+        // we didn't fill the whole viewport with items starting from firstVisibleItemIndex.
+        // lets try to scroll back if we have enough items before firstVisibleItemIndex.
+        if (mainAxisUsed < maxOffset) {
+            val toScrollBack = maxOffset - mainAxisUsed
+            currentFirstItemScrollOffset -= toScrollBack
+            mainAxisUsed += toScrollBack
+            while (currentFirstItemScrollOffset < 0 && currentFirstItemIndex > DataIndex(0)) {
+                val previous = DataIndex(currentFirstItemIndex.value - 1)
+                val alreadyComposedIndex = notUsedButComposedItems?.lastIndex ?: -1
+                val measuredItem = if (alreadyComposedIndex >= 0) {
+                    notUsedButComposedItems!!.removeAt(alreadyComposedIndex)
+                } else {
+                    itemProvider.getAndMeasure(previous)
+                }
+                visibleItems.add(0, measuredItem)
+                maxCrossAxis = maxOf(maxCrossAxis, measuredItem.crossAxisSize)
+                currentFirstItemScrollOffset += measuredItem.mainAxisSize
+                currentFirstItemIndex = previous
+            }
+            scrollDelta += toScrollBack
+            if (currentFirstItemScrollOffset < minOffset) {
+                scrollDelta += currentFirstItemScrollOffset
+                mainAxisUsed += currentFirstItemScrollOffset
+                currentFirstItemScrollOffset = minOffset
+            }
+        }
+
+        // report the amount of pixels we consumed. scrollDelta can be smaller than
+        // scrollToBeConsumed if there were not enough items to fill the offered space or it
+        // can be larger if items were resized, or if, for example, we were previously
+        // displaying the item 15, but now we have only 10 items in total in the data set.
+        val consumedScroll = if (scrollToBeConsumed.roundToInt().sign == scrollDelta.sign &&
+            abs(scrollToBeConsumed.roundToInt()) >= abs(scrollDelta)
+        ) {
+            scrollDelta.toFloat()
+        } else {
+            scrollToBeConsumed
+        }
+
+        // the initial offset for items from visibleItems list
+        val firstItemOffset = -(currentFirstItemScrollOffset + startContentPadding)
+
+        // compensate the content padding we initially added in currentFirstItemScrollOffset.
+        // if the item is fully located in the start padding area we  need to use the next
+        // item as a value for currentFirstItemIndex
+        if (startContentPadding > 0) {
+            currentFirstItemScrollOffset += startContentPadding
+            var startPaddingItems = 0
+            while (startPaddingItems < visibleItems.lastIndex) {
+                val size = visibleItems[startPaddingItems].mainAxisSize
+                if (size <= currentFirstItemScrollOffset) {
+                    startPaddingItems++
+                    currentFirstItemScrollOffset -= size
+                    currentFirstItemIndex++
+                } else {
+                    break
+                }
+            }
+        }
+
+        return LazyListMeasureResult(
+            mainAxisSize = mainAxisUsed + startContentPadding,
+            crossAxisSize = maxCrossAxis,
+            items = visibleItems,
+            itemsScrollOffset = firstItemOffset,
+            firstVisibleItemIndex = currentFirstItemIndex,
+            firstVisibleItemScrollOffset = currentFirstItemScrollOffset,
+            canScrollForward = mainAxisUsed > maxOffset,
+            consumedScroll = consumedScroll
+        )
+    }
+}
+
+/**
+ * Lays out [LazyMeasuredItem]s based on the [LazyListMeasureResult] and the passed arrangement.
+ */
+@OptIn(InternalLayoutApi::class)
+internal fun MeasureScope.layoutLazyList(
+    constraints: Constraints,
+    isVertical: Boolean,
+    verticalArrangement: Arrangement.Vertical?,
+    horizontalArrangement: Arrangement.Horizontal?,
+    measureResult: LazyListMeasureResult,
+    reverseLayout: Boolean
+): MeasureResult {
+    val layoutWidth = constraints.constrainWidth(
+        if (isVertical) measureResult.crossAxisSize else measureResult.mainAxisSize
+    )
+    val layoutHeight = constraints.constrainHeight(
+        if (isVertical) measureResult.mainAxisSize else measureResult.crossAxisSize
+    )
+    val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
+    val hasSpareSpace = measureResult.mainAxisSize < mainAxisLayoutSize
+    if (hasSpareSpace) {
+        check(measureResult.itemsScrollOffset == 0)
+    }
+    val density = this
+
+    return layout(layoutWidth, layoutHeight) {
+        var currentMainAxis = measureResult.itemsScrollOffset
+        if (hasSpareSpace) {
+            val items = if (reverseLayout) measureResult.items.reversed() else measureResult.items
+            val sizes = IntArray(items.size) { index ->
+                items[index].let { it.mainAxisSize - it.extraMainAxisSize }
+            }
+            val positions = IntArray(items.size) { 0 }
+            if (isVertical) {
+                requireNotNull(verticalArrangement)
+                    .arrange(mainAxisLayoutSize, sizes, density, positions)
+            } else {
+                requireNotNull(horizontalArrangement)
+                    .arrange(mainAxisLayoutSize, sizes, layoutDirection, density, positions)
+            }
+            positions.forEachIndexed { index, position ->
+                items[index].place(this, layoutWidth, layoutHeight, position, reverseLayout)
+            }
+        } else {
+            measureResult.items.fastForEach {
+                val offset = if (reverseLayout) {
+                    mainAxisLayoutSize - currentMainAxis - (it.mainAxisSize - it.extraMainAxisSize)
+                } else {
+                    currentMainAxis
+                }
+                it.place(this, layoutWidth, layoutHeight, offset, reverseLayout)
+                currentMainAxis += it.mainAxisSize
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
new file mode 100644
index 0000000..f3a3fcd
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+/**
+ * The result of the measure pass for lazy list layout.
+ */
+internal class LazyListMeasureResult(
+    /** Calculated size for the main axis.*/
+    val mainAxisSize: Int,
+    /** Calculated size for the cross axis.*/
+    val crossAxisSize: Int,
+    /** The list of items to be placed during the layout pass.*/
+    val items: List<LazyMeasuredItem>,
+    /** The main axis offset to be used for the first item in the [items] list.*/
+    val itemsScrollOffset: Int,
+    /** The new value for [LazyListState.firstVisibleItemIndex].*/
+    val firstVisibleItemIndex: DataIndex,
+    /** The new value for [LazyListState.firstVisibleItemScrollOffset].*/
+    val firstVisibleItemScrollOffset: Int,
+    /** True if there is some space available to continue scrolling in the forward direction.*/
+    val canScrollForward: Boolean,
+    /** The amount of scroll consumed during the measure pass.*/
+    val consumedScroll: Float
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 456a124..52250e1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -25,7 +25,6 @@
 import androidx.compose.foundation.InteractionState
 import androidx.compose.foundation.animation.FlingConfig
 import androidx.compose.foundation.animation.defaultFlingConfig
-import androidx.compose.foundation.assertNotNestingScrollableContainers
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.ScrollableController
 import androidx.compose.runtime.Composable
@@ -35,34 +34,12 @@
 import androidx.compose.runtime.savedinstancestate.Saver
 import androidx.compose.runtime.savedinstancestate.listSaver
 import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.Remeasurement
 import androidx.compose.ui.layout.RemeasurementModifier
-import androidx.compose.ui.layout.SubcomposeMeasureScope
 import androidx.compose.ui.platform.AmbientAnimationClock
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.constrainHeight
-import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.annotation.IntRange
 import androidx.compose.ui.util.annotation.VisibleForTesting
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
-import androidx.compose.ui.util.fastSumBy
 import kotlin.math.abs
-import kotlin.math.roundToInt
-import kotlin.math.sign
-
-@Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_FEATURE_WARNING")
-internal inline class DataIndex(val value: Int) {
-    inline operator fun inc(): DataIndex = DataIndex(value + 1)
-    inline operator fun dec(): DataIndex = DataIndex(value - 1)
-    inline operator fun plus(i: Int): DataIndex = DataIndex(value + i)
-    inline operator fun minus(i: Int): DataIndex = DataIndex(value - i)
-    inline operator fun minus(i: DataIndex): DataIndex = DataIndex(value - i.value)
-    inline operator fun compareTo(other: DataIndex): Int = value - other.value
-}
 
 /**
  * Creates a [LazyListState] that is remembered across compositions.
@@ -154,7 +131,18 @@
      * The amount of scroll to be consumed in the next layout pass.  Scrolling forward is negative
      * - that is, it is the amount that the items are offset in y
      */
-    private var scrollToBeConsumed = 0f
+    internal var scrollToBeConsumed = 0f
+        private set
+
+    /**
+     * The same as [firstVisibleItemIndex] but the read will not trigger remeasure.
+     */
+    internal val firstVisibleItemIndexNonObservable: DataIndex get() = scrollPosition.index
+
+    /**
+     * The same as [firstVisibleItemScrollOffset] but the read will not trigger remeasure.
+     */
+    internal val firstVisibleItemScrollOffsetNonObservable: Int get() = scrollPosition.scrollOffset
 
     /**
      * The ScrollableController instance. We keep it as we need to call stopAnimation on it once
@@ -179,6 +167,7 @@
      */
     @VisibleForTesting
     internal var numMeasurePasses: Int = 0
+        private set
 
     /**
      * The modifier which provides [remeasurement].
@@ -284,245 +273,18 @@
     }
 
     /**
-     * Measures and positions currently visible items using [itemContentFactory] for subcomposing.
+     *  Updates the state with the new calculated scroll position and consumed scroll.
      */
-    internal fun measure(
-        scope: SubcomposeMeasureScope,
-        constraints: Constraints,
-        isVertical: Boolean,
-        horizontalAlignment: Alignment.Horizontal,
-        verticalAlignment: Alignment.Vertical,
-        startContentPadding: Int,
-        endContentPadding: Int,
-        itemsCount: Int,
-        itemContentFactory: (Int) -> @Composable () -> Unit
-    ): MeasureResult = with(scope) {
+    internal fun applyMeasureResult(measureResult: LazyListMeasureResult) {
+        scrollPosition.update(
+            index = measureResult.firstVisibleItemIndex,
+            scrollOffset = measureResult.firstVisibleItemScrollOffset,
+            canScrollForward = measureResult.canScrollForward
+        )
+        scrollToBeConsumed -= measureResult.consumedScroll
         numMeasurePasses++
-        constraints.assertNotNestingScrollableContainers(isVertical)
-        require(startContentPadding >= 0)
-        require(endContentPadding >= 0)
-        if (itemsCount <= 0) {
-            // empty data set. reset the current scroll and report zero size
-            scrollPosition.update(
-                index = DataIndex(0),
-                scrollOffset = 0,
-                canScrollForward = false
-            )
-            layout(constraints.constrainWidth(0), constraints.constrainHeight(0)) {}
-        } else {
-            var currentFirstItemIndex = scrollPosition.index
-            var currentFirstItemScrollOffset = scrollPosition.scrollOffset
-
-            if (currentFirstItemIndex.value >= itemsCount) {
-                // the data set has been updated and now we have less items that we were
-                // scrolled to before
-                currentFirstItemIndex = DataIndex(itemsCount - 1)
-                currentFirstItemScrollOffset = 0
-            }
-
-            // represents the real amount of scroll we applied as a result of this measure pass.
-            var scrollDelta = scrollToBeConsumed.roundToInt()
-
-            // applying the whole requested scroll offset. we will figure out if we can't consume
-            // all of it later
-            currentFirstItemScrollOffset -= scrollDelta
-
-            // if the current scroll offset is less than minimally possible
-            if (currentFirstItemIndex == DataIndex(0) && currentFirstItemScrollOffset < 0) {
-                scrollDelta += currentFirstItemScrollOffset
-                currentFirstItemScrollOffset = 0
-            }
-
-            // the constraints we will measure child with. the cross axis are not restricted
-            val childConstraints = Constraints(
-                maxWidth = if (isVertical) constraints.maxWidth else Constraints.Infinity,
-                maxHeight = if (!isVertical) constraints.maxHeight else Constraints.Infinity
-            )
-            // saving it into the field as we first go backward and after that want to go forward
-            // again from the initial position
-            val goingForwardInitialIndex = currentFirstItemIndex
-            var goingForwardInitialScrollOffset = currentFirstItemScrollOffset
-
-            // this will contain all the placeables representing the visible items
-            val visibleItemsPlaceables = mutableListOf<List<Placeable>>()
-
-            // include the start padding so we compose items in the padding area. in the end we
-            // will remove it back from the currentFirstItemScrollOffset calculation
-            currentFirstItemScrollOffset -= startContentPadding
-
-            // define min and max offsets (min offset currently includes startPadding)
-            val minOffset = -startContentPadding
-            val maxOffset = (if (isVertical) constraints.maxHeight else constraints.maxWidth)
-
-            // we had scrolled backward or we compose items in the start padding area, which means
-            // items before current firstItemScrollOffset should be visible. compose them and update
-            // firstItemScrollOffset
-            while (currentFirstItemScrollOffset < 0 && currentFirstItemIndex > DataIndex(0)) {
-                val previous = DataIndex(currentFirstItemIndex.value - 1)
-                val placeables =
-                    subcompose(previous, itemContentFactory(previous.value)).fastMap {
-                        it.measure(childConstraints)
-                    }
-                visibleItemsPlaceables.add(0, placeables)
-                currentFirstItemScrollOffset += placeables.mainAxisSize(isVertical)
-                currentFirstItemIndex = previous
-            }
-            // if we were scrolled backward, but there were not enough items before. this means
-            // not the whole scroll was consumed
-            if (currentFirstItemScrollOffset < minOffset) {
-                scrollDelta += currentFirstItemScrollOffset
-                goingForwardInitialScrollOffset += currentFirstItemScrollOffset
-                currentFirstItemScrollOffset = minOffset
-            }
-
-            // remembers the composed placeables which we are not currently placing as they are out
-            // of screen. it is possible we will need to place them if the remaining items will
-            // not fill the whole viewport and we will need to scroll back
-            var notUsedButComposedItems: MutableList<List<Placeable>>? = null
-
-            // composing visible items starting from goingForwardInitialIndex until we fill the
-            // whole viewport
-            var index = goingForwardInitialIndex
-            val maxMainAxis = maxOffset + endContentPadding
-            var mainAxisUsed = -goingForwardInitialScrollOffset
-            var maxCrossAxis = 0
-            while (mainAxisUsed <= maxMainAxis && index.value < itemsCount) {
-                val placeables =
-                    subcompose(index, itemContentFactory(index.value)).fastMap {
-                        it.measure(childConstraints)
-                    }
-                var size = 0
-                placeables.fastForEach {
-                    size += if (isVertical) it.height else it.width
-                    maxCrossAxis = maxOf(maxCrossAxis, if (!isVertical) it.height else it.width)
-                }
-                mainAxisUsed += size
-
-                if (mainAxisUsed < minOffset) {
-                    // this item is offscreen and will not be placed. advance firstVisibleItemIndex
-                    currentFirstItemIndex = index + 1
-                    currentFirstItemScrollOffset -= size
-                    // but remember the corresponding placeables in case we will be forced to
-                    // scroll back as there were not enough items to fill the viewport
-                    if (notUsedButComposedItems == null) {
-                        notUsedButComposedItems = mutableListOf()
-                    }
-                    notUsedButComposedItems.add(placeables)
-                } else {
-                    visibleItemsPlaceables.add(placeables)
-                }
-
-                index++
-            }
-
-            // we didn't fill the whole viewport with items starting from firstVisibleItemIndex.
-            // lets try to scroll back if we have enough items before firstVisibleItemIndex.
-            if (mainAxisUsed < maxOffset) {
-                val toScrollBack = maxOffset - mainAxisUsed
-                currentFirstItemScrollOffset -= toScrollBack
-                mainAxisUsed += toScrollBack
-                while (currentFirstItemScrollOffset < 0 && currentFirstItemIndex > DataIndex(0)) {
-                    val previous = DataIndex(currentFirstItemIndex.value - 1)
-                    val alreadyComposedIndex = notUsedButComposedItems?.lastIndex ?: -1
-                    val placeables = if (alreadyComposedIndex >= 0) {
-                        notUsedButComposedItems!!.removeAt(alreadyComposedIndex)
-                    } else {
-                        subcompose(previous, itemContentFactory(previous.value)).fastMap {
-                            it.measure(childConstraints)
-                        }
-                    }
-                    visibleItemsPlaceables.add(0, placeables)
-                    val size = placeables.mainAxisSize(isVertical)
-                    currentFirstItemScrollOffset += size
-                    currentFirstItemIndex = previous
-                }
-                scrollDelta += toScrollBack
-                if (currentFirstItemScrollOffset < minOffset) {
-                    scrollDelta += currentFirstItemScrollOffset
-                    mainAxisUsed += currentFirstItemScrollOffset
-                    currentFirstItemScrollOffset = minOffset
-                }
-            }
-
-            // report the amount of pixels we consumed. scrollDelta can be smaller than
-            // scrollToBeConsumed if there were not enough items to fill the offered space or it
-            // can be larger if items were resized, or if, for example, we were previously
-            // displaying the item 15, but now we have only 10 items in total in the data set.
-            if (scrollToBeConsumed.roundToInt().sign == scrollDelta.sign &&
-                abs(scrollToBeConsumed.roundToInt()) >= abs(scrollDelta)
-            ) {
-                scrollToBeConsumed -= scrollDelta
-            } else {
-                scrollToBeConsumed = 0f
-            }
-
-            // Wrap the content of the children
-            val layoutWidth = constraints.constrainWidth(
-                if (isVertical) maxCrossAxis else mainAxisUsed + startContentPadding
-            )
-            val layoutHeight = constraints.constrainHeight(
-                if (!isVertical) maxCrossAxis else mainAxisUsed + startContentPadding
-            )
-
-            // the initial offset for placeables in visibleItemsPlaceables
-            val firstPlaceableOffset = -(currentFirstItemScrollOffset + startContentPadding)
-
-            // compensate the content padding we initially added in currentFirstItemScrollOffset.
-            // if the item is fully located in the start padding area we  need to use the next
-            // item as a value for currentFirstItemIndex
-            if (startContentPadding > 0) {
-                currentFirstItemScrollOffset += startContentPadding
-                var startPaddingItems = 0
-                while (startPaddingItems < visibleItemsPlaceables.lastIndex) {
-                    val size = visibleItemsPlaceables[startPaddingItems].mainAxisSize(isVertical)
-                    if (size <= currentFirstItemScrollOffset) {
-                        startPaddingItems++
-                        currentFirstItemScrollOffset -= size
-                        currentFirstItemIndex++
-                    } else {
-                        break
-                    }
-                }
-            }
-
-            // update state with the new calculated scroll position
-            scrollPosition.update(
-                index = currentFirstItemIndex,
-                scrollOffset = currentFirstItemScrollOffset,
-                canScrollForward = mainAxisUsed > maxOffset
-            )
-
-            return layout(layoutWidth, layoutHeight) {
-                var currentMainAxis = firstPlaceableOffset
-                visibleItemsPlaceables.fastForEach { placeables ->
-                    placeables.fastForEach {
-                        if (isVertical) {
-                            val x =
-                                horizontalAlignment.align(it.width, layoutWidth, layoutDirection)
-                            if (currentMainAxis + it.height > minOffset &&
-                                currentMainAxis < layoutHeight + endContentPadding
-                            ) {
-                                it.placeWithLayer(x, currentMainAxis)
-                            }
-                            currentMainAxis += it.height
-                        } else {
-                            val y = verticalAlignment.align(it.height, layoutHeight)
-                            if (currentMainAxis + it.width > minOffset &&
-                                currentMainAxis < layoutWidth + endContentPadding
-                            ) {
-                                it.placeRelativeWithLayer(currentMainAxis, y)
-                            }
-                            currentMainAxis += it.width
-                        }
-                    }
-                }
-            }
-        }
     }
 
-    private fun List<Placeable>.mainAxisSize(isVertical: Boolean) =
-        fastSumBy { if (isVertical) it.height else it.width }
-
     companion object {
         /**
          * The default [Saver] implementation for [LazyListState].
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
new file mode 100644
index 0000000..d5add54
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastForEach
+
+/**
+ * Represents one measured item of the lazy list. It can in fact consist of multiple placeables
+ * if the user emit multiple layout nodes in the item callback.
+ */
+internal class LazyMeasuredItem(
+    private val placeables: List<Placeable>,
+    private val isVertical: Boolean,
+    private val horizontalAlignment: Alignment.Horizontal?,
+    private val verticalAlignment: Alignment.Vertical?,
+    private val layoutDirection: LayoutDirection,
+    private val startContentPadding: Int,
+    private val endContentPadding: Int,
+    /**
+     * Extra size to be added to [mainAxisSize] aside from the sum of the [placeables] size.
+     */
+    val extraMainAxisSize: Int
+) {
+    /**
+     * Sum of the main axis sizes of all the inner placeables.
+     */
+    val mainAxisSize: Int
+
+    /**
+     * Max of the cross axis sizes of all the inner placeables.
+     */
+    val crossAxisSize: Int
+
+    init {
+        var mainAxisSize = extraMainAxisSize
+        var maxCrossAxis = 0
+        placeables.fastForEach {
+            mainAxisSize += if (isVertical) it.height else it.width
+            maxCrossAxis = maxOf(maxCrossAxis, if (!isVertical) it.height else it.width)
+        }
+        this.mainAxisSize = mainAxisSize
+        this.crossAxisSize = maxCrossAxis
+    }
+
+    /**
+     * Perform placing for all the inner placeables at [offset] main axis position. [layoutWidth]
+     * and [layoutHeight] should be provided to not place placeables which are ended up outside of
+     * the viewport (for example one item consist of 2 placeables, and the first one is not going
+     * to be visible, so we don't place it as an optimization, but place the second one).
+     * If [reverseOrder] is true the inner placeables would be placed in the inverted order.
+     */
+    fun place(
+        scope: Placeable.PlacementScope,
+        layoutWidth: Int,
+        layoutHeight: Int,
+        offset: Int,
+        reverseOrder: Boolean
+    ) = with(scope) {
+        var mainAxisOffset = offset
+        val indices = if (reverseOrder) placeables.lastIndex downTo 0 else placeables.indices
+        for (index in indices) {
+            val it = placeables[index]
+            if (isVertical) {
+                val x = requireNotNull(horizontalAlignment)
+                    .align(it.width, layoutWidth, layoutDirection)
+                if (mainAxisOffset + it.height > -startContentPadding &&
+                    mainAxisOffset < layoutHeight + endContentPadding
+                ) {
+                    it.placeWithLayer(x, mainAxisOffset)
+                }
+                mainAxisOffset += it.height
+            } else {
+                val y = requireNotNull(verticalAlignment).align(it.height, layoutHeight)
+                if (mainAxisOffset + it.width > -startContentPadding &&
+                    mainAxisOffset < layoutWidth + endContentPadding
+                ) {
+                    it.placeRelativeWithLayer(mainAxisOffset, y)
+                }
+                mainAxisOffset += it.width
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
new file mode 100644
index 0000000..89ae7d7
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.SubcomposeMeasureScope
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.util.fastMap
+
+/**
+ * Abstracts away the subcomposition from the measuring logic.
+ */
+internal class LazyMeasuredItemProvider(
+    constraints: Constraints,
+    isVertical: Boolean,
+    private val scope: SubcomposeMeasureScope,
+    private val itemContentFactory: (Int) -> @Composable () -> Unit,
+    private val measuredItemFactory: (DataIndex, List<Placeable>) -> LazyMeasuredItem
+) {
+    // the constraints we will measure child with. the main axis is not restricted
+    private val childConstraints = Constraints(
+        maxWidth = if (isVertical) constraints.maxWidth else Constraints.Infinity,
+        maxHeight = if (!isVertical) constraints.maxHeight else Constraints.Infinity
+    )
+
+    /**
+     * Used to subcompose items of lazy lists. Composed placeables will be measured with the
+     * correct constraints and wrapped into [LazyMeasuredItem].
+     * This method can be called only once with each [index] per the measure pass.
+     */
+    fun getAndMeasure(index: DataIndex): LazyMeasuredItem {
+        val placeables = scope.subcompose(index, itemContentFactory(index.value)).fastMap {
+            it.measure(childConstraints)
+        }
+        return measuredItemFactory(index, placeables)
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
index 57b9d1b..cec507f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.Interaction
+import androidx.compose.foundation.InteractionState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -92,6 +94,10 @@
  * communicating with platform text input service, e.g. software keyboard on Android. Called with
  * [SoftwareKeyboardController] instance which can be used for requesting input show/hide software
  * keyboard.
+ * @param interactionState the [InteractionState] representing the different [Interaction]s
+ * present on this TextField. You can create and pass in your own remembered [InteractionState]
+ * if you want to read the [InteractionState] and customize the appearance / behavior of this
+ * TextField in different [Interaction]s.
  * @param cursorColor Color of the cursor. If [Color.Unspecified], there will be no cursor drawn
  */
 @OptIn(ExperimentalTextApi::class)
@@ -108,6 +114,7 @@
     visualTransformation: VisualTransformation = VisualTransformation.None,
     onTextLayout: (TextLayoutResult) -> Unit = {},
     onTextInputStarted: (SoftwareKeyboardController) -> Unit = {},
+    interactionState: InteractionState = remember { InteractionState() },
     cursorColor: Color = Color.Black
 ) {
     var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) }
@@ -130,6 +137,7 @@
         onTextLayout = onTextLayout,
         onTextInputStarted = onTextInputStarted,
         cursorColor = cursorColor,
+        interactionState = interactionState,
         singleLine = singleLine
     )
 }
@@ -188,6 +196,10 @@
  * communicating with platform text input service, e.g. software keyboard on Android. Called with
  * [SoftwareKeyboardController] instance which can be used for requesting input show/hide software
  * keyboard.
+ * @param interactionState The [InteractionState] representing the different [Interaction]s
+ * present on this TextField. You can create and pass in your own remembered [InteractionState]
+ * if you want to read the [InteractionState] and customize the appearance / behavior of this
+ * TextField in different [Interaction]s.
  * @param cursorColor Color of the cursor. If [Color.Unspecified], there will be no cursor drawn
  */
 @Composable
@@ -204,6 +216,7 @@
     visualTransformation: VisualTransformation = VisualTransformation.None,
     onTextLayout: (TextLayoutResult) -> Unit = {},
     onTextInputStarted: (SoftwareKeyboardController) -> Unit = {},
+    interactionState: InteractionState = remember { InteractionState() },
     cursorColor: Color = Color.Black
 ) {
     // We use it to get the cursor position
@@ -224,6 +237,7 @@
             textLayoutResult.value = it
             onTextLayout(it)
         },
+        interactionState = interactionState,
         onTextInputStarted = onTextInputStarted,
         cursorColor = cursorColor,
         imeOptions = keyboardOptions.toImeOptions(singleLine = singleLine),
@@ -235,6 +249,7 @@
                 scrollerPosition,
                 value,
                 visualTransformation,
+                interactionState,
                 textLayoutResult
             )
     )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index a39fc33..8638678 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -52,6 +52,7 @@
 import androidx.compose.ui.semantics.getTextLayoutResult
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.InternalTextApi
 import androidx.compose.ui.text.Placeholder
 import androidx.compose.ui.text.TextDelegate
@@ -96,6 +97,7 @@
  */
 @Composable
 @InternalTextApi
+@OptIn(ExperimentalTextApi::class)
 fun CoreText(
     text: AnnotatedString,
     modifier: Modifier = Modifier,
@@ -194,7 +196,10 @@
     }
 }
 
-@OptIn(InternalTextApi::class)
+@OptIn(
+    InternalTextApi::class,
+    ExperimentalTextApi::class
+)
 private class TextController(val state: TextState) {
     var selectionRegistrar: SelectionRegistrar? = null
 
@@ -225,7 +230,7 @@
             if (state.selectionRange != null) {
                 val newGlobalPosition = it.globalPosition
                 if (newGlobalPosition != state.previousGlobalPosition) {
-                    selectionRegistrar.onPositionChange()
+                    selectionRegistrar.notifyPositionChange()
                 }
                 state.previousGlobalPosition = newGlobalPosition
             }
@@ -353,7 +358,7 @@
     var layoutCoordinates: LayoutCoordinates? = null
     /** The latest TextLayoutResult calculated in the measure block */
     var layoutResult: TextLayoutResult? = null
-    /** The global position calculated during the last onPositioned callback */
+    /** The global position calculated during the last notifyPosition callback */
     var previousGlobalPosition: Offset = Offset.Zero
     /** The paint used to draw highlight background for selected text. */
     val selectionPaint: Paint = Paint()
@@ -433,7 +438,10 @@
     return Pair(placeholders, inlineComposables)
 }
 
-@OptIn(InternalTextApi::class)
+@OptIn(
+    InternalTextApi::class,
+    ExperimentalTextApi::class
+)
 @VisibleForTesting
 internal fun longPressDragObserver(
     state: TextState,
@@ -455,10 +463,9 @@
             state.layoutCoordinates?.let {
                 if (!it.isAttached) return
 
-                selectionRegistrar?.onUpdateSelection(
+                selectionRegistrar?.notifySelectionUpdateStart(
                     layoutCoordinates = it,
-                    startPosition = pxPosition,
-                    endPosition = pxPosition
+                    startPosition = pxPosition
                 )
 
                 dragBeginPosition = pxPosition
@@ -466,7 +473,6 @@
         }
 
         override fun onDragStart() {
-            super.onDragStart()
             // selection never started
             if (state.selectionRange == null) return
             // Zero out the total distance that being dragged.
@@ -481,7 +487,7 @@
 
                 dragTotalDistance += dragDistance
 
-                selectionRegistrar?.onUpdateSelection(
+                selectionRegistrar?.notifySelectionUpdate(
                     layoutCoordinates = it,
                     startPosition = dragBeginPosition,
                     endPosition = dragBeginPosition + dragTotalDistance
@@ -489,5 +495,13 @@
             }
             return dragDistance
         }
+
+        override fun onStop(velocity: Offset) {
+            selectionRegistrar?.notifySelectionUpdateEnd()
+        }
+
+        override fun onCancel() {
+            selectionRegistrar?.notifySelectionUpdateEnd()
+        }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 37b6ebd..5ab2ff5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -17,6 +17,9 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.Interaction
+import androidx.compose.foundation.InteractionState
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.text.selection.TextFieldSelectionHandle
 import androidx.compose.foundation.text.selection.TextFieldSelectionManager
 import androidx.compose.runtime.Composable
@@ -29,7 +32,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.focus
 import androidx.compose.ui.focus.ExperimentalFocus
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.isFocused
@@ -58,7 +60,6 @@
 import androidx.compose.ui.selection.SimpleLayout
 import androidx.compose.ui.semantics.copyText
 import androidx.compose.ui.semantics.cutText
-import androidx.compose.ui.semantics.focused
 import androidx.compose.ui.semantics.getTextLayoutResult
 import androidx.compose.ui.semantics.imeAction
 import androidx.compose.ui.semantics.onClick
@@ -123,6 +124,10 @@
  * communicating with platform text input service, e.g. software keyboard on Android. Called with
  * [SoftwareKeyboardController] instance which can be used for requesting input show/hide software
  * keyboard.
+ * @param interactionState The [InteractionState] representing the different [Interaction]s
+ * present on this TextField. You can create and pass in your own remembered [InteractionState]
+ * if you want to read the [InteractionState] and customize the appearance / behavior of this
+ * TextField in different [Interaction]s.
  * @param cursorColor Color of the cursor. If [Color.Unspecified], there will be no cursor drawn
  * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
  * text will be positioned as if there was unlimited horizontal space.
@@ -143,6 +148,7 @@
     visualTransformation: VisualTransformation = VisualTransformation.None,
     onTextLayout: (TextLayoutResult) -> Unit = {},
     onTextInputStarted: (SoftwareKeyboardController) -> Unit = {},
+    interactionState: InteractionState? = null,
     cursorColor: Color = Color.Unspecified,
     softWrap: Boolean = true,
     imeOptions: ImeOptions = ImeOptions.Default
@@ -269,6 +275,7 @@
     }
 
     val dragPositionGestureModifier = Modifier.dragPositionGestureFilter(
+        interactionState = interactionState,
         onPress = {
             if (state.hasFocus) {
                 state.selectionIsOn = false
@@ -341,10 +348,10 @@
     }
 
     val semanticsModifier = Modifier.semantics {
+        // focused semantics are handled by Modifier.focusable()
         this.imeAction = imeOptions.imeAction
         this.text = AnnotatedString(value.text)
         this.textSelectionRange = value.selection
-        this.focused = state.hasFocus
         getTextLayoutResult {
             if (state.layoutResult != null) {
                 it.add(state.layoutResult!!)
@@ -419,7 +426,7 @@
         .then(semanticsModifier)
         .textFieldMinSize(textStyle)
         .textFieldKeyboardModifier(manager)
-        .focus()
+        .focusable(interactionState = interactionState)
 
     SimpleLayout(modifiers) {
         Layout(emptyContent()) { _, constraints ->
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
index 626a730..e22c3ab 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
@@ -18,11 +18,10 @@
 
 import androidx.compose.animation.core.AnimatedFloat
 import androidx.compose.animation.core.AnimationClockObservable
-import androidx.compose.animation.core.AnimationConstants
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.infiniteRepeatable
 import androidx.compose.animation.core.keyframes
-import androidx.compose.animation.core.repeatable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -103,8 +102,7 @@
 }
 
 private val cursorAnimationSpec: AnimationSpec<Float>
-    get() = repeatable(
-        iterations = AnimationConstants.Infinite,
+    get() = infiniteRepeatable(
         animation = keyframes {
             durationMillis = 1000
             1f at 0
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDragGestureFilter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDragGestureFilter.kt
index 057acb3..560431f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDragGestureFilter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDragGestureFilter.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.Interaction
+import androidx.compose.foundation.InteractionState
+import androidx.compose.runtime.onDispose
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
@@ -30,19 +33,28 @@
 @Suppress("ModifierInspectorInfo")
 internal fun Modifier.dragPositionGestureFilter(
     onPress: (Offset) -> Unit,
-    onRelease: (Offset) -> Unit
+    onRelease: (Offset) -> Unit,
+    interactionState: InteractionState?,
 ): Modifier = composed {
     val tracker = remember { DragEventTracker() }
     // TODO(shepshapard): PressIndicator doesn't seem to be the right thing to use here.  It
     //  actually may be functionally correct, but might mostly suggest that it should not
     //  actually be called PressIndicator, but instead something else.
+    onDispose {
+        interactionState?.removeInteraction(Interaction.Pressed)
+    }
     pressIndicatorGestureFilter(
         onStart = {
+            interactionState?.addInteraction(Interaction.Pressed, it)
             tracker.init(it)
             onPress(it)
         },
         onStop = {
+            interactionState?.removeInteraction(Interaction.Pressed)
             onRelease(tracker.getPosition())
+        },
+        onCancel = {
+            interactionState?.removeInteraction(Interaction.Pressed)
         }
     )
         .dragGestureFilter(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
index d51b0511..41b50c6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.InteractionState
 import androidx.compose.foundation.gestures.rememberScrollableController
 import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.runtime.Stable
@@ -53,17 +54,15 @@
     scrollerPosition: TextFieldScrollerPosition,
     textFieldValue: TextFieldValue,
     visualTransformation: VisualTransformation,
+    interactionState: InteractionState,
     textLayoutResult: Ref<TextLayoutResult?>
 ) = composed(
     factory = {
         // do not reverse direction only in case of RTL in horizontal orientation
         val rtl = AmbientLayoutDirection.current == LayoutDirection.Rtl
         val reverseDirection = orientation == Orientation.Vertical || !rtl
-        val scroll = Modifier.scrollable(
-            orientation = orientation,
-            canScroll = { scrollerPosition.maximum != 0f },
-            reverseDirection = reverseDirection,
-            controller = rememberScrollableController { delta ->
+        val controller =
+            rememberScrollableController(interactionState = interactionState) { delta ->
                 val newOffset = scrollerPosition.offset + delta
                 val consumedDelta = when {
                     newOffset > scrollerPosition.maximum ->
@@ -74,6 +73,11 @@
                 scrollerPosition.offset += consumedDelta
                 consumedDelta
             }
+        val scroll = Modifier.scrollable(
+            orientation = orientation,
+            canScroll = { scrollerPosition.maximum != 0f },
+            reverseDirection = reverseDirection,
+            controller = controller
         )
 
         val cursorOffset = scrollerPosition.getOffsetToFollow(textFieldValue.selection)
@@ -106,6 +110,7 @@
         properties["scrollerPosition"] = scrollerPosition
         properties["textFieldValue"] = textFieldValue
         properties["visualTransformation"] = visualTransformation
+        properties["interactionState"] = interactionState
         properties["textLayoutResult"] = textLayoutResult
     }
 )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
index eaa5cc2..f908eb0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
@@ -22,10 +22,12 @@
 import androidx.compose.ui.selection.Selectable
 import androidx.compose.ui.selection.Selection
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextRange
 import kotlin.math.max
 
+@OptIn(ExperimentalTextApi::class)
 internal class MultiWidgetSelectionDelegate(
     private val selectionRangeUpdate: (TextRange?) -> Unit,
     private val coordinatesCallback: () -> LayoutCoordinates?,
@@ -123,6 +125,7 @@
  *
  * @return [Selection] of the current composable, or null if the composable is not selected.
  */
+@OptIn(ExperimentalTextApi::class)
 internal fun getTextSelectionInfo(
     textLayoutResult: TextLayoutResult,
     selectionCoordinates: Pair<Offset, Offset>,
@@ -206,6 +209,7 @@
  *
  * @return [Selection] of the current composable, or null if the composable is not selected.
  */
+@OptIn(ExperimentalTextApi::class)
 private fun getRefinedSelectionInfo(
     rawStartOffset: Int,
     rawEndOffset: Int,
@@ -282,6 +286,7 @@
  *
  * @return an assembled object of [Selection] using the offered selection info.
  */
+@OptIn(ExperimentalTextApi::class)
 private fun getAssembledSelectionInfo(
     startOffset: Int,
     endOffset: Int,
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
index 087f17a..96e0375 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
@@ -42,7 +42,6 @@
 import androidx.compose.ui.input.pointer.pointerMoveFilter
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.MeasuringIntrinsicsMeasureBlocks
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
@@ -169,7 +168,6 @@
 
 // TODO(demin): do we need to stop dragging if cursor is beyond constraints?
 // TODO(demin): add Interaction.Hovered to interactionState
-@OptIn(ExperimentalLayoutNodeApi::class)
 @Composable
 private fun Scrollbar(
     adapter: ScrollbarAdapter,
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
index f1130b2..c9642e5 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.foundation.gestures
 
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.anyPositionChangeConsumed
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.consumeAllChanges
@@ -34,7 +31,6 @@
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
-@OptIn(ExperimentalPointerInput::class)
 class DragGestureDetectorTest(dragType: GestureType) {
     enum class GestureType {
         VerticalDrag,
@@ -57,6 +53,7 @@
     private var gestureEnded = false
     private var gestureCanceled = false
     private var consumePositiveOnly = false
+    private var sloppyDetector = false
 
     private val DragTouchSlopUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
         detectDragGestures(
@@ -109,8 +106,8 @@
                         change.consumePositionChange(0f, change.positionChange().y)
                     }
                 }
-                if (slopChange != null) {
-                    var pointer = slopChange.id
+                if (slopChange != null || sloppyDetector) {
+                    var pointer = if (sloppyDetector) down.id else slopChange!!.id
                     do {
                         val change = awaitVerticalDragOrCancellation(pointer)
                         if (change == null) {
@@ -141,8 +138,8 @@
                             change.consumePositionChange(change.positionChange().x, 0f)
                         }
                     }
-                if (slopChange != null) {
-                    var pointer = slopChange.id
+                if (slopChange != null || sloppyDetector) {
+                    var pointer = if (sloppyDetector) down.id else slopChange!!.id
                     do {
                         val change = awaitHorizontalDragOrCancellation(pointer)
                         if (change == null) {
@@ -173,8 +170,8 @@
                         change.consumeAllChanges()
                     }
                 }
-                if (slopChange != null) {
-                    var pointer = slopChange.id
+                if (slopChange != null || sloppyDetector) {
+                    var pointer = if (sloppyDetector) down.id else slopChange!!.id
                     do {
                         val change = awaitDragOrCancellation(pointer)
                         if (change == null) {
@@ -224,6 +221,13 @@
         else -> false
     }
 
+    private val supportsSloppyGesture = when (dragType) {
+        GestureType.AwaitVerticalDragOrCancel,
+        GestureType.AwaitHorizontalDragOrCancel,
+        GestureType.AwaitDragOrCancel -> true
+        else -> false
+    }
+
     @Before
     fun setup() {
         dragDistance = 0f
@@ -440,4 +444,27 @@
             consumePositiveOnly = false
         }
     }
+
+    /**
+     * When gesture detectors use the wrong pointer for the drag, it should just not
+     * detect the touch.
+     */
+    @Test
+    fun pointerUpTooQuickly() = util.executeInComposition {
+        if (supportsSloppyGesture) {
+            try {
+                sloppyDetector = true
+
+                val finger1 = down()
+                val finger2 = down()
+                finger1.up()
+                finger2.moveBy(dragMotion).up()
+
+                // The sloppy detector doesn't know to look at finger2
+                assertTrue(gestureCanceled)
+            } finally {
+                sloppyDetector = false
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetectorTest.kt
index de07073..aa3caaa 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetectorTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetectorTest.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.foundation.gestures
 
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.anyPositionChangeConsumed
 import androidx.compose.ui.input.pointer.consumeAllChanges
 import org.junit.Assert.assertEquals
@@ -31,13 +28,14 @@
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
-@OptIn(ExperimentalPointerInput::class)
 class MultitouchGestureDetectorTest(val panZoomLock: Boolean) {
     companion object {
         @JvmStatic
         @Parameterized.Parameters
         fun parameters() = arrayOf(false, true)
     }
+
+    private var centroid = Offset.Zero
     private var panned = false
     private var panAmount = Offset.Zero
     private var rotated = false
@@ -46,21 +44,21 @@
     private var zoomAmount = 1f
 
     private val util = SuspendingGestureTestUtil {
-        detectMultitouchGestures(
-            panZoomLock = panZoomLock,
-            onRotate = {
+        detectMultitouchGestures(panZoomLock = panZoomLock) { c, pan, gestureZoom, gestureAngle ->
+            centroid = c
+            if (gestureAngle != 0f) {
                 rotated = true
-                rotateAmount += it
-            },
-            onZoom = {
-                zoomed = true
-                zoomAmount *= it
-            },
-            onPan = {
-                panned = true
-                panAmount += it
+                rotateAmount += gestureAngle
             }
-        )
+            if (gestureZoom != 1f) {
+                zoomed = true
+                zoomAmount *= gestureZoom
+            }
+            if (pan != Offset.Zero) {
+                panned = true
+                panAmount += pan
+            }
+        }
     }
 
     @Before
@@ -91,6 +89,8 @@
         val move2 = move1.moveBy(Offset(0.1f, 0.1f))
         assertTrue(move2.anyPositionChangeConsumed())
 
+        assertEquals(17.7f, centroid.x, 0.1f)
+        assertEquals(17.7f, centroid.y, 0.1f)
         assertTrue(panned)
         assertFalse(zoomed)
         assertFalse(rotated)
@@ -129,6 +129,8 @@
         // Now we've averaged enough movement
         assertTrue(moveB1.anyPositionChangeConsumed())
 
+        assertEquals((5f + 25f + 12.8f) / 2f, centroid.x, 0.1f)
+        assertEquals((5f + 25f + 12.8f) / 2f, centroid.y, 0.1f)
         assertTrue(panned)
         assertTrue(zoomed)
         assertFalse(rotated)
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
index 1114272..1d8e90e 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
@@ -28,19 +28,7 @@
 import androidx.compose.runtime.dispatch.MonotonicFrameClock
 import androidx.compose.runtime.withRunningRecomposer
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.TransformOrigin
-import androidx.compose.ui.autofill.Autofill
-import androidx.compose.ui.autofill.AutofillTree
-import androidx.compose.ui.focus.ExperimentalFocus
-import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
-import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Matrix
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.hapticfeedback.HapticFeedback
-import androidx.compose.ui.input.key.ExperimentalKeyInput
-import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.pointer.ConsumedData
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
@@ -50,33 +38,15 @@
 import androidx.compose.ui.input.pointer.PointerInputFilter
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.materialize
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.InternalCoreApi
-import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.node.OwnedLayer
-import androidx.compose.ui.node.Owner
-import androidx.compose.ui.node.OwnerSnapshotObserver
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.platform.AmbientViewConfiguration
-import androidx.compose.ui.platform.ClipboardManager
-import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.ViewConfiguration
-import androidx.compose.ui.semantics.SemanticsOwner
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.input.TextInputService
-import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Duration
-import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.Uptime
 import androidx.compose.ui.unit.milliseconds
-import androidx.compose.ui.platform.WindowManager
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.test.runBlockingTest
@@ -88,7 +58,6 @@
  * [gestureDetector]. The [width] and [height] of the LayoutNode may
  * be provided.
  */
-@OptIn(ExperimentalPointerInput::class)
 internal class SuspendingGestureTestUtil(
     val width: Int = 10,
     val height: Int = 10,
@@ -137,7 +106,6 @@
                     pointerInputFilter = currentComposer
                         .materialize(Modifier.pointerInput(gestureDetector)) as
                         PointerInputFilter
-                    LayoutNode(0, 0, width, height, pointerInputFilter!! as Modifier)
                 }
             }
             yield()
@@ -348,24 +316,6 @@
         }
     }
 
-    @Suppress("SameParameterValue")
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    private fun LayoutNode(x: Int, y: Int, x2: Int, y2: Int, modifier: Modifier = Modifier) =
-        LayoutNode().apply {
-            this.modifier = modifier
-            measureBlocks = object : LayoutNode.NoIntrinsicsMeasureBlocks("not supported") {
-                override fun measure(
-                    measureScope: MeasureScope,
-                    measurables: List<Measurable>,
-                    constraints: Constraints
-                ): MeasureResult =
-                    measureScope.layout(x2 - x, y2 - y) {}
-            }
-            attach(MockOwner())
-            measure(Constraints.fixed(x2 - x, y2 - y))
-            place(x, y)
-        }
-
     internal class TestFrameClock : MonotonicFrameClock {
 
         private val frameCh = Channel<Long>()
@@ -379,136 +329,15 @@
             onFrame(frameCh.receive())
     }
 
-    @OptIn(
-        ExperimentalFocus::class,
-        ExperimentalLayoutNodeApi::class,
-        InternalCoreApi::class
-    )
-    private class MockOwner(
-        val position: IntOffset = IntOffset.Zero,
-        override val root: LayoutNode = LayoutNode()
-    ) : Owner {
-        val onRequestMeasureParams = mutableListOf<LayoutNode>()
-        val onAttachParams = mutableListOf<LayoutNode>()
-        val onDetachParams = mutableListOf<LayoutNode>()
-
-        override val hapticFeedBack: HapticFeedback
-            get() = TODO("Not yet implemented")
-        override val clipboardManager: ClipboardManager
-            get() = TODO("Not yet implemented")
-        override val textToolbar: TextToolbar
-            get() = TODO("Not yet implemented")
-        override val autofillTree: AutofillTree
-            get() = TODO("Not yet implemented")
-        override val autofill: Autofill?
-            get() = TODO("Not yet implemented")
-        override val density: Density
-            get() = Density(1f)
-        override val semanticsOwner: SemanticsOwner
-            get() = TODO("Not yet implemented")
-        override val textInputService: TextInputService
-            get() = TODO("Not yet implemented")
-        override val focusManager: FocusManager
-            get() = TODO("Not yet implemented")
-        override val windowManager: WindowManager
-            get() = TODO("Not yet implemented")
-        override val fontLoader: Font.ResourceLoader
-            get() = TODO("Not yet implemented")
-        override val layoutDirection: LayoutDirection
-            get() = LayoutDirection.Ltr
-        override var showLayoutBounds: Boolean = false
-        override val snapshotObserver = OwnerSnapshotObserver { it.invoke() }
-
-        override fun onRequestMeasure(layoutNode: LayoutNode) {
-            onRequestMeasureParams += layoutNode
-        }
-
-        override fun onRequestRelayout(layoutNode: LayoutNode) {
-        }
-
-        override val hasPendingMeasureOrLayout = false
-
-        override fun onAttach(node: LayoutNode) {
-            onAttachParams += node
-        }
-
-        override fun onDetach(node: LayoutNode) {
-            onDetachParams += node
-        }
-
-        override fun calculatePosition(): IntOffset = position
-
-        override fun requestFocus(): Boolean = false
-
-        @ExperimentalKeyInput
-        override fun sendKeyEvent(keyEvent: KeyEvent): Boolean = false
-
-        override fun measureAndLayout() {
-        }
-
-        override fun createLayer(
-            drawBlock: (Canvas) -> Unit,
-            invalidateParentLayer: () -> Unit
-        ): OwnedLayer {
-            return object : OwnedLayer {
-                override val layerId: Long
-                    get() = 0
-
-                override fun updateLayerProperties(
-                    scaleX: Float,
-                    scaleY: Float,
-                    alpha: Float,
-                    translationX: Float,
-                    translationY: Float,
-                    shadowElevation: Float,
-                    rotationX: Float,
-                    rotationY: Float,
-                    rotationZ: Float,
-                    cameraDistance: Float,
-                    transformOrigin: TransformOrigin,
-                    shape: Shape,
-                    clip: Boolean
-                ) {
-                }
-
-                override fun move(position: IntOffset) {
-                }
-
-                override fun resize(size: IntSize) {
-                }
-
-                override fun drawLayer(canvas: Canvas) {
-                    drawBlock(canvas)
-                }
-
-                override fun updateDisplayList() {
-                }
-
-                override fun invalidate() {
-                }
-
-                override fun destroy() {
-                }
-
-                override fun getMatrix(matrix: Matrix) {
-                }
-            }
-        }
-
-        override fun onSemanticsChange() {
-        }
-
-        override val measureIteration: Long = 0
-        override val viewConfiguration: ViewConfiguration
-            get() = TestViewConfiguration()
-    }
-
     @OptIn(ExperimentalComposeApi::class)
     class EmptyApplier : Applier<Unit> {
         override val current: Unit = Unit
         override fun down(node: Unit) {}
         override fun up() {}
-        override fun insert(index: Int, instance: Unit) {
+        override fun insertTopDown(index: Int, instance: Unit) {
+            error("Unexpected")
+        }
+        override fun insertBottomUp(index: Int, instance: Unit) {
             error("Unexpected")
         }
         override fun remove(index: Int, count: Int) {
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
index a5740b8..5484742 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.foundation.gestures
 
 import androidx.compose.ui.gesture.DoubleTapTimeout
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.gesture.LongPressTimeout
 import androidx.compose.ui.input.pointer.consumeDownChange
 import androidx.compose.ui.input.pointer.consumePositionChange
@@ -34,7 +31,6 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@OptIn(ExperimentalPointerInput::class)
 class TapGestureDetectorTest {
     private var pressed = false
     private var released = false
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
index 9ea31ce..cd6e838 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.selection.Selectable
 import androidx.compose.ui.selection.Selection
 import androidx.compose.ui.selection.SelectionRegistrar
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.InternalTextApi
 import androidx.compose.ui.text.TextDelegate
 import androidx.compose.ui.text.style.ResolvedTextDirection
@@ -36,7 +37,10 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@OptIn(InternalTextApi::class)
+@OptIn(
+    InternalTextApi::class,
+    ExperimentalTextApi::class
+)
 class TextSelectionLongPressDragTest {
     private val selectionRegistrar = mock<SelectionRegistrar>()
     private val selectable = mock<Selectable>()
@@ -93,15 +97,14 @@
     }
 
     @Test
-    fun longPressDragObserver_onLongPress_calls_getSelection_change_selection() {
+    fun longPressDragObserver_onLongPress_calls_notifySelectionInitiated() {
         val position = Offset(100f, 100f)
 
         gesture.onLongPress(position)
 
-        verify(selectionRegistrar, times(1)).onUpdateSelection(
+        verify(selectionRegistrar, times(1)).notifySelectionUpdateStart(
             layoutCoordinates = layoutCoordinates,
-            startPosition = position,
-            endPosition = position
+            startPosition = position
         )
     }
 
@@ -127,7 +130,7 @@
 
         // Verify.
         verify(selectionRegistrar, times(1))
-            .onUpdateSelection(
+            .notifySelectionUpdate(
                 layoutCoordinates = layoutCoordinates,
                 startPosition = beginPosition2,
                 endPosition = beginPosition2 + dragDistance2
@@ -135,7 +138,7 @@
     }
 
     @Test
-    fun longPressDragObserver_onDrag_calls_getSelection_change_selection() {
+    fun longPressDragObserver_onDrag_calls_notifySelectionDrag() {
         val dragDistance = Offset(15f, 10f)
         val beginPosition = Offset(30f, 20f)
         gesture.onLongPress(beginPosition)
@@ -146,10 +149,35 @@
 
         assertThat(result).isEqualTo(dragDistance)
         verify(selectionRegistrar, times(1))
-            .onUpdateSelection(
+            .notifySelectionUpdate(
                 layoutCoordinates = layoutCoordinates,
                 startPosition = beginPosition,
                 endPosition = beginPosition + dragDistance
             )
     }
+
+    @Test
+    fun longPressDragObserver_onStop_calls_notifySelectionEnd() {
+        val dragDistance = Offset(15f, 10f)
+        val beginPosition = Offset(30f, 20f)
+        gesture.onLongPress(beginPosition)
+        state.selectionRange = fakeInitialSelection.toTextRange()
+        gesture.onDragStart()
+        gesture.onStop(dragDistance)
+
+        verify(selectionRegistrar, times(1))
+            .notifySelectionUpdateEnd()
+    }
+
+    @Test
+    fun longPressDragObserver_onCancel_calls_notifySelectionEnd() {
+        val beginPosition = Offset(30f, 20f)
+        gesture.onLongPress(beginPosition)
+        state.selectionRange = fakeInitialSelection.toTextRange()
+        gesture.onDragStart()
+        gesture.onCancel()
+
+        verify(selectionRegistrar, times(1))
+            .notifySelectionUpdateEnd()
+    }
 }
\ No newline at end of file
diff --git a/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/LayoutNodeModifierBenchmark.kt b/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/LayoutNodeModifierBenchmark.kt
index 9f9e3dd..e67c2a2 100644
--- a/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/LayoutNodeModifierBenchmark.kt
+++ b/compose/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/LayoutNodeModifierBenchmark.kt
@@ -29,10 +29,9 @@
 import androidx.compose.ui.input.key.ExperimentalKeyInput
 import androidx.compose.ui.input.key.keyInputFilter
 import androidx.compose.ui.layout.layoutId
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.test.junit4.DisableTransitionsTestRule
@@ -55,7 +54,6 @@
  */
 @LargeTest
 @RunWith(Parameterized::class)
-@OptIn(ExperimentalLayoutNodeApi::class)
 class LayoutNodeModifierBenchmark(
     private val numberOfModifiers: Int
 ) {
@@ -96,7 +94,7 @@
             rule.activityTestRule.activity.setContent { Box(Modifier) }
         }
         rule.activityTestRule.runOnUiThread {
-            val composeView = rule.findAndroidOwner()
+            val composeView = rule.findViewRootForTest()
             val root = composeView.root
             check(root.children.size == 1) { "Expecting only a Box" }
             layoutNode = root.children[0]
@@ -143,9 +141,9 @@
                 .apply(base, description)
         }
 
-        fun findAndroidOwner(): AndroidOwner {
+        fun findViewRootForTest(): ViewRootForTest {
             return activityTestRule.activity.findViewById<ViewGroup>(android.R.id.content)
-                .getChildAt(0) as AndroidOwner
+                .getChildAt(0) as ViewRootForTest
         }
     }
 }
diff --git a/compose/integration-tests/demos/build.gradle b/compose/integration-tests/demos/build.gradle
index 2f7c2c0..e422582 100644
--- a/compose/integration-tests/demos/build.gradle
+++ b/compose/integration-tests/demos/build.gradle
@@ -32,7 +32,7 @@
     implementation project(":compose:runtime:runtime")
     implementation project(":compose:ui:ui")
 
-    implementation "androidx.preference:preference-ktx:1.1.0"
+    implementation "androidx.preference:preference-ktx:1.1.1"
 
     androidTestImplementation project(":compose:ui:ui-test-junit4")
 
diff --git a/compose/integration-tests/demos/lint-baseline.xml b/compose/integration-tests/demos/lint-baseline.xml
deleted file mode 100644
index 08f1cf1..0000000
--- a/compose/integration-tests/demos/lint-baseline.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-alpha15" client="gradle" variant="debug" version="4.2.0-alpha15">
-
-    <issue
-        id="ObsoleteLintCustomCheck"
-        message="Lint found an issue registry (`androidx.lifecycle.lint.LifecycleRuntimeIssueRegistry`) which is older than the current API level; these checks may not work correctly.&#xA;&#xA;Recompile the checks against the latest version. Custom check API version is 6 (3.6), current lint API level is 8 (4.1)">
-        <location
-            file="../../../../../../../home/jeffrygaston/.gradle/caches/transforms-2/files-2.1/02dcda78691438fc76cdfd012889a28d/lifecycle-runtime-ktx-2.2.0/jars/lint.jar"/>
-    </issue>
-
-</issues>
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoColors.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoColors.kt
index 4f908ce..5bb3529 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoColors.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoColors.kt
@@ -36,7 +36,6 @@
     var light: Colors by mutableStateOf(lightColors())
     var dark: Colors by mutableStateOf(darkColors())
 
-    @Composable
     val colors
-        get() = if (isSystemInDarkTheme()) dark else light
+        @Composable get() = if (isSystemInDarkTheme()) dark else light
 }
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/preview/LayoutPreview.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/preview/LayoutPreview.kt
index 3661374..25c3edd 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/preview/LayoutPreview.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/preview/LayoutPreview.kt
@@ -21,7 +21,7 @@
 package androidx.compose.integration.docs.preview
 
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants.defaultButtonColors
+import androidx.compose.material.ButtonDefaults.buttonColors
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
@@ -78,7 +78,7 @@
     fun Counter(count: Int, updateCount: (Int) -> Unit) {
         Button(
             onClick = { updateCount(count + 1) },
-            colors = defaultButtonColors(
+            colors = buttonColors(
                 backgroundColor = if (count > 5) Color.Green else Color.White
             )
         ) {
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/theming/Theming.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/theming/Theming.kt
index 918997d..95a8359 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/theming/Theming.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/theming/Theming.kt
@@ -26,7 +26,7 @@
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.AmbientContentAlpha
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Colors
 import androidx.compose.material.ContentAlpha
 import androidx.compose.material.MaterialTheme
@@ -179,9 +179,8 @@
 }
 
 private object ThemingSnippet11 {
-    @Composable
     val Colors.snackbarAction: Color
-        get() = if (isLight) Red300 else Red700
+        @Composable get() = if (isLight) Red300 else Red700
 }
 
 @Composable private fun ThemingSnippet12() {
@@ -251,7 +250,7 @@
         content: @Composable RowScope.() -> Unit
     ) {
         Button(
-            colors = ButtonConstants.defaultButtonColors(
+            colors = ButtonDefaults.buttonColors(
                 backgroundColor = MaterialTheme.colors.secondary
             ),
             onClick = onClick,
diff --git a/compose/integration-tests/macrobenchmark-target/build.gradle b/compose/integration-tests/macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..666cb1c
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -0,0 +1,39 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("AndroidXUiPlugin")
+    id("com.android.application")
+    id("org.jetbrains.kotlin.android")
+}
+
+android {
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
+        }
+    }
+}
+
+dependencies {
+    kotlinPlugin project(":compose:compiler:compiler")
+
+    implementation(KOTLIN_STDLIB)
+    implementation project(":compose:foundation:foundation-layout")
+    implementation project(":compose:material:material")
+    implementation project(":compose:runtime:runtime")
+    implementation project(":compose:ui:ui")
+    implementation project(":compose:ui:ui-tooling")
+}
+
+android.defaultConfig.minSdkVersion 21
+
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions {
+        useIR = true
+    }
+}
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..baca019
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<!--
+  ~ 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
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="androidx.compose.integration.macrobenchmark.target">
+
+    <application
+        android:label="Jetpack Compose Macrobenchmark Target"
+        android:allowBackup="false"
+        android:supportsRtl="true"
+        android:icon="@mipmap/ic_launcher"
+        tools:ignore="GoogleAppIndexingWarning">
+
+        <!--
+        Activities need to be exported so the macrobenchmark can discover them
+        under the new package visibility changes for Android 11.
+         -->
+        <activity
+            android:name=".TrivialStartupActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.TRIVIAL_STARTUP_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".LazyColumnActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.LAZY_COLUMN_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/ic_launcher-web.png b/compose/integration-tests/macrobenchmark-target/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..88e5f3b
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/ic_launcher-web.png
Binary files differ
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/LazyColumnActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/LazyColumnActivity.kt
new file mode 100644
index 0000000..8fa8f88
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/LazyColumnActivity.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+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.lazy.LazyColumnFor
+import androidx.compose.material.Card
+import androidx.compose.material.Checkbox
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.setContent
+import androidx.compose.ui.unit.dp
+
+class LazyColumnActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val itemCount = intent.getIntExtra(EXTRA_ITEM_COUNT, 1000)
+
+        setContent {
+            LazyColumnFor(
+                items = List(itemCount) {
+                    Entry("Item $it")
+                },
+                modifier = Modifier.fillMaxWidth(),
+                itemContent = { ListRow(it) }
+            )
+        }
+    }
+
+    companion object {
+        const val EXTRA_ITEM_COUNT = "ITEM_COUNT"
+    }
+}
+
+@Composable
+private fun ListRow(entry: Entry) {
+    Card(modifier = Modifier.padding(8.dp)) {
+        Row {
+            Text(
+                text = entry.contents,
+                modifier = Modifier.padding(16.dp)
+            )
+            Spacer(modifier = Modifier.weight(1f, fill = true))
+            Checkbox(
+                checked = false,
+                onCheckedChange = {},
+                modifier = Modifier.padding(16.dp)
+            )
+        }
+    }
+}
+
+data class Entry(val contents: String)
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/TrivialStartupActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/TrivialStartupActivity.kt
new file mode 100644
index 0000000..49dd58e
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/TrivialStartupActivity.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.compose.material.Text
+import androidx.compose.ui.platform.setContent
+
+class TrivialStartupActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContent {
+            Text("Compose Macrobenchmark Target")
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/drawable-v24/ic_launcher_foreground.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..bbbd1ec
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1" />
+</vector>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_background.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..2aea1e0
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector
+    android:height="108dp"
+    android:width="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#008577"
+          android:pathData="M0,0h108v108h-108z"/>
+    <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+</vector>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher.png b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher.png b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher.png b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed4659
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher.png b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/compose/integration-tests/macrobenchmark/build.gradle b/compose/integration-tests/macrobenchmark/build.gradle
index 9a6d909a..07444b7 100644
--- a/compose/integration-tests/macrobenchmark/build.gradle
+++ b/compose/integration-tests/macrobenchmark/build.gradle
@@ -40,4 +40,4 @@
 
 // Define a task dependency so the app is installed before we run macro benchmarks.
 tasks.getByPath(':compose:integration-tests:macrobenchmark:connectedCheck')
-    .dependsOn(tasks.getByPath(':compose:integration-tests:demos:installRelease'))
+    .dependsOn(tasks.getByPath(':compose:integration-tests:macrobenchmark-target:installRelease'))
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/AndroidManifest.xml b/compose/integration-tests/macrobenchmark/src/androidTest/AndroidManifest.xml
index df1de06..82ef367 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -19,13 +19,13 @@
 
     <!--
     The Macro Benchmark Sample needs to launch activities in
-    `androidx.compose.integration.demos` APK.
+    `androidx.compose.integration.macrobenchmark.target` APK.
 
     The Macro Benchmark Library uses `PackageManager` to query for activities. This requires
-     the test APK to declare that `androidx.compose.integration.demos` be visible to
+     the test APK to declare that `androidx.compose.integration.macrobenchmark.target` be visible to
      the APK (given Android 11's package visibility rules).
     -->
     <queries>
-        <package android:name="androidx.compose.integration.demos" />
+        <package android:name="androidx.compose.integration.macrobenchmark.target" />
     </queries>
 </manifest>
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/ProcessSpeedProfileValidation.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/ProcessSpeedProfileValidation.kt
index a9c3920..0fc4cabb 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/ProcessSpeedProfileValidation.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/ProcessSpeedProfileValidation.kt
@@ -56,7 +56,7 @@
     }
 
     companion object {
-        private const val PACKAGE_NAME = "androidx.compose.integration.demos"
+        private const val PACKAGE_NAME = "androidx.compose.integration.macrobenchmark.target"
 
         @Parameterized.Parameters(name = "compilation_mode={0}, startup_mode={1}")
         @JvmStatic
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupDemosMacrobenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
similarity index 62%
copy from compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupDemosMacrobenchmark.kt
copy to compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
index ace7a4c..34cf7f4 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupDemosMacrobenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
@@ -19,39 +19,32 @@
 import androidx.benchmark.macro.MacrobenchmarkRule
 import androidx.benchmark.macro.StartupMode
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
 @LargeTest
-@SdkSuppress(minSdkVersion = 29)
-@RunWith(Parameterized::class) // Parameterized to work around timeouts (b/174175784)
-class StartupDemosMacrobenchmark(
-    @Suppress("unused") private val ignored: Boolean
-) {
-
+@RunWith(Parameterized::class)
+class SmallListStartupBenchmark(private val startupMode: StartupMode) {
     @get:Rule
     val benchmarkRule = MacrobenchmarkRule()
 
     @Test
-    fun compiledColdStartup() = benchmarkRule.measureStartup(
+    fun startup() = benchmarkRule.measureStartup(
         profileCompiled = true,
-        startupMode = StartupMode.COLD
-    )
-
-    @Test
-    fun uncompiledColdStartup() = benchmarkRule.measureStartup(
-        profileCompiled = false,
-        startupMode = StartupMode.COLD
-    )
+        startupMode = startupMode
+    ) {
+        action = "androidx.compose.integration.macrobenchmark.target.LAZY_COLUMN_ACTIVITY"
+        putExtra("ITEM_COUNT", 5)
+    }
 
     companion object {
+        @Parameterized.Parameters(name = "mode={0}")
         @JvmStatic
-        @Parameterized.Parameters
-        fun startupDemosParameters(): List<Array<Any>> {
-            return listOf(arrayOf(false))
+        fun parameters(): List<Array<Any>> {
+            return listOf(StartupMode.COLD, StartupMode.WARM)
+                .map { arrayOf(it) }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupUtils.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupUtils.kt
index fe30427..4ef10ad4 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupUtils.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupUtils.kt
@@ -23,7 +23,7 @@
 import androidx.benchmark.macro.StartupMode
 import androidx.benchmark.macro.StartupTimingMetric
 
-const val TargetPackage = "androidx.compose.integration.demos"
+const val TargetPackage = "androidx.compose.integration.macrobenchmark.target"
 
 /**
  * Simplified interface for standardizing e.g. package,
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupDemosMacrobenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
similarity index 62%
rename from compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupDemosMacrobenchmark.kt
rename to compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
index ace7a4c..0d92fdc 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/StartupDemosMacrobenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
@@ -19,39 +19,31 @@
 import androidx.benchmark.macro.MacrobenchmarkRule
 import androidx.benchmark.macro.StartupMode
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
 @LargeTest
-@SdkSuppress(minSdkVersion = 29)
-@RunWith(Parameterized::class) // Parameterized to work around timeouts (b/174175784)
-class StartupDemosMacrobenchmark(
-    @Suppress("unused") private val ignored: Boolean
-) {
-
+@RunWith(Parameterized::class)
+class TrivialStartupBenchmark(private val startupMode: StartupMode) {
     @get:Rule
     val benchmarkRule = MacrobenchmarkRule()
 
     @Test
-    fun compiledColdStartup() = benchmarkRule.measureStartup(
+    fun startup() = benchmarkRule.measureStartup(
         profileCompiled = true,
-        startupMode = StartupMode.COLD
-    )
-
-    @Test
-    fun uncompiledColdStartup() = benchmarkRule.measureStartup(
-        profileCompiled = false,
-        startupMode = StartupMode.COLD
-    )
+        startupMode = startupMode
+    ) {
+        action = "androidx.compose.integration.macrobenchmark.target.TRIVIAL_STARTUP_ACTIVITY"
+    }
 
     companion object {
+        @Parameterized.Parameters(name = "mode={0}")
         @JvmStatic
-        @Parameterized.Parameters
-        fun startupDemosParameters(): List<Array<Any>> {
-            return listOf(arrayOf(false))
+        fun parameters(): List<Array<Any>> {
+            return listOf(StartupMode.COLD, StartupMode.WARM, StartupMode.HOT)
+                .map { arrayOf(it) }
         }
     }
 }
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index a589728..9034474 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -12,18 +12,32 @@
     method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional float elevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class BackdropScaffoldConstants {
-    method public float getDefaultFrontLayerElevation-D9Ej5fM();
-    method public long getDefaultFrontLayerScrimColor-0d7_KjU();
-    method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
-    method public float getDefaultHeaderHeight-D9Ej5fM();
-    method public float getDefaultPeekHeight-D9Ej5fM();
+  @Deprecated public final class BackdropScaffoldConstants {
+    method @Deprecated public float getDefaultFrontLayerElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultFrontLayerScrimColor-0d7_KjU();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
+    method @Deprecated public float getDefaultHeaderHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultPeekHeight-D9Ej5fM();
     property public final float DefaultFrontLayerElevation;
-    property public final long DefaultFrontLayerScrimColor;
-    property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
+    property @androidx.compose.runtime.Composable public final long DefaultFrontLayerScrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
     property public final float DefaultHeaderHeight;
     property public final float DefaultPeekHeight;
-    field public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
+  }
+
+  public final class BackdropScaffoldDefaults {
+    method public float getFrontLayerElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getFrontLayerScrimColor-0d7_KjU();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFrontLayerShape();
+    method public float getHeaderHeight-D9Ej5fM();
+    method public float getPeekHeight-D9Ej5fM();
+    property public final float FrontLayerElevation;
+    property public final float HeaderHeight;
+    property public final float PeekHeight;
+    property @androidx.compose.runtime.Composable public final long frontLayerScrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape frontLayerShape;
+    field public static final androidx.compose.material.BackdropScaffoldDefaults INSTANCE;
   }
 
   public final class BackdropScaffoldKt {
@@ -82,12 +96,20 @@
     method @androidx.compose.runtime.Composable public static void BottomNavigationItem-S1l6qvI(kotlin.jvm.functions.Function0<kotlin.Unit> icon, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> label, optional boolean alwaysShowLabels, optional androidx.compose.foundation.InteractionState interactionState, optional long selectedContentColor, optional long unselectedContentColor);
   }
 
-  public final class BottomSheetScaffoldConstants {
-    method public float getDefaultSheetElevation-D9Ej5fM();
-    method public float getDefaultSheetPeekHeight-D9Ej5fM();
+  @Deprecated public final class BottomSheetScaffoldConstants {
+    method @Deprecated public float getDefaultSheetElevation-D9Ej5fM();
+    method @Deprecated public float getDefaultSheetPeekHeight-D9Ej5fM();
     property public final float DefaultSheetElevation;
     property public final float DefaultSheetPeekHeight;
-    field public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+  }
+
+  public final class BottomSheetScaffoldDefaults {
+    method public float getSheetElevation-D9Ej5fM();
+    method public float getSheetPeekHeight-D9Ej5fM();
+    property public final float SheetElevation;
+    property public final float SheetPeekHeight;
+    field public static final androidx.compose.material.BottomSheetScaffoldDefaults INSTANCE;
   }
 
   public final class BottomSheetScaffoldKt {
@@ -131,19 +153,19 @@
     method public long contentColor-0d7_KjU(boolean enabled);
   }
 
-  public final class ButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultButtonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation defaultElevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultOutlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultTextButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
-    method public androidx.compose.foundation.layout.PaddingValues getDefaultContentPadding();
-    method public float getDefaultIconSize-D9Ej5fM();
-    method public float getDefaultIconSpacing-D9Ej5fM();
-    method public float getDefaultMinHeight-D9Ej5fM();
-    method public float getDefaultMinWidth-D9Ej5fM();
-    method public androidx.compose.foundation.BorderStroke getDefaultOutlinedBorder();
-    method public androidx.compose.foundation.layout.PaddingValues getDefaultTextContentPadding();
-    method public float getOutlinedBorderSize-D9Ej5fM();
+  @Deprecated public final class ButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultButtonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation defaultElevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultOutlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultTextButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated public androidx.compose.foundation.layout.PaddingValues getDefaultContentPadding();
+    method @Deprecated public float getDefaultIconSize-D9Ej5fM();
+    method @Deprecated public float getDefaultIconSpacing-D9Ej5fM();
+    method @Deprecated public float getDefaultMinHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultMinWidth-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke getDefaultOutlinedBorder();
+    method @Deprecated public androidx.compose.foundation.layout.PaddingValues getDefaultTextContentPadding();
+    method @Deprecated public float getOutlinedBorderSize-D9Ej5fM();
     property public final androidx.compose.foundation.layout.PaddingValues DefaultContentPadding;
     property public final float DefaultIconSize;
     property public final float DefaultIconSpacing;
@@ -151,8 +173,33 @@
     property public final float DefaultMinWidth;
     property public final androidx.compose.foundation.layout.PaddingValues DefaultTextContentPadding;
     property public final float OutlinedBorderSize;
-    property public final androidx.compose.foundation.BorderStroke defaultOutlinedBorder;
-    field public static final androidx.compose.material.ButtonConstants INSTANCE;
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.BorderStroke defaultOutlinedBorder;
+    field @Deprecated public static final androidx.compose.material.ButtonConstants INSTANCE;
+    field @Deprecated public static final float OutlinedBorderOpacity = 0.12f;
+  }
+
+  public final class ButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors buttonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method public float getIconSize-D9Ej5fM();
+    method public float getIconSpacing-D9Ej5fM();
+    method public float getMinHeight-D9Ej5fM();
+    method public float getMinWidth-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke getOutlinedBorder();
+    method public float getOutlinedBorderSize-D9Ej5fM();
+    method public androidx.compose.foundation.layout.PaddingValues getTextButtonContentPadding();
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors outlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors textButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
+    property public final float IconSize;
+    property public final float IconSpacing;
+    property public final float MinHeight;
+    property public final float MinWidth;
+    property public final float OutlinedBorderSize;
+    property public final androidx.compose.foundation.layout.PaddingValues TextButtonContentPadding;
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.BorderStroke outlinedBorder;
+    field public static final androidx.compose.material.ButtonDefaults INSTANCE;
     field public static final float OutlinedBorderOpacity = 0.12f;
   }
 
@@ -176,9 +223,14 @@
     method public long checkmarkColor-0d7_KjU(androidx.compose.ui.state.ToggleableState state);
   }
 
-  public final class CheckboxConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors defaultColors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
-    field public static final androidx.compose.material.CheckboxConstants INSTANCE;
+  @Deprecated public final class CheckboxConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors defaultColors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
+    field @Deprecated public static final androidx.compose.material.CheckboxConstants INSTANCE;
+  }
+
+  public final class CheckboxDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors colors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
+    field public static final androidx.compose.material.CheckboxDefaults INSTANCE;
   }
 
   public final class CheckboxKt {
@@ -224,12 +276,12 @@
   }
 
   public final class ContentAlpha {
-    method public float getDisabled();
-    method public float getHigh();
-    method public float getMedium();
-    property public final float disabled;
-    property public final float high;
-    property public final float medium;
+    method @androidx.compose.runtime.Composable public float getDisabled();
+    method @androidx.compose.runtime.Composable public float getHigh();
+    method @androidx.compose.runtime.Composable public float getMedium();
+    property @androidx.compose.runtime.Composable public final float disabled;
+    property @androidx.compose.runtime.Composable public final float high;
+    property @androidx.compose.runtime.Composable public final float medium;
     field public static final androidx.compose.material.ContentAlpha INSTANCE;
   }
 
@@ -270,13 +322,22 @@
     method @androidx.compose.runtime.Composable public static void Divider-JRSVyrs(optional androidx.compose.ui.Modifier modifier, optional long color, optional float thickness, optional float startIndent);
   }
 
-  public final class DrawerConstants {
-    method public float getDefaultElevation-D9Ej5fM();
-    method public long getDefaultScrimColor-0d7_KjU();
+  @Deprecated public final class DrawerConstants {
+    method @Deprecated public float getDefaultElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultScrimColor-0d7_KjU();
     property public final float DefaultElevation;
-    property public final long defaultScrimColor;
-    field public static final androidx.compose.material.DrawerConstants INSTANCE;
-    field public static final float ScrimDefaultOpacity = 0.32f;
+    property @androidx.compose.runtime.Composable public final long defaultScrimColor;
+    field @Deprecated public static final androidx.compose.material.DrawerConstants INSTANCE;
+    field @Deprecated public static final float ScrimDefaultOpacity = 0.32f;
+  }
+
+  public final class DrawerDefaults {
+    method public float getElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getScrimColor-0d7_KjU();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long scrimColor;
+    field public static final androidx.compose.material.DrawerDefaults INSTANCE;
+    field public static final float ScrimOpacity = 0.32f;
   }
 
   public final class DrawerKt {
@@ -306,10 +367,16 @@
     enum_constant public static final androidx.compose.material.DrawerValue Open;
   }
 
-  public final class ElevationConstants {
+  @Deprecated public final class ElevationConstants {
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? incomingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? outgoingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
+    field @Deprecated public static final androidx.compose.material.ElevationConstants INSTANCE;
+  }
+
+  public final class ElevationDefaults {
     method public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? incomingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
     method public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? outgoingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
-    field public static final androidx.compose.material.ElevationConstants INSTANCE;
+    field public static final androidx.compose.material.ElevationDefaults INSTANCE;
   }
 
   public final class ElevationKt {
@@ -335,12 +402,12 @@
   }
 
   @Deprecated public interface EmphasisLevels {
-    method @Deprecated public androidx.compose.material.Emphasis getDisabled();
-    method @Deprecated public androidx.compose.material.Emphasis getHigh();
-    method @Deprecated public androidx.compose.material.Emphasis getMedium();
-    property public abstract androidx.compose.material.Emphasis disabled;
-    property public abstract androidx.compose.material.Emphasis high;
-    property public abstract androidx.compose.material.Emphasis medium;
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getDisabled();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getHigh();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getMedium();
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis disabled;
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis high;
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis medium;
   }
 
   @kotlin.RequiresOptIn(message="This material API is experimental and is likely to change or to be removed in" + " the future.") public @interface ExperimentalMaterialApi {
@@ -356,9 +423,14 @@
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public androidx.compose.material.FixedThreshold copy-0680j_4(float offset);
   }
 
-  public final class FloatingActionButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
-    field public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
+  @Deprecated public final class FloatingActionButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
+    field @Deprecated public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
+  }
+
+  public final class FloatingActionButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
+    field public static final androidx.compose.material.FloatingActionButtonDefaults INSTANCE;
   }
 
   @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface FloatingActionButtonElevation {
@@ -395,12 +467,12 @@
   }
 
   public final class MaterialTheme {
-    method public androidx.compose.material.Colors getColors();
-    method public androidx.compose.material.Shapes getShapes();
-    method public androidx.compose.material.Typography getTypography();
-    property public final androidx.compose.material.Colors colors;
-    property public final androidx.compose.material.Shapes shapes;
-    property public final androidx.compose.material.Typography typography;
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Colors getColors();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Shapes getShapes();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Typography getTypography();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Colors colors;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Shapes shapes;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Typography typography;
     field public static final androidx.compose.material.MaterialTheme INSTANCE;
   }
 
@@ -413,12 +485,20 @@
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  public final class ModalBottomSheetConstants {
-    method public float getDefaultElevation-D9Ej5fM();
-    method public long getDefaultScrimColor-0d7_KjU();
+  @Deprecated public final class ModalBottomSheetConstants {
+    method @Deprecated public float getDefaultElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultScrimColor-0d7_KjU();
     property public final float DefaultElevation;
-    property public final long DefaultScrimColor;
-    field public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+    property @androidx.compose.runtime.Composable public final long DefaultScrimColor;
+    field @Deprecated public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+  }
+
+  public final class ModalBottomSheetDefaults {
+    method public float getElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getScrimColor-0d7_KjU();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long scrimColor;
+    field public static final androidx.compose.material.ModalBottomSheetDefaults INSTANCE;
   }
 
   public final class ModalBottomSheetKt {
@@ -450,13 +530,22 @@
     method @androidx.compose.runtime.Composable public static void OutlinedTextField-IIju55g(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
   }
 
-  public final class ProgressIndicatorConstants {
-    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultProgressAnimationSpec();
-    method public float getDefaultStrokeWidth-D9Ej5fM();
+  @Deprecated public final class ProgressIndicatorConstants {
+    method @Deprecated public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultProgressAnimationSpec();
+    method @Deprecated public float getDefaultStrokeWidth-D9Ej5fM();
     property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> DefaultProgressAnimationSpec;
     property public final float DefaultStrokeWidth;
-    field public static final float DefaultIndicatorBackgroundOpacity = 0.24f;
-    field public static final androidx.compose.material.ProgressIndicatorConstants INSTANCE;
+    field @Deprecated public static final float DefaultIndicatorBackgroundOpacity = 0.24f;
+    field @Deprecated public static final androidx.compose.material.ProgressIndicatorConstants INSTANCE;
+  }
+
+  public final class ProgressIndicatorDefaults {
+    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getProgressAnimationSpec();
+    method public float getStrokeWidth-D9Ej5fM();
+    property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> ProgressAnimationSpec;
+    property public final float StrokeWidth;
+    field public static final androidx.compose.material.ProgressIndicatorDefaults INSTANCE;
+    field public static final float IndicatorBackgroundOpacity = 0.24f;
   }
 
   public final class ProgressIndicatorKt {
@@ -470,9 +559,14 @@
     method public long radioColor-0d7_KjU(boolean enabled, boolean selected);
   }
 
-  public final class RadioButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors defaultColors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
-    field public static final androidx.compose.material.RadioButtonConstants INSTANCE;
+  @Deprecated public final class RadioButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors defaultColors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
+    field @Deprecated public static final androidx.compose.material.RadioButtonConstants INSTANCE;
+  }
+
+  public final class RadioButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors colors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
+    field public static final androidx.compose.material.RadioButtonDefaults INSTANCE;
   }
 
   public final class RadioButtonKt {
@@ -525,8 +619,14 @@
   public final class ShapesKt {
   }
 
-  public final class SliderConstants {
-    field public static final androidx.compose.material.SliderConstants INSTANCE;
+  @Deprecated public final class SliderConstants {
+    field @Deprecated public static final androidx.compose.material.SliderConstants INSTANCE;
+    field @Deprecated public static final float InactiveTrackColorAlpha = 0.24f;
+    field @Deprecated public static final float TickColorAlpha = 0.54f;
+  }
+
+  public final class SliderDefaults {
+    field public static final androidx.compose.material.SliderDefaults INSTANCE;
     field public static final float InactiveTrackColorAlpha = 0.24f;
     field public static final float TickColorAlpha = 0.54f;
   }
@@ -535,12 +635,12 @@
     method @androidx.compose.runtime.Composable public static void Slider-B7rb6FQ(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit> onValueChangeEnd, optional androidx.compose.foundation.InteractionState interactionState, optional long thumbColor, optional long activeTrackColor, optional long inactiveTrackColor, optional long activeTickColor, optional long inactiveTickColor);
   }
 
-  public final class SnackbarConstants {
-    method public long getDefaultActionPrimaryColor-0d7_KjU();
-    method public long getDefaultBackgroundColor-0d7_KjU();
-    property public final long defaultActionPrimaryColor;
-    property public final long defaultBackgroundColor;
-    field public static final androidx.compose.material.SnackbarConstants INSTANCE;
+  @Deprecated public final class SnackbarConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultActionPrimaryColor-0d7_KjU();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultBackgroundColor-0d7_KjU();
+    property @androidx.compose.runtime.Composable public final long defaultActionPrimaryColor;
+    property @androidx.compose.runtime.Composable public final long defaultBackgroundColor;
+    field @Deprecated public static final androidx.compose.material.SnackbarConstants INSTANCE;
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public interface SnackbarData {
@@ -554,6 +654,14 @@
     property public abstract String message;
   }
 
+  public final class SnackbarDefaults {
+    method @androidx.compose.runtime.Composable public long getBackgroundColor-0d7_KjU();
+    method @androidx.compose.runtime.Composable public long getPrimaryActionColor-0d7_KjU();
+    property @androidx.compose.runtime.Composable public final long backgroundColor;
+    property @androidx.compose.runtime.Composable public final long primaryActionColor;
+    field public static final androidx.compose.material.SnackbarDefaults INSTANCE;
+  }
+
   public enum SnackbarDuration {
     enum_constant public static final androidx.compose.material.SnackbarDuration Indefinite;
     enum_constant public static final androidx.compose.material.SnackbarDuration Long;
@@ -605,13 +713,24 @@
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.DismissState rememberDismissState(optional androidx.compose.material.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.DismissValue,java.lang.Boolean> confirmStateChange);
   }
 
-  public final class SwipeableConstants {
-    method public androidx.compose.material.ResistanceConfig? defaultResistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
-    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultAnimationSpec();
-    method public float getDefaultVelocityThreshold-D9Ej5fM();
+  @Deprecated public final class SwipeableConstants {
+    method @Deprecated public androidx.compose.material.ResistanceConfig? defaultResistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
+    method @Deprecated public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultAnimationSpec();
+    method @Deprecated public float getDefaultVelocityThreshold-D9Ej5fM();
     property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> DefaultAnimationSpec;
     property public final float DefaultVelocityThreshold;
-    field public static final androidx.compose.material.SwipeableConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.SwipeableConstants INSTANCE;
+    field @Deprecated public static final float StandardResistanceFactor = 10.0f;
+    field @Deprecated public static final float StiffResistanceFactor = 20.0f;
+  }
+
+  public final class SwipeableDefaults {
+    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getAnimationSpec();
+    method public float getVelocityThreshold-D9Ej5fM();
+    method public androidx.compose.material.ResistanceConfig? resistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
+    property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> AnimationSpec;
+    property public final float VelocityThreshold;
+    field public static final androidx.compose.material.SwipeableDefaults INSTANCE;
     field public static final float StandardResistanceFactor = 10.0f;
     field public static final float StiffResistanceFactor = 20.0f;
   }
@@ -651,27 +770,46 @@
     method public long trackColor-0d7_KjU(boolean enabled, boolean checked);
   }
 
-  public final class SwitchConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
-    field public static final androidx.compose.material.SwitchConstants INSTANCE;
+  @Deprecated public final class SwitchConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field @Deprecated public static final androidx.compose.material.SwitchConstants INSTANCE;
+  }
+
+  public final class SwitchDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors colors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field public static final androidx.compose.material.SwitchDefaults INSTANCE;
   }
 
   public final class SwitchKt {
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.material.SwitchColors colors);
   }
 
-  public final class TabConstants {
-    method @androidx.compose.runtime.Composable public void DefaultDivider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
-    method @androidx.compose.runtime.Composable public void DefaultIndicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
-    method public androidx.compose.ui.Modifier defaultTabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
-    method public float getDefaultDividerThickness-D9Ej5fM();
-    method public float getDefaultIndicatorHeight-D9Ej5fM();
-    method public float getDefaultScrollableTabRowPadding-D9Ej5fM();
+  @Deprecated public final class TabConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public void DefaultDivider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public void DefaultIndicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @Deprecated public androidx.compose.ui.Modifier defaultTabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
+    method @Deprecated public float getDefaultDividerThickness-D9Ej5fM();
+    method @Deprecated public float getDefaultIndicatorHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultScrollableTabRowPadding-D9Ej5fM();
     property public final float DefaultDividerThickness;
     property public final float DefaultIndicatorHeight;
     property public final float DefaultScrollableTabRowPadding;
-    field public static final float DefaultDividerOpacity = 0.12f;
-    field public static final androidx.compose.material.TabConstants INSTANCE;
+    field @Deprecated public static final float DefaultDividerOpacity = 0.12f;
+    field @Deprecated public static final androidx.compose.material.TabConstants INSTANCE;
+  }
+
+  public final class TabDefaults {
+    method @androidx.compose.runtime.Composable public void Divider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @androidx.compose.runtime.Composable public void Indicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method public float getDividerThickness-D9Ej5fM();
+    method public float getIndicatorHeight-D9Ej5fM();
+    method public float getScrollableTabRowPadding-D9Ej5fM();
+    method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
+    property public final float DividerThickness;
+    property public final float IndicatorHeight;
+    property public final float ScrollableTabRowPadding;
+    field public static final float DividerOpacity = 0.12f;
+    field public static final androidx.compose.material.TabDefaults INSTANCE;
   }
 
   public final class TabKt {
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index a589728..9034474 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -12,18 +12,32 @@
     method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional float elevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class BackdropScaffoldConstants {
-    method public float getDefaultFrontLayerElevation-D9Ej5fM();
-    method public long getDefaultFrontLayerScrimColor-0d7_KjU();
-    method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
-    method public float getDefaultHeaderHeight-D9Ej5fM();
-    method public float getDefaultPeekHeight-D9Ej5fM();
+  @Deprecated public final class BackdropScaffoldConstants {
+    method @Deprecated public float getDefaultFrontLayerElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultFrontLayerScrimColor-0d7_KjU();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
+    method @Deprecated public float getDefaultHeaderHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultPeekHeight-D9Ej5fM();
     property public final float DefaultFrontLayerElevation;
-    property public final long DefaultFrontLayerScrimColor;
-    property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
+    property @androidx.compose.runtime.Composable public final long DefaultFrontLayerScrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
     property public final float DefaultHeaderHeight;
     property public final float DefaultPeekHeight;
-    field public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
+  }
+
+  public final class BackdropScaffoldDefaults {
+    method public float getFrontLayerElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getFrontLayerScrimColor-0d7_KjU();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFrontLayerShape();
+    method public float getHeaderHeight-D9Ej5fM();
+    method public float getPeekHeight-D9Ej5fM();
+    property public final float FrontLayerElevation;
+    property public final float HeaderHeight;
+    property public final float PeekHeight;
+    property @androidx.compose.runtime.Composable public final long frontLayerScrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape frontLayerShape;
+    field public static final androidx.compose.material.BackdropScaffoldDefaults INSTANCE;
   }
 
   public final class BackdropScaffoldKt {
@@ -82,12 +96,20 @@
     method @androidx.compose.runtime.Composable public static void BottomNavigationItem-S1l6qvI(kotlin.jvm.functions.Function0<kotlin.Unit> icon, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> label, optional boolean alwaysShowLabels, optional androidx.compose.foundation.InteractionState interactionState, optional long selectedContentColor, optional long unselectedContentColor);
   }
 
-  public final class BottomSheetScaffoldConstants {
-    method public float getDefaultSheetElevation-D9Ej5fM();
-    method public float getDefaultSheetPeekHeight-D9Ej5fM();
+  @Deprecated public final class BottomSheetScaffoldConstants {
+    method @Deprecated public float getDefaultSheetElevation-D9Ej5fM();
+    method @Deprecated public float getDefaultSheetPeekHeight-D9Ej5fM();
     property public final float DefaultSheetElevation;
     property public final float DefaultSheetPeekHeight;
-    field public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+  }
+
+  public final class BottomSheetScaffoldDefaults {
+    method public float getSheetElevation-D9Ej5fM();
+    method public float getSheetPeekHeight-D9Ej5fM();
+    property public final float SheetElevation;
+    property public final float SheetPeekHeight;
+    field public static final androidx.compose.material.BottomSheetScaffoldDefaults INSTANCE;
   }
 
   public final class BottomSheetScaffoldKt {
@@ -131,19 +153,19 @@
     method public long contentColor-0d7_KjU(boolean enabled);
   }
 
-  public final class ButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultButtonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation defaultElevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultOutlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultTextButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
-    method public androidx.compose.foundation.layout.PaddingValues getDefaultContentPadding();
-    method public float getDefaultIconSize-D9Ej5fM();
-    method public float getDefaultIconSpacing-D9Ej5fM();
-    method public float getDefaultMinHeight-D9Ej5fM();
-    method public float getDefaultMinWidth-D9Ej5fM();
-    method public androidx.compose.foundation.BorderStroke getDefaultOutlinedBorder();
-    method public androidx.compose.foundation.layout.PaddingValues getDefaultTextContentPadding();
-    method public float getOutlinedBorderSize-D9Ej5fM();
+  @Deprecated public final class ButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultButtonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation defaultElevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultOutlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultTextButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated public androidx.compose.foundation.layout.PaddingValues getDefaultContentPadding();
+    method @Deprecated public float getDefaultIconSize-D9Ej5fM();
+    method @Deprecated public float getDefaultIconSpacing-D9Ej5fM();
+    method @Deprecated public float getDefaultMinHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultMinWidth-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke getDefaultOutlinedBorder();
+    method @Deprecated public androidx.compose.foundation.layout.PaddingValues getDefaultTextContentPadding();
+    method @Deprecated public float getOutlinedBorderSize-D9Ej5fM();
     property public final androidx.compose.foundation.layout.PaddingValues DefaultContentPadding;
     property public final float DefaultIconSize;
     property public final float DefaultIconSpacing;
@@ -151,8 +173,33 @@
     property public final float DefaultMinWidth;
     property public final androidx.compose.foundation.layout.PaddingValues DefaultTextContentPadding;
     property public final float OutlinedBorderSize;
-    property public final androidx.compose.foundation.BorderStroke defaultOutlinedBorder;
-    field public static final androidx.compose.material.ButtonConstants INSTANCE;
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.BorderStroke defaultOutlinedBorder;
+    field @Deprecated public static final androidx.compose.material.ButtonConstants INSTANCE;
+    field @Deprecated public static final float OutlinedBorderOpacity = 0.12f;
+  }
+
+  public final class ButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors buttonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method public float getIconSize-D9Ej5fM();
+    method public float getIconSpacing-D9Ej5fM();
+    method public float getMinHeight-D9Ej5fM();
+    method public float getMinWidth-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke getOutlinedBorder();
+    method public float getOutlinedBorderSize-D9Ej5fM();
+    method public androidx.compose.foundation.layout.PaddingValues getTextButtonContentPadding();
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors outlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors textButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
+    property public final float IconSize;
+    property public final float IconSpacing;
+    property public final float MinHeight;
+    property public final float MinWidth;
+    property public final float OutlinedBorderSize;
+    property public final androidx.compose.foundation.layout.PaddingValues TextButtonContentPadding;
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.BorderStroke outlinedBorder;
+    field public static final androidx.compose.material.ButtonDefaults INSTANCE;
     field public static final float OutlinedBorderOpacity = 0.12f;
   }
 
@@ -176,9 +223,14 @@
     method public long checkmarkColor-0d7_KjU(androidx.compose.ui.state.ToggleableState state);
   }
 
-  public final class CheckboxConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors defaultColors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
-    field public static final androidx.compose.material.CheckboxConstants INSTANCE;
+  @Deprecated public final class CheckboxConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors defaultColors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
+    field @Deprecated public static final androidx.compose.material.CheckboxConstants INSTANCE;
+  }
+
+  public final class CheckboxDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors colors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
+    field public static final androidx.compose.material.CheckboxDefaults INSTANCE;
   }
 
   public final class CheckboxKt {
@@ -224,12 +276,12 @@
   }
 
   public final class ContentAlpha {
-    method public float getDisabled();
-    method public float getHigh();
-    method public float getMedium();
-    property public final float disabled;
-    property public final float high;
-    property public final float medium;
+    method @androidx.compose.runtime.Composable public float getDisabled();
+    method @androidx.compose.runtime.Composable public float getHigh();
+    method @androidx.compose.runtime.Composable public float getMedium();
+    property @androidx.compose.runtime.Composable public final float disabled;
+    property @androidx.compose.runtime.Composable public final float high;
+    property @androidx.compose.runtime.Composable public final float medium;
     field public static final androidx.compose.material.ContentAlpha INSTANCE;
   }
 
@@ -270,13 +322,22 @@
     method @androidx.compose.runtime.Composable public static void Divider-JRSVyrs(optional androidx.compose.ui.Modifier modifier, optional long color, optional float thickness, optional float startIndent);
   }
 
-  public final class DrawerConstants {
-    method public float getDefaultElevation-D9Ej5fM();
-    method public long getDefaultScrimColor-0d7_KjU();
+  @Deprecated public final class DrawerConstants {
+    method @Deprecated public float getDefaultElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultScrimColor-0d7_KjU();
     property public final float DefaultElevation;
-    property public final long defaultScrimColor;
-    field public static final androidx.compose.material.DrawerConstants INSTANCE;
-    field public static final float ScrimDefaultOpacity = 0.32f;
+    property @androidx.compose.runtime.Composable public final long defaultScrimColor;
+    field @Deprecated public static final androidx.compose.material.DrawerConstants INSTANCE;
+    field @Deprecated public static final float ScrimDefaultOpacity = 0.32f;
+  }
+
+  public final class DrawerDefaults {
+    method public float getElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getScrimColor-0d7_KjU();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long scrimColor;
+    field public static final androidx.compose.material.DrawerDefaults INSTANCE;
+    field public static final float ScrimOpacity = 0.32f;
   }
 
   public final class DrawerKt {
@@ -306,10 +367,16 @@
     enum_constant public static final androidx.compose.material.DrawerValue Open;
   }
 
-  public final class ElevationConstants {
+  @Deprecated public final class ElevationConstants {
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? incomingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? outgoingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
+    field @Deprecated public static final androidx.compose.material.ElevationConstants INSTANCE;
+  }
+
+  public final class ElevationDefaults {
     method public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? incomingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
     method public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? outgoingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
-    field public static final androidx.compose.material.ElevationConstants INSTANCE;
+    field public static final androidx.compose.material.ElevationDefaults INSTANCE;
   }
 
   public final class ElevationKt {
@@ -335,12 +402,12 @@
   }
 
   @Deprecated public interface EmphasisLevels {
-    method @Deprecated public androidx.compose.material.Emphasis getDisabled();
-    method @Deprecated public androidx.compose.material.Emphasis getHigh();
-    method @Deprecated public androidx.compose.material.Emphasis getMedium();
-    property public abstract androidx.compose.material.Emphasis disabled;
-    property public abstract androidx.compose.material.Emphasis high;
-    property public abstract androidx.compose.material.Emphasis medium;
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getDisabled();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getHigh();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getMedium();
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis disabled;
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis high;
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis medium;
   }
 
   @kotlin.RequiresOptIn(message="This material API is experimental and is likely to change or to be removed in" + " the future.") public @interface ExperimentalMaterialApi {
@@ -356,9 +423,14 @@
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public androidx.compose.material.FixedThreshold copy-0680j_4(float offset);
   }
 
-  public final class FloatingActionButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
-    field public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
+  @Deprecated public final class FloatingActionButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
+    field @Deprecated public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
+  }
+
+  public final class FloatingActionButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
+    field public static final androidx.compose.material.FloatingActionButtonDefaults INSTANCE;
   }
 
   @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface FloatingActionButtonElevation {
@@ -395,12 +467,12 @@
   }
 
   public final class MaterialTheme {
-    method public androidx.compose.material.Colors getColors();
-    method public androidx.compose.material.Shapes getShapes();
-    method public androidx.compose.material.Typography getTypography();
-    property public final androidx.compose.material.Colors colors;
-    property public final androidx.compose.material.Shapes shapes;
-    property public final androidx.compose.material.Typography typography;
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Colors getColors();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Shapes getShapes();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Typography getTypography();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Colors colors;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Shapes shapes;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Typography typography;
     field public static final androidx.compose.material.MaterialTheme INSTANCE;
   }
 
@@ -413,12 +485,20 @@
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  public final class ModalBottomSheetConstants {
-    method public float getDefaultElevation-D9Ej5fM();
-    method public long getDefaultScrimColor-0d7_KjU();
+  @Deprecated public final class ModalBottomSheetConstants {
+    method @Deprecated public float getDefaultElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultScrimColor-0d7_KjU();
     property public final float DefaultElevation;
-    property public final long DefaultScrimColor;
-    field public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+    property @androidx.compose.runtime.Composable public final long DefaultScrimColor;
+    field @Deprecated public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+  }
+
+  public final class ModalBottomSheetDefaults {
+    method public float getElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getScrimColor-0d7_KjU();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long scrimColor;
+    field public static final androidx.compose.material.ModalBottomSheetDefaults INSTANCE;
   }
 
   public final class ModalBottomSheetKt {
@@ -450,13 +530,22 @@
     method @androidx.compose.runtime.Composable public static void OutlinedTextField-IIju55g(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
   }
 
-  public final class ProgressIndicatorConstants {
-    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultProgressAnimationSpec();
-    method public float getDefaultStrokeWidth-D9Ej5fM();
+  @Deprecated public final class ProgressIndicatorConstants {
+    method @Deprecated public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultProgressAnimationSpec();
+    method @Deprecated public float getDefaultStrokeWidth-D9Ej5fM();
     property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> DefaultProgressAnimationSpec;
     property public final float DefaultStrokeWidth;
-    field public static final float DefaultIndicatorBackgroundOpacity = 0.24f;
-    field public static final androidx.compose.material.ProgressIndicatorConstants INSTANCE;
+    field @Deprecated public static final float DefaultIndicatorBackgroundOpacity = 0.24f;
+    field @Deprecated public static final androidx.compose.material.ProgressIndicatorConstants INSTANCE;
+  }
+
+  public final class ProgressIndicatorDefaults {
+    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getProgressAnimationSpec();
+    method public float getStrokeWidth-D9Ej5fM();
+    property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> ProgressAnimationSpec;
+    property public final float StrokeWidth;
+    field public static final androidx.compose.material.ProgressIndicatorDefaults INSTANCE;
+    field public static final float IndicatorBackgroundOpacity = 0.24f;
   }
 
   public final class ProgressIndicatorKt {
@@ -470,9 +559,14 @@
     method public long radioColor-0d7_KjU(boolean enabled, boolean selected);
   }
 
-  public final class RadioButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors defaultColors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
-    field public static final androidx.compose.material.RadioButtonConstants INSTANCE;
+  @Deprecated public final class RadioButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors defaultColors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
+    field @Deprecated public static final androidx.compose.material.RadioButtonConstants INSTANCE;
+  }
+
+  public final class RadioButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors colors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
+    field public static final androidx.compose.material.RadioButtonDefaults INSTANCE;
   }
 
   public final class RadioButtonKt {
@@ -525,8 +619,14 @@
   public final class ShapesKt {
   }
 
-  public final class SliderConstants {
-    field public static final androidx.compose.material.SliderConstants INSTANCE;
+  @Deprecated public final class SliderConstants {
+    field @Deprecated public static final androidx.compose.material.SliderConstants INSTANCE;
+    field @Deprecated public static final float InactiveTrackColorAlpha = 0.24f;
+    field @Deprecated public static final float TickColorAlpha = 0.54f;
+  }
+
+  public final class SliderDefaults {
+    field public static final androidx.compose.material.SliderDefaults INSTANCE;
     field public static final float InactiveTrackColorAlpha = 0.24f;
     field public static final float TickColorAlpha = 0.54f;
   }
@@ -535,12 +635,12 @@
     method @androidx.compose.runtime.Composable public static void Slider-B7rb6FQ(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit> onValueChangeEnd, optional androidx.compose.foundation.InteractionState interactionState, optional long thumbColor, optional long activeTrackColor, optional long inactiveTrackColor, optional long activeTickColor, optional long inactiveTickColor);
   }
 
-  public final class SnackbarConstants {
-    method public long getDefaultActionPrimaryColor-0d7_KjU();
-    method public long getDefaultBackgroundColor-0d7_KjU();
-    property public final long defaultActionPrimaryColor;
-    property public final long defaultBackgroundColor;
-    field public static final androidx.compose.material.SnackbarConstants INSTANCE;
+  @Deprecated public final class SnackbarConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultActionPrimaryColor-0d7_KjU();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultBackgroundColor-0d7_KjU();
+    property @androidx.compose.runtime.Composable public final long defaultActionPrimaryColor;
+    property @androidx.compose.runtime.Composable public final long defaultBackgroundColor;
+    field @Deprecated public static final androidx.compose.material.SnackbarConstants INSTANCE;
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public interface SnackbarData {
@@ -554,6 +654,14 @@
     property public abstract String message;
   }
 
+  public final class SnackbarDefaults {
+    method @androidx.compose.runtime.Composable public long getBackgroundColor-0d7_KjU();
+    method @androidx.compose.runtime.Composable public long getPrimaryActionColor-0d7_KjU();
+    property @androidx.compose.runtime.Composable public final long backgroundColor;
+    property @androidx.compose.runtime.Composable public final long primaryActionColor;
+    field public static final androidx.compose.material.SnackbarDefaults INSTANCE;
+  }
+
   public enum SnackbarDuration {
     enum_constant public static final androidx.compose.material.SnackbarDuration Indefinite;
     enum_constant public static final androidx.compose.material.SnackbarDuration Long;
@@ -605,13 +713,24 @@
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.DismissState rememberDismissState(optional androidx.compose.material.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.DismissValue,java.lang.Boolean> confirmStateChange);
   }
 
-  public final class SwipeableConstants {
-    method public androidx.compose.material.ResistanceConfig? defaultResistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
-    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultAnimationSpec();
-    method public float getDefaultVelocityThreshold-D9Ej5fM();
+  @Deprecated public final class SwipeableConstants {
+    method @Deprecated public androidx.compose.material.ResistanceConfig? defaultResistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
+    method @Deprecated public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultAnimationSpec();
+    method @Deprecated public float getDefaultVelocityThreshold-D9Ej5fM();
     property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> DefaultAnimationSpec;
     property public final float DefaultVelocityThreshold;
-    field public static final androidx.compose.material.SwipeableConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.SwipeableConstants INSTANCE;
+    field @Deprecated public static final float StandardResistanceFactor = 10.0f;
+    field @Deprecated public static final float StiffResistanceFactor = 20.0f;
+  }
+
+  public final class SwipeableDefaults {
+    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getAnimationSpec();
+    method public float getVelocityThreshold-D9Ej5fM();
+    method public androidx.compose.material.ResistanceConfig? resistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
+    property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> AnimationSpec;
+    property public final float VelocityThreshold;
+    field public static final androidx.compose.material.SwipeableDefaults INSTANCE;
     field public static final float StandardResistanceFactor = 10.0f;
     field public static final float StiffResistanceFactor = 20.0f;
   }
@@ -651,27 +770,46 @@
     method public long trackColor-0d7_KjU(boolean enabled, boolean checked);
   }
 
-  public final class SwitchConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
-    field public static final androidx.compose.material.SwitchConstants INSTANCE;
+  @Deprecated public final class SwitchConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field @Deprecated public static final androidx.compose.material.SwitchConstants INSTANCE;
+  }
+
+  public final class SwitchDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors colors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field public static final androidx.compose.material.SwitchDefaults INSTANCE;
   }
 
   public final class SwitchKt {
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.material.SwitchColors colors);
   }
 
-  public final class TabConstants {
-    method @androidx.compose.runtime.Composable public void DefaultDivider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
-    method @androidx.compose.runtime.Composable public void DefaultIndicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
-    method public androidx.compose.ui.Modifier defaultTabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
-    method public float getDefaultDividerThickness-D9Ej5fM();
-    method public float getDefaultIndicatorHeight-D9Ej5fM();
-    method public float getDefaultScrollableTabRowPadding-D9Ej5fM();
+  @Deprecated public final class TabConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public void DefaultDivider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public void DefaultIndicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @Deprecated public androidx.compose.ui.Modifier defaultTabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
+    method @Deprecated public float getDefaultDividerThickness-D9Ej5fM();
+    method @Deprecated public float getDefaultIndicatorHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultScrollableTabRowPadding-D9Ej5fM();
     property public final float DefaultDividerThickness;
     property public final float DefaultIndicatorHeight;
     property public final float DefaultScrollableTabRowPadding;
-    field public static final float DefaultDividerOpacity = 0.12f;
-    field public static final androidx.compose.material.TabConstants INSTANCE;
+    field @Deprecated public static final float DefaultDividerOpacity = 0.12f;
+    field @Deprecated public static final androidx.compose.material.TabConstants INSTANCE;
+  }
+
+  public final class TabDefaults {
+    method @androidx.compose.runtime.Composable public void Divider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @androidx.compose.runtime.Composable public void Indicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method public float getDividerThickness-D9Ej5fM();
+    method public float getIndicatorHeight-D9Ej5fM();
+    method public float getScrollableTabRowPadding-D9Ej5fM();
+    method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
+    property public final float DividerThickness;
+    property public final float IndicatorHeight;
+    property public final float ScrollableTabRowPadding;
+    field public static final float DividerOpacity = 0.12f;
+    field public static final androidx.compose.material.TabDefaults INSTANCE;
   }
 
   public final class TabKt {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index a589728..9034474 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -12,18 +12,32 @@
     method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional float elevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  public final class BackdropScaffoldConstants {
-    method public float getDefaultFrontLayerElevation-D9Ej5fM();
-    method public long getDefaultFrontLayerScrimColor-0d7_KjU();
-    method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
-    method public float getDefaultHeaderHeight-D9Ej5fM();
-    method public float getDefaultPeekHeight-D9Ej5fM();
+  @Deprecated public final class BackdropScaffoldConstants {
+    method @Deprecated public float getDefaultFrontLayerElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultFrontLayerScrimColor-0d7_KjU();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
+    method @Deprecated public float getDefaultHeaderHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultPeekHeight-D9Ej5fM();
     property public final float DefaultFrontLayerElevation;
-    property public final long DefaultFrontLayerScrimColor;
-    property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
+    property @androidx.compose.runtime.Composable public final long DefaultFrontLayerScrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
     property public final float DefaultHeaderHeight;
     property public final float DefaultPeekHeight;
-    field public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.BackdropScaffoldConstants INSTANCE;
+  }
+
+  public final class BackdropScaffoldDefaults {
+    method public float getFrontLayerElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getFrontLayerScrimColor-0d7_KjU();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFrontLayerShape();
+    method public float getHeaderHeight-D9Ej5fM();
+    method public float getPeekHeight-D9Ej5fM();
+    property public final float FrontLayerElevation;
+    property public final float HeaderHeight;
+    property public final float PeekHeight;
+    property @androidx.compose.runtime.Composable public final long frontLayerScrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape frontLayerShape;
+    field public static final androidx.compose.material.BackdropScaffoldDefaults INSTANCE;
   }
 
   public final class BackdropScaffoldKt {
@@ -82,12 +96,20 @@
     method @androidx.compose.runtime.Composable public static void BottomNavigationItem-S1l6qvI(kotlin.jvm.functions.Function0<kotlin.Unit> icon, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> label, optional boolean alwaysShowLabels, optional androidx.compose.foundation.InteractionState interactionState, optional long selectedContentColor, optional long unselectedContentColor);
   }
 
-  public final class BottomSheetScaffoldConstants {
-    method public float getDefaultSheetElevation-D9Ej5fM();
-    method public float getDefaultSheetPeekHeight-D9Ej5fM();
+  @Deprecated public final class BottomSheetScaffoldConstants {
+    method @Deprecated public float getDefaultSheetElevation-D9Ej5fM();
+    method @Deprecated public float getDefaultSheetPeekHeight-D9Ej5fM();
     property public final float DefaultSheetElevation;
     property public final float DefaultSheetPeekHeight;
-    field public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.BottomSheetScaffoldConstants INSTANCE;
+  }
+
+  public final class BottomSheetScaffoldDefaults {
+    method public float getSheetElevation-D9Ej5fM();
+    method public float getSheetPeekHeight-D9Ej5fM();
+    property public final float SheetElevation;
+    property public final float SheetPeekHeight;
+    field public static final androidx.compose.material.BottomSheetScaffoldDefaults INSTANCE;
   }
 
   public final class BottomSheetScaffoldKt {
@@ -131,19 +153,19 @@
     method public long contentColor-0d7_KjU(boolean enabled);
   }
 
-  public final class ButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultButtonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation defaultElevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultOutlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultTextButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
-    method public androidx.compose.foundation.layout.PaddingValues getDefaultContentPadding();
-    method public float getDefaultIconSize-D9Ej5fM();
-    method public float getDefaultIconSpacing-D9Ej5fM();
-    method public float getDefaultMinHeight-D9Ej5fM();
-    method public float getDefaultMinWidth-D9Ej5fM();
-    method public androidx.compose.foundation.BorderStroke getDefaultOutlinedBorder();
-    method public androidx.compose.foundation.layout.PaddingValues getDefaultTextContentPadding();
-    method public float getOutlinedBorderSize-D9Ej5fM();
+  @Deprecated public final class ButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultButtonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation defaultElevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultOutlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors defaultTextButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @Deprecated public androidx.compose.foundation.layout.PaddingValues getDefaultContentPadding();
+    method @Deprecated public float getDefaultIconSize-D9Ej5fM();
+    method @Deprecated public float getDefaultIconSpacing-D9Ej5fM();
+    method @Deprecated public float getDefaultMinHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultMinWidth-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke getDefaultOutlinedBorder();
+    method @Deprecated public androidx.compose.foundation.layout.PaddingValues getDefaultTextContentPadding();
+    method @Deprecated public float getOutlinedBorderSize-D9Ej5fM();
     property public final androidx.compose.foundation.layout.PaddingValues DefaultContentPadding;
     property public final float DefaultIconSize;
     property public final float DefaultIconSpacing;
@@ -151,8 +173,33 @@
     property public final float DefaultMinWidth;
     property public final androidx.compose.foundation.layout.PaddingValues DefaultTextContentPadding;
     property public final float OutlinedBorderSize;
-    property public final androidx.compose.foundation.BorderStroke defaultOutlinedBorder;
-    field public static final androidx.compose.material.ButtonConstants INSTANCE;
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.BorderStroke defaultOutlinedBorder;
+    field @Deprecated public static final androidx.compose.material.ButtonConstants INSTANCE;
+    field @Deprecated public static final float OutlinedBorderOpacity = 0.12f;
+  }
+
+  public final class ButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors buttonColors-nlx5xbs(optional long backgroundColor, optional long disabledBackgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonElevation elevation-qYQSm_w(optional float defaultElevation, optional float pressedElevation, optional float disabledElevation);
+    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method public float getIconSize-D9Ej5fM();
+    method public float getIconSpacing-D9Ej5fM();
+    method public float getMinHeight-D9Ej5fM();
+    method public float getMinWidth-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke getOutlinedBorder();
+    method public float getOutlinedBorderSize-D9Ej5fM();
+    method public androidx.compose.foundation.layout.PaddingValues getTextButtonContentPadding();
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors outlinedButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.material.ButtonColors textButtonColors-xS_xkl8(optional long backgroundColor, optional long contentColor, optional long disabledContentColor);
+    property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
+    property public final float IconSize;
+    property public final float IconSpacing;
+    property public final float MinHeight;
+    property public final float MinWidth;
+    property public final float OutlinedBorderSize;
+    property public final androidx.compose.foundation.layout.PaddingValues TextButtonContentPadding;
+    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.BorderStroke outlinedBorder;
+    field public static final androidx.compose.material.ButtonDefaults INSTANCE;
     field public static final float OutlinedBorderOpacity = 0.12f;
   }
 
@@ -176,9 +223,14 @@
     method public long checkmarkColor-0d7_KjU(androidx.compose.ui.state.ToggleableState state);
   }
 
-  public final class CheckboxConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors defaultColors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
-    field public static final androidx.compose.material.CheckboxConstants INSTANCE;
+  @Deprecated public final class CheckboxConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors defaultColors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
+    field @Deprecated public static final androidx.compose.material.CheckboxConstants INSTANCE;
+  }
+
+  public final class CheckboxDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.CheckboxColors colors-QGkLkJU(optional long checkedColor, optional long uncheckedColor, optional long checkmarkColor, optional long disabledColor, optional long disabledIndeterminateColor);
+    field public static final androidx.compose.material.CheckboxDefaults INSTANCE;
   }
 
   public final class CheckboxKt {
@@ -224,12 +276,12 @@
   }
 
   public final class ContentAlpha {
-    method public float getDisabled();
-    method public float getHigh();
-    method public float getMedium();
-    property public final float disabled;
-    property public final float high;
-    property public final float medium;
+    method @androidx.compose.runtime.Composable public float getDisabled();
+    method @androidx.compose.runtime.Composable public float getHigh();
+    method @androidx.compose.runtime.Composable public float getMedium();
+    property @androidx.compose.runtime.Composable public final float disabled;
+    property @androidx.compose.runtime.Composable public final float high;
+    property @androidx.compose.runtime.Composable public final float medium;
     field public static final androidx.compose.material.ContentAlpha INSTANCE;
   }
 
@@ -270,13 +322,22 @@
     method @androidx.compose.runtime.Composable public static void Divider-JRSVyrs(optional androidx.compose.ui.Modifier modifier, optional long color, optional float thickness, optional float startIndent);
   }
 
-  public final class DrawerConstants {
-    method public float getDefaultElevation-D9Ej5fM();
-    method public long getDefaultScrimColor-0d7_KjU();
+  @Deprecated public final class DrawerConstants {
+    method @Deprecated public float getDefaultElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultScrimColor-0d7_KjU();
     property public final float DefaultElevation;
-    property public final long defaultScrimColor;
-    field public static final androidx.compose.material.DrawerConstants INSTANCE;
-    field public static final float ScrimDefaultOpacity = 0.32f;
+    property @androidx.compose.runtime.Composable public final long defaultScrimColor;
+    field @Deprecated public static final androidx.compose.material.DrawerConstants INSTANCE;
+    field @Deprecated public static final float ScrimDefaultOpacity = 0.32f;
+  }
+
+  public final class DrawerDefaults {
+    method public float getElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getScrimColor-0d7_KjU();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long scrimColor;
+    field public static final androidx.compose.material.DrawerDefaults INSTANCE;
+    field public static final float ScrimOpacity = 0.32f;
   }
 
   public final class DrawerKt {
@@ -306,10 +367,16 @@
     enum_constant public static final androidx.compose.material.DrawerValue Open;
   }
 
-  public final class ElevationConstants {
+  @Deprecated public final class ElevationConstants {
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? incomingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
+    method @Deprecated public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? outgoingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
+    field @Deprecated public static final androidx.compose.material.ElevationConstants INSTANCE;
+  }
+
+  public final class ElevationDefaults {
     method public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? incomingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
     method public androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>? outgoingAnimationSpecForInteraction(androidx.compose.foundation.Interaction interaction);
-    field public static final androidx.compose.material.ElevationConstants INSTANCE;
+    field public static final androidx.compose.material.ElevationDefaults INSTANCE;
   }
 
   public final class ElevationKt {
@@ -335,12 +402,12 @@
   }
 
   @Deprecated public interface EmphasisLevels {
-    method @Deprecated public androidx.compose.material.Emphasis getDisabled();
-    method @Deprecated public androidx.compose.material.Emphasis getHigh();
-    method @Deprecated public androidx.compose.material.Emphasis getMedium();
-    property public abstract androidx.compose.material.Emphasis disabled;
-    property public abstract androidx.compose.material.Emphasis high;
-    property public abstract androidx.compose.material.Emphasis medium;
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getDisabled();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getHigh();
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.Emphasis getMedium();
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis disabled;
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis high;
+    property @androidx.compose.runtime.Composable public abstract androidx.compose.material.Emphasis medium;
   }
 
   @kotlin.RequiresOptIn(message="This material API is experimental and is likely to change or to be removed in" + " the future.") public @interface ExperimentalMaterialApi {
@@ -356,9 +423,14 @@
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Immutable public androidx.compose.material.FixedThreshold copy-0680j_4(float offset);
   }
 
-  public final class FloatingActionButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
-    field public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
+  @Deprecated public final class FloatingActionButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation defaultElevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
+    field @Deprecated public static final androidx.compose.material.FloatingActionButtonConstants INSTANCE;
+  }
+
+  public final class FloatingActionButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.FloatingActionButtonElevation elevation-ioHfwGI(optional float defaultElevation, optional float pressedElevation);
+    field public static final androidx.compose.material.FloatingActionButtonDefaults INSTANCE;
   }
 
   @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface FloatingActionButtonElevation {
@@ -395,12 +467,12 @@
   }
 
   public final class MaterialTheme {
-    method public androidx.compose.material.Colors getColors();
-    method public androidx.compose.material.Shapes getShapes();
-    method public androidx.compose.material.Typography getTypography();
-    property public final androidx.compose.material.Colors colors;
-    property public final androidx.compose.material.Shapes shapes;
-    property public final androidx.compose.material.Typography typography;
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Colors getColors();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Shapes getShapes();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public androidx.compose.material.Typography getTypography();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Colors colors;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Shapes shapes;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final androidx.compose.material.Typography typography;
     field public static final androidx.compose.material.MaterialTheme INSTANCE;
   }
 
@@ -413,12 +485,20 @@
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  public final class ModalBottomSheetConstants {
-    method public float getDefaultElevation-D9Ej5fM();
-    method public long getDefaultScrimColor-0d7_KjU();
+  @Deprecated public final class ModalBottomSheetConstants {
+    method @Deprecated public float getDefaultElevation-D9Ej5fM();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultScrimColor-0d7_KjU();
     property public final float DefaultElevation;
-    property public final long DefaultScrimColor;
-    field public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+    property @androidx.compose.runtime.Composable public final long DefaultScrimColor;
+    field @Deprecated public static final androidx.compose.material.ModalBottomSheetConstants INSTANCE;
+  }
+
+  public final class ModalBottomSheetDefaults {
+    method public float getElevation-D9Ej5fM();
+    method @androidx.compose.runtime.Composable public long getScrimColor-0d7_KjU();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long scrimColor;
+    field public static final androidx.compose.material.ModalBottomSheetDefaults INSTANCE;
   }
 
   public final class ModalBottomSheetKt {
@@ -450,13 +530,22 @@
     method @androidx.compose.runtime.Composable public static void OutlinedTextField-IIju55g(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
   }
 
-  public final class ProgressIndicatorConstants {
-    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultProgressAnimationSpec();
-    method public float getDefaultStrokeWidth-D9Ej5fM();
+  @Deprecated public final class ProgressIndicatorConstants {
+    method @Deprecated public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultProgressAnimationSpec();
+    method @Deprecated public float getDefaultStrokeWidth-D9Ej5fM();
     property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> DefaultProgressAnimationSpec;
     property public final float DefaultStrokeWidth;
-    field public static final float DefaultIndicatorBackgroundOpacity = 0.24f;
-    field public static final androidx.compose.material.ProgressIndicatorConstants INSTANCE;
+    field @Deprecated public static final float DefaultIndicatorBackgroundOpacity = 0.24f;
+    field @Deprecated public static final androidx.compose.material.ProgressIndicatorConstants INSTANCE;
+  }
+
+  public final class ProgressIndicatorDefaults {
+    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getProgressAnimationSpec();
+    method public float getStrokeWidth-D9Ej5fM();
+    property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> ProgressAnimationSpec;
+    property public final float StrokeWidth;
+    field public static final androidx.compose.material.ProgressIndicatorDefaults INSTANCE;
+    field public static final float IndicatorBackgroundOpacity = 0.24f;
   }
 
   public final class ProgressIndicatorKt {
@@ -470,9 +559,14 @@
     method public long radioColor-0d7_KjU(boolean enabled, boolean selected);
   }
 
-  public final class RadioButtonConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors defaultColors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
-    field public static final androidx.compose.material.RadioButtonConstants INSTANCE;
+  @Deprecated public final class RadioButtonConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors defaultColors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
+    field @Deprecated public static final androidx.compose.material.RadioButtonConstants INSTANCE;
+  }
+
+  public final class RadioButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.RadioButtonColors colors-xS_xkl8(optional long selectedColor, optional long unselectedColor, optional long disabledColor);
+    field public static final androidx.compose.material.RadioButtonDefaults INSTANCE;
   }
 
   public final class RadioButtonKt {
@@ -525,8 +619,14 @@
   public final class ShapesKt {
   }
 
-  public final class SliderConstants {
-    field public static final androidx.compose.material.SliderConstants INSTANCE;
+  @Deprecated public final class SliderConstants {
+    field @Deprecated public static final androidx.compose.material.SliderConstants INSTANCE;
+    field @Deprecated public static final float InactiveTrackColorAlpha = 0.24f;
+    field @Deprecated public static final float TickColorAlpha = 0.54f;
+  }
+
+  public final class SliderDefaults {
+    field public static final androidx.compose.material.SliderDefaults INSTANCE;
     field public static final float InactiveTrackColorAlpha = 0.24f;
     field public static final float TickColorAlpha = 0.54f;
   }
@@ -535,12 +635,12 @@
     method @androidx.compose.runtime.Composable public static void Slider-B7rb6FQ(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit> onValueChangeEnd, optional androidx.compose.foundation.InteractionState interactionState, optional long thumbColor, optional long activeTrackColor, optional long inactiveTrackColor, optional long activeTickColor, optional long inactiveTickColor);
   }
 
-  public final class SnackbarConstants {
-    method public long getDefaultActionPrimaryColor-0d7_KjU();
-    method public long getDefaultBackgroundColor-0d7_KjU();
-    property public final long defaultActionPrimaryColor;
-    property public final long defaultBackgroundColor;
-    field public static final androidx.compose.material.SnackbarConstants INSTANCE;
+  @Deprecated public final class SnackbarConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultActionPrimaryColor-0d7_KjU();
+    method @Deprecated @androidx.compose.runtime.Composable public long getDefaultBackgroundColor-0d7_KjU();
+    property @androidx.compose.runtime.Composable public final long defaultActionPrimaryColor;
+    property @androidx.compose.runtime.Composable public final long defaultBackgroundColor;
+    field @Deprecated public static final androidx.compose.material.SnackbarConstants INSTANCE;
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public interface SnackbarData {
@@ -554,6 +654,14 @@
     property public abstract String message;
   }
 
+  public final class SnackbarDefaults {
+    method @androidx.compose.runtime.Composable public long getBackgroundColor-0d7_KjU();
+    method @androidx.compose.runtime.Composable public long getPrimaryActionColor-0d7_KjU();
+    property @androidx.compose.runtime.Composable public final long backgroundColor;
+    property @androidx.compose.runtime.Composable public final long primaryActionColor;
+    field public static final androidx.compose.material.SnackbarDefaults INSTANCE;
+  }
+
   public enum SnackbarDuration {
     enum_constant public static final androidx.compose.material.SnackbarDuration Indefinite;
     enum_constant public static final androidx.compose.material.SnackbarDuration Long;
@@ -605,13 +713,24 @@
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.DismissState rememberDismissState(optional androidx.compose.material.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.DismissValue,java.lang.Boolean> confirmStateChange);
   }
 
-  public final class SwipeableConstants {
-    method public androidx.compose.material.ResistanceConfig? defaultResistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
-    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultAnimationSpec();
-    method public float getDefaultVelocityThreshold-D9Ej5fM();
+  @Deprecated public final class SwipeableConstants {
+    method @Deprecated public androidx.compose.material.ResistanceConfig? defaultResistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
+    method @Deprecated public androidx.compose.animation.core.SpringSpec<java.lang.Float> getDefaultAnimationSpec();
+    method @Deprecated public float getDefaultVelocityThreshold-D9Ej5fM();
     property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> DefaultAnimationSpec;
     property public final float DefaultVelocityThreshold;
-    field public static final androidx.compose.material.SwipeableConstants INSTANCE;
+    field @Deprecated public static final androidx.compose.material.SwipeableConstants INSTANCE;
+    field @Deprecated public static final float StandardResistanceFactor = 10.0f;
+    field @Deprecated public static final float StiffResistanceFactor = 20.0f;
+  }
+
+  public final class SwipeableDefaults {
+    method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getAnimationSpec();
+    method public float getVelocityThreshold-D9Ej5fM();
+    method public androidx.compose.material.ResistanceConfig? resistanceConfig(java.util.Set<java.lang.Float> anchors, optional float factorAtMin, optional float factorAtMax);
+    property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> AnimationSpec;
+    property public final float VelocityThreshold;
+    field public static final androidx.compose.material.SwipeableDefaults INSTANCE;
     field public static final float StandardResistanceFactor = 10.0f;
     field public static final float StiffResistanceFactor = 20.0f;
   }
@@ -651,27 +770,46 @@
     method public long trackColor-0d7_KjU(boolean enabled, boolean checked);
   }
 
-  public final class SwitchConstants {
-    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
-    field public static final androidx.compose.material.SwitchConstants INSTANCE;
+  @Deprecated public final class SwitchConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors defaultColors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field @Deprecated public static final androidx.compose.material.SwitchConstants INSTANCE;
+  }
+
+  public final class SwitchDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material.SwitchColors colors-R8aI8sA(optional long checkedThumbColor, optional long checkedTrackColor, optional float checkedTrackAlpha, optional long uncheckedThumbColor, optional long uncheckedTrackColor, optional float uncheckedTrackAlpha, optional long disabledCheckedThumbColor, optional long disabledCheckedTrackColor, optional long disabledUncheckedThumbColor, optional long disabledUncheckedTrackColor);
+    field public static final androidx.compose.material.SwitchDefaults INSTANCE;
   }
 
   public final class SwitchKt {
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.material.SwitchColors colors);
   }
 
-  public final class TabConstants {
-    method @androidx.compose.runtime.Composable public void DefaultDivider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
-    method @androidx.compose.runtime.Composable public void DefaultIndicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
-    method public androidx.compose.ui.Modifier defaultTabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
-    method public float getDefaultDividerThickness-D9Ej5fM();
-    method public float getDefaultIndicatorHeight-D9Ej5fM();
-    method public float getDefaultScrollableTabRowPadding-D9Ej5fM();
+  @Deprecated public final class TabConstants {
+    method @Deprecated @androidx.compose.runtime.Composable public void DefaultDivider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @Deprecated @androidx.compose.runtime.Composable public void DefaultIndicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method @Deprecated public androidx.compose.ui.Modifier defaultTabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
+    method @Deprecated public float getDefaultDividerThickness-D9Ej5fM();
+    method @Deprecated public float getDefaultIndicatorHeight-D9Ej5fM();
+    method @Deprecated public float getDefaultScrollableTabRowPadding-D9Ej5fM();
     property public final float DefaultDividerThickness;
     property public final float DefaultIndicatorHeight;
     property public final float DefaultScrollableTabRowPadding;
-    field public static final float DefaultDividerOpacity = 0.12f;
-    field public static final androidx.compose.material.TabConstants INSTANCE;
+    field @Deprecated public static final float DefaultDividerOpacity = 0.12f;
+    field @Deprecated public static final androidx.compose.material.TabConstants INSTANCE;
+  }
+
+  public final class TabDefaults {
+    method @androidx.compose.runtime.Composable public void Divider-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float thickness, optional long color);
+    method @androidx.compose.runtime.Composable public void Indicator-Z-uBYeE(optional androidx.compose.ui.Modifier modifier, optional float height, optional long color);
+    method public float getDividerThickness-D9Ej5fM();
+    method public float getIndicatorHeight-D9Ej5fM();
+    method public float getScrollableTabRowPadding-D9Ej5fM();
+    method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, androidx.compose.material.TabPosition currentTabPosition);
+    property public final float DividerThickness;
+    property public final float IndicatorHeight;
+    property public final float ScrollableTabRowPadding;
+    field public static final float DividerOpacity = 0.12f;
+    field public static final androidx.compose.material.TabDefaults INSTANCE;
   }
 
   public final class TabKt {
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
index db79704..cf2c613 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
@@ -28,7 +28,7 @@
 import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.foundation.shape.GenericShape
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.OutlinedButton
 import androidx.compose.material.Text
@@ -78,7 +78,7 @@
     Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
         Button(
             onClick = {},
-            colors = ButtonConstants.defaultButtonColors(
+            colors = ButtonDefaults.buttonColors(
                 backgroundColor = MaterialTheme.colors.secondary
             )
         ) {
@@ -135,7 +135,7 @@
         onClick = {},
         modifier = Modifier.preferredSize(110.dp),
         shape = TriangleShape,
-        colors = ButtonConstants.defaultOutlinedButtonColors(
+        colors = ButtonDefaults.outlinedButtonColors(
             backgroundColor = Color.Yellow
         ),
         border = BorderStroke(width = 2.dp, color = Color.Black)
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ListItemDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ListItemDemo.kt
index 9b53c84..f0af72f 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ListItemDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ListItemDemo.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.ScrollableColumn
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Call
+import androidx.compose.material.samples.ClickableListItems
 import androidx.compose.material.samples.OneLineListItems
 import androidx.compose.material.samples.OneLineRtlLtrListItems
 import androidx.compose.material.samples.ThreeLineListItems
@@ -35,6 +36,7 @@
     val icon56 = imageResource(R.drawable.ic_android)
     val vectorIcon = Icons.Default.Call
     ScrollableColumn {
+        ClickableListItems()
         OneLineListItems(icon24, icon40, icon56, vectorIcon)
         TwoLineListItems(icon24, icon40)
         ThreeLineListItems(icon24, vectorIcon)
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
index ddbfc47..0dcc59c 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
@@ -85,7 +85,7 @@
         expanded = expanded,
         onDismissRequest = { expanded = false },
         toggle = iconButton,
-        dropdownOffset = Position(-12.dp, -12.dp),
+        dropdownOffset = Position(24.dp, 0.dp),
         toggleModifier = modifier
     ) {
         options.forEach {
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TabDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TabDemo.kt
index 1d503d0..cb37e1c 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TabDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/TabDemo.kt
@@ -21,7 +21,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.preferredHeight
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Text
 import androidx.compose.material.samples.FancyIndicatorContainerTabs
 import androidx.compose.material.samples.FancyIndicatorTabs
@@ -69,7 +69,7 @@
             onClick = {
                 showingSimple.value = !showingSimple.value
             },
-            colors = ButtonConstants.defaultButtonColors(backgroundColor = Color.Cyan)
+            colors = ButtonDefaults.buttonColors(backgroundColor = Color.Cyan)
         ) {
             Text(buttonText)
         }
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt
index e2ac96e..12cfe41 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ButtonSamples.kt
@@ -20,7 +20,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Icon
 import androidx.compose.material.OutlinedButton
 import androidx.compose.material.Text
@@ -58,8 +58,8 @@
 @Composable
 fun ButtonWithIconSample() {
     Button(onClick = { /* Do something! */ }) {
-        Icon(Icons.Filled.Favorite, Modifier.size(ButtonConstants.DefaultIconSize))
-        Spacer(Modifier.size(ButtonConstants.DefaultIconSpacing))
+        Icon(Icons.Filled.Favorite, Modifier.size(ButtonDefaults.IconSize))
+        Spacer(Modifier.size(ButtonDefaults.IconSpacing))
         Text("Like")
     }
 }
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
index efb6f3c..88f67fd 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
@@ -19,12 +19,16 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.selection.toggleable
 import androidx.compose.material.AmbientContentColor
 import androidx.compose.material.Checkbox
 import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Icon
 import androidx.compose.material.ListItem
+import androidx.compose.material.Switch
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -35,6 +39,57 @@
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.semantics.clearAndSetSemantics
+
+@Sampled
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun ClickableListItems() {
+    Column {
+        var switched by remember { mutableStateOf(false) }
+        val onSwitchedChange: (Boolean) -> Unit = { switched = it }
+        ListItem(
+            text = { Text("Switch ListItem") },
+            trailing = {
+                // The [clearAndSetSemantics] causes the switch's redundant
+                // toggleable semantics to be cleared in favor of the [ListItem]
+                // toggleable's, to improve usability with screen-readers.
+                Box(Modifier.clearAndSetSemantics {}) {
+                    Switch(
+                        checked = switched,
+                        onCheckedChange = onSwitchedChange
+                    )
+                }
+            },
+            modifier = Modifier.toggleable(
+                value = switched,
+                onValueChange = onSwitchedChange
+            )
+        )
+        Divider()
+        var checked by remember { mutableStateOf(true) }
+        val onCheckedChange: (Boolean) -> Unit = { checked = it }
+        ListItem(
+            text = { Text("Checkbox ListItem") },
+            trailing = {
+                // The [clearAndSetSemantics] causes the checkbox's redundant
+                // toggleable semantics to be cleared in favor of the [ListItem]
+                // toggleable's, to improve usability with screen-readers.
+                Box(Modifier.clearAndSetSemantics {}) {
+                    Checkbox(
+                        checked = checked,
+                        onCheckedChange = onCheckedChange
+                    )
+                }
+            },
+            modifier = Modifier.toggleable(
+                value = checked,
+                onValueChange = onCheckedChange
+            )
+        )
+        Divider()
+    }
+}
 
 @Sampled
 @Composable
@@ -131,21 +186,6 @@
             }
         )
         Divider()
-        var checked by remember { mutableStateOf(false) }
-        ListItem(
-            text = { Text("Two line list item") },
-            secondaryText = { Text("Secondary text") },
-            icon = {
-                Image(
-                    icon40x40,
-                    colorFilter = ColorFilter.tint(AmbientContentColor.current)
-                )
-            },
-            trailing = {
-                Checkbox(checked, onCheckedChange = { checked = !checked })
-            }
-        )
-        Divider()
     }
 }
 
@@ -266,22 +306,6 @@
         )
         Divider()
         ListItem(
-            text = { Text("Clickable two line item") },
-            secondaryText = { Text("Secondary text") },
-            icon = {
-                Image(
-                    icon40x40,
-                    colorFilter = ColorFilter.tint(AmbientContentColor.current)
-                )
-            },
-            trailing = {
-                var checked by remember { mutableStateOf(false) }
-                Checkbox(checked, onCheckedChange = { checked = !checked })
-            },
-            modifier = Modifier.clickable { }
-        )
-        Divider()
-        ListItem(
             text = { Text("بندان قابلان للنقر") },
             secondaryText = { Text("نص ثانوي") },
             icon = {
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
index 8c81625..ab3277e 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
@@ -24,7 +24,7 @@
 import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.material.LinearProgressIndicator
 import androidx.compose.material.OutlinedButton
-import androidx.compose.material.ProgressIndicatorConstants
+import androidx.compose.material.ProgressIndicatorDefaults
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -41,7 +41,7 @@
     var progress by remember { mutableStateOf(0.1f) }
     val animatedProgress = animate(
         target = progress,
-        animSpec = ProgressIndicatorConstants.DefaultProgressAnimationSpec
+        animSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
     )
 
     Column(horizontalAlignment = Alignment.CenterHorizontally) {
@@ -63,7 +63,7 @@
     var progress by remember { mutableStateOf(0.1f) }
     val animatedProgress = animate(
         target = progress,
-        animSpec = ProgressIndicatorConstants.DefaultProgressAnimationSpec
+        animSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
     )
 
     Column(horizontalAlignment = Alignment.CenterHorizontally) {
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SelectionControlsSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SelectionControlsSamples.kt
index b4664b6..67c5f45 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SelectionControlsSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SelectionControlsSamples.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -24,7 +25,7 @@
 import androidx.compose.foundation.layout.preferredHeight
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.material.Checkbox
-import androidx.compose.material.CheckboxConstants
+import androidx.compose.material.CheckboxDefaults
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.RadioButton
 import androidx.compose.material.Switch
@@ -37,6 +38,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.unit.dp
 
@@ -64,7 +66,7 @@
         TriStateCheckbox(
             state = parentState,
             onClick = onParentClick,
-            colors = CheckboxConstants.defaultColors(
+            colors = CheckboxDefaults.colors(
                 checkedColor = MaterialTheme.colors.primary
             )
         )
@@ -130,10 +132,15 @@
                     .padding(horizontal = 16.dp),
                 verticalAlignment = Alignment.CenterVertically
             ) {
-                RadioButton(
-                    selected = (text == selectedOption),
-                    onClick = { onOptionSelected(text) }
-                )
+                // The [clearAndSetSemantics] causes the button's redundant
+                // selectable semantics to be cleared in favor of the [Row]
+                // selectable's, to improve usability with screen-readers.
+                Box(Modifier.clearAndSetSemantics {}) {
+                    RadioButton(
+                        selected = (text == selectedOption),
+                        onClick = { onOptionSelected(text) }
+                    )
+                }
                 Text(
                     text = text,
                     style = MaterialTheme.typography.body1.merge(),
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
index b76d5a8..a6b9745 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TabSamples.kt
@@ -41,7 +41,7 @@
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.ScrollableTabRow
 import androidx.compose.material.Tab
-import androidx.compose.material.TabConstants.defaultTabIndicatorOffset
+import androidx.compose.material.TabDefaults.tabIndicatorOffset
 import androidx.compose.material.TabPosition
 import androidx.compose.material.TabRow
 import androidx.compose.material.Text
@@ -189,7 +189,7 @@
 
     // Reuse the default offset animation modifier, but use our own indicator
     val indicator = @Composable { tabPositions: List<TabPosition> ->
-        FancyIndicator(Color.White, Modifier.defaultTabIndicatorOffset(tabPositions[state]))
+        FancyIndicator(Color.White, Modifier.tabIndicatorOffset(tabPositions[state]))
     }
 
     Column {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt
index 03c84ef..452942f 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt
@@ -395,7 +395,7 @@
                     Button(
                         onClick = {},
                         enabled = false,
-                        colors = ButtonConstants.defaultButtonColors(
+                        colors = ButtonDefaults.buttonColors(
                             backgroundColor = Color.Red
                         ),
                         shape = RectangleShape
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
index f81b3d9..9f831e1 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
@@ -182,7 +182,7 @@
                     FloatingActionButton(
                         modifier = Modifier.testTag("myButton"),
                         onClick = {},
-                        elevation = FloatingActionButtonConstants.defaultElevation(
+                        elevation = FloatingActionButtonDefaults.elevation(
                             defaultElevation = 0.dp
                         )
                     ) {
@@ -219,7 +219,7 @@
                     ExtendedFloatingActionButton(
                         modifier = Modifier.testTag("myButton"),
                         onClick = {},
-                        elevation = FloatingActionButtonConstants.defaultElevation(
+                        elevation = FloatingActionButtonDefaults.elevation(
                             defaultElevation = 0.dp
                         ),
                         text = { Box(Modifier.preferredSize(10.dp, 50.dp)) }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt
index 64e7730..aff45d5 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt
@@ -144,7 +144,7 @@
         )
 
         assertThat(ltrPosition.x).isEqualTo(
-            anchorPosition.x + anchorSize.width + offsetX
+            anchorPosition.x + offsetX
         )
         assertThat(ltrPosition.y).isEqualTo(
             anchorPosition.y + anchorSize.height + offsetY
@@ -161,7 +161,7 @@
         )
 
         assertThat(rtlPosition.x).isEqualTo(
-            anchorPosition.x - popupSize.width - offsetX
+            anchorPosition.x + anchorSize.width - offsetX - popupSize.width
         )
         assertThat(rtlPosition.y).isEqualTo(
             anchorPosition.y + anchorSize.height + offsetY
@@ -192,7 +192,7 @@
         )
 
         assertThat(ltrPosition.x).isEqualTo(
-            anchorPosition.x - popupSize.width - offsetX
+            anchorPosition.x + anchorSize.width - offsetX - popupSize.width
         )
         assertThat(ltrPosition.y).isEqualTo(
             anchorPosition.y - popupSize.height - offsetY
@@ -209,7 +209,7 @@
         )
 
         assertThat(rtlPosition.x).isEqualTo(
-            anchorPositionRtl.x + anchorSize.width + offsetX
+            anchorPositionRtl.x + offsetX
         )
         assertThat(rtlPosition.y).isEqualTo(
             anchorPositionRtl.y - popupSize.height - offsetY
@@ -275,9 +275,9 @@
         assertThat(obtainedParentBounds).isEqualTo(IntBounds(anchorPosition, anchorSize))
         assertThat(obtainedMenuBounds).isEqualTo(
             IntBounds(
-                anchorPosition.x + anchorSize.width + offsetX,
+                anchorPosition.x + offsetX,
                 anchorPosition.y + anchorSize.height + offsetY,
-                anchorPosition.x + anchorSize.width + offsetX + popupSize.width,
+                anchorPosition.x + offsetX + popupSize.width,
                 anchorPosition.y + anchorSize.height + offsetY + popupSize.height
             )
         )
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
index 9cf48ed..9b05fd8 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
@@ -98,7 +98,7 @@
                 Switch(
                     checked = true,
                     onCheckedChange = { },
-                    colors = SwitchConstants.defaultColors(checkedThumbColor = Color.Red)
+                    colors = SwitchDefaults.colors(checkedThumbColor = Color.Red)
                 )
             }
         }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
index a10d1cb..bd3d988 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
@@ -19,7 +19,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.preferredHeight
-import androidx.compose.material.TabConstants.defaultTabIndicatorOffset
+import androidx.compose.material.TabDefaults.tabIndicatorOffset
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.samples.ScrollingTextTabs
@@ -124,7 +124,7 @@
             val indicator = @Composable { tabPositions: List<TabPosition> ->
                 Box(
                     Modifier
-                        .defaultTabIndicatorOffset(tabPositions[state])
+                        .tabIndicatorOffset(tabPositions[state])
                         .fillMaxWidth()
                         .preferredHeight(indicatorHeight)
                         .background(color = Color.Red)
@@ -299,7 +299,7 @@
             val indicator = @Composable { tabPositions: List<TabPosition> ->
                 Box(
                     Modifier
-                        .defaultTabIndicatorOffset(tabPositions[state])
+                        .tabIndicatorOffset(tabPositions[state])
                         .fillMaxWidth()
                         .preferredHeight(indicatorHeight)
                         .background(color = Color.Red)
@@ -330,7 +330,7 @@
         rule.onNodeWithTag("indicator")
             .assertPositionInRootIsEqualTo(
                 // Tabs in a scrollable tab row are offset 52.dp from each end
-                expectedLeft = TabConstants.DefaultScrollableTabRowPadding,
+                expectedLeft = TabDefaults.ScrollableTabRowPadding,
                 expectedTop = tabRowBounds.height - indicatorHeight
             )
 
@@ -341,7 +341,7 @@
         // should be in the middle of the TabRow
         rule.onNodeWithTag("indicator")
             .assertPositionInRootIsEqualTo(
-                expectedLeft = TabConstants.DefaultScrollableTabRowPadding + minimumTabWidth,
+                expectedLeft = TabDefaults.ScrollableTabRowPadding + minimumTabWidth,
                 expectedTop = tabRowBounds.height - indicatorHeight
             )
     }
@@ -446,8 +446,8 @@
     fun testInspectorValue() {
         val pos = TabPosition(10.0.dp, 200.0.dp)
         rule.setContent {
-            val modifier = Modifier.defaultTabIndicatorOffset(pos) as InspectableValue
-            assertThat(modifier.nameFallback).isEqualTo("defaultTabIndicatorOffset")
+            val modifier = Modifier.tabIndicatorOffset(pos) as InspectableValue
+            assertThat(modifier.nameFallback).isEqualTo("tabIndicatorOffset")
             assertThat(modifier.valueOverride).isEqualTo(pos)
             assertThat(modifier.inspectableElements.asIterable()).isEmpty()
         }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
index 4d4bad92..540685c 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
@@ -86,7 +86,7 @@
 class BackdropScaffoldState(
     initialValue: BackdropValue,
     clock: AnimationClockObservable,
-    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
     confirmStateChange: (BackdropValue) -> Boolean = { true },
     val snackbarHostState: SnackbarHostState = SnackbarHostState()
 ) : SwipeableState<BackdropValue>(
@@ -177,7 +177,7 @@
 fun rememberBackdropScaffoldState(
     initialValue: BackdropValue,
     clock: AnimationClockObservable = AmbientAnimationClock.current,
-    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
     confirmStateChange: (BackdropValue) -> Boolean = { true },
     snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
 ): BackdropScaffoldState {
@@ -268,17 +268,17 @@
     modifier: Modifier = Modifier,
     scaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(Concealed),
     gesturesEnabled: Boolean = true,
-    peekHeight: Dp = BackdropScaffoldConstants.DefaultPeekHeight,
-    headerHeight: Dp = BackdropScaffoldConstants.DefaultHeaderHeight,
+    peekHeight: Dp = BackdropScaffoldDefaults.PeekHeight,
+    headerHeight: Dp = BackdropScaffoldDefaults.HeaderHeight,
     persistentAppBar: Boolean = true,
     stickyFrontLayer: Boolean = true,
     backLayerBackgroundColor: Color = MaterialTheme.colors.primary,
     backLayerContentColor: Color = contentColorFor(backLayerBackgroundColor),
-    frontLayerShape: Shape = BackdropScaffoldConstants.DefaultFrontLayerShape,
-    frontLayerElevation: Dp = BackdropScaffoldConstants.DefaultFrontLayerElevation,
+    frontLayerShape: Shape = BackdropScaffoldDefaults.frontLayerShape,
+    frontLayerElevation: Dp = BackdropScaffoldDefaults.FrontLayerElevation,
     frontLayerBackgroundColor: Color = MaterialTheme.colors.surface,
     frontLayerContentColor: Color = contentColorFor(frontLayerBackgroundColor),
-    frontLayerScrimColor: Color = BackdropScaffoldConstants.DefaultFrontLayerScrimColor,
+    frontLayerScrimColor: Color = BackdropScaffoldDefaults.frontLayerScrimColor,
     snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
     appBar: @Composable () -> Unit,
     backLayerContent: @Composable () -> Unit,
@@ -461,6 +461,13 @@
 /**
  * Contains useful constants for [BackdropScaffold].
  */
+@Deprecated(
+    "BackdropScaffoldConstants has been replaced with BackdropScaffoldDefaults",
+    ReplaceWith(
+        "BackdropScaffoldDefaults",
+        "androidx.compose.material.BackdropScaffoldDefaults"
+    )
+)
 object BackdropScaffoldConstants {
 
     /**
@@ -476,8 +483,8 @@
     /**
      * The default shape of the front layer.
      */
-    @Composable
     val DefaultFrontLayerShape: Shape
+        @Composable
         get() = MaterialTheme.shapes.large
             .copy(topLeft = CornerSize(16.dp), topRight = CornerSize(16.dp))
 
@@ -489,9 +496,43 @@
     /**
      * The default color of the scrim applied to the front layer.
      */
-    @Composable
     val DefaultFrontLayerScrimColor: Color
-        get() = MaterialTheme.colors.surface.copy(alpha = 0.60f)
+        @Composable get() = MaterialTheme.colors.surface.copy(alpha = 0.60f)
+}
+
+/**
+ * Contains useful defaults for [BackdropScaffold].
+ */
+object BackdropScaffoldDefaults {
+
+    /**
+     * The default peek height of the back layer.
+     */
+    val PeekHeight = 56.dp
+
+    /**
+     * The default header height of the front layer.
+     */
+    val HeaderHeight = 48.dp
+
+    /**
+     * The default shape of the front layer.
+     */
+    val frontLayerShape: Shape
+        @Composable
+        get() = MaterialTheme.shapes.large
+            .copy(topLeft = CornerSize(16.dp), topRight = CornerSize(16.dp))
+
+    /**
+     * The default elevation of the front layer.
+     */
+    val FrontLayerElevation = 1.dp
+
+    /**
+     * The default color of the scrim applied to the front layer.
+     */
+    val frontLayerScrimColor: Color
+        @Composable get() = MaterialTheme.colors.surface.copy(alpha = 0.60f)
 }
 
 private val AnimationSlideOffset = 20.dp
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
index ba1e9b6..0755c1e 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
@@ -79,7 +79,7 @@
 class BottomSheetState(
     initialValue: BottomSheetValue,
     clock: AnimationClockObservable,
-    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
     confirmStateChange: (BottomSheetValue) -> Boolean = { true }
 ) : SwipeableState<BottomSheetValue>(
     initialValue = initialValue,
@@ -164,7 +164,7 @@
 @ExperimentalMaterialApi
 fun rememberBottomSheetState(
     initialValue: BottomSheetValue,
-    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
     confirmStateChange: (BottomSheetValue) -> Boolean = { true }
 ): BottomSheetState {
     val disposableClock = AmbientAnimationClock.current.asDisposableClock()
@@ -279,17 +279,17 @@
     floatingActionButtonPosition: FabPosition = FabPosition.End,
     sheetGesturesEnabled: Boolean = true,
     sheetShape: Shape = MaterialTheme.shapes.large,
-    sheetElevation: Dp = BottomSheetScaffoldConstants.DefaultSheetElevation,
+    sheetElevation: Dp = BottomSheetScaffoldDefaults.SheetElevation,
     sheetBackgroundColor: Color = MaterialTheme.colors.surface,
     sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
-    sheetPeekHeight: Dp = BottomSheetScaffoldConstants.DefaultSheetPeekHeight,
+    sheetPeekHeight: Dp = BottomSheetScaffoldDefaults.SheetPeekHeight,
     drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
     drawerGesturesEnabled: Boolean = true,
     drawerShape: Shape = MaterialTheme.shapes.large,
-    drawerElevation: Dp = DrawerConstants.DefaultElevation,
+    drawerElevation: Dp = DrawerDefaults.Elevation,
     drawerBackgroundColor: Color = MaterialTheme.colors.surface,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
-    drawerScrimColor: Color = DrawerConstants.defaultScrimColor,
+    drawerScrimColor: Color = DrawerDefaults.scrimColor,
     backgroundColor: Color = MaterialTheme.colors.background,
     contentColor: Color = contentColorFor(backgroundColor),
     bodyContent: @Composable (PaddingValues) -> Unit
@@ -422,6 +422,13 @@
 /**
  * Contains useful constants for [BottomSheetScaffold].
  */
+@Deprecated(
+    message = "BottomSheetScaffoldConstants has been replaced with BottomSheetScaffoldDefaults",
+    ReplaceWith(
+        "BottomSheetScaffoldDefaults",
+        "androidx.compose.material.BottomSheetScaffoldDefaults"
+    )
+)
 object BottomSheetScaffoldConstants {
 
     /**
@@ -433,4 +440,20 @@
      * The default peek height used by [BottomSheetScaffold].
      */
     val DefaultSheetPeekHeight = 56.dp
+}
+
+/**
+ * Contains useful defaults for [BottomSheetScaffold].
+ */
+object BottomSheetScaffoldDefaults {
+
+    /**
+     * The default elevation used by [BottomSheetScaffold].
+     */
+    val SheetElevation = 8.dp
+
+    /**
+     * The default peek height used by [BottomSheetScaffold].
+     */
+    val SheetPeekHeight = 56.dp
 }
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
index cdeff5d..18676bf 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
@@ -82,11 +82,11 @@
  * it is [Interaction.Pressed].
  * @param elevation [ButtonElevation] used to resolve the elevation for this button in different
  * states. This controls the size of the shadow below the button. Pass `null` here to disable
- * elevation for this button. See [ButtonConstants.defaultElevation].
+ * elevation for this button. See [ButtonDefaults.elevation].
  * @param shape Defines the button's shape as well as its shadow
  * @param border Border to draw around the button
  * @param colors [ButtonColors] that will be used to resolve the background and content color for
- * this button in different states. See [ButtonConstants.defaultButtonColors].
+ * this button in different states. See [ButtonDefaults.buttonColors].
  * @param contentPadding The spacing values to apply internally between the container and the content
  */
 @OptIn(ExperimentalMaterialApi::class)
@@ -96,11 +96,11 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     interactionState: InteractionState = remember { InteractionState() },
-    elevation: ButtonElevation? = ButtonConstants.defaultElevation(),
+    elevation: ButtonElevation? = ButtonDefaults.elevation(),
     shape: Shape = MaterialTheme.shapes.small,
     border: BorderStroke? = null,
-    colors: ButtonColors = ButtonConstants.defaultButtonColors(),
-    contentPadding: PaddingValues = ButtonConstants.DefaultContentPadding,
+    colors: ButtonColors = ButtonDefaults.buttonColors(),
+    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
     content: @Composable RowScope.() -> Unit
 ) {
     // TODO(aelias): Avoid manually putting the clickable above the clip and
@@ -128,8 +128,8 @@
                 Row(
                     Modifier
                         .defaultMinSizeConstraints(
-                            minWidth = ButtonConstants.DefaultMinWidth,
-                            minHeight = ButtonConstants.DefaultMinHeight
+                            minWidth = ButtonDefaults.MinWidth,
+                            minHeight = ButtonDefaults.MinHeight
                         )
                         .indication(interactionState, AmbientIndication.current())
                         .padding(contentPadding),
@@ -176,7 +176,7 @@
  * @param shape Defines the button's shape as well as its shadow
  * @param border Border to draw around the button
  * @param colors [ButtonColors] that will be used to resolve the background and content color for
- * this button in different states. See [ButtonConstants.defaultOutlinedButtonColors].
+ * this button in different states. See [ButtonDefaults.outlinedButtonColors].
  * @param contentPadding The spacing values to apply internally between the container and the content
  */
 @OptIn(ExperimentalMaterialApi::class)
@@ -188,9 +188,9 @@
     interactionState: InteractionState = remember { InteractionState() },
     elevation: ButtonElevation? = null,
     shape: Shape = MaterialTheme.shapes.small,
-    border: BorderStroke? = ButtonConstants.defaultOutlinedBorder,
-    colors: ButtonColors = ButtonConstants.defaultOutlinedButtonColors(),
-    contentPadding: PaddingValues = ButtonConstants.DefaultContentPadding,
+    border: BorderStroke? = ButtonDefaults.outlinedBorder,
+    colors: ButtonColors = ButtonDefaults.outlinedButtonColors(),
+    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
     noinline content: @Composable RowScope.() -> Unit
 ) = Button(
     onClick = onClick,
@@ -236,7 +236,7 @@
  * @param shape Defines the button's shape as well as its shadow
  * @param border Border to draw around the button
  * @param colors [ButtonColors] that will be used to resolve the background and content color for
- * this button in different states. See [ButtonConstants.defaultTextButtonColors].
+ * this button in different states. See [ButtonDefaults.textButtonColors].
  * @param contentPadding The spacing values to apply internally between the container and the content
  */
 @OptIn(ExperimentalMaterialApi::class)
@@ -249,8 +249,8 @@
     elevation: ButtonElevation? = null,
     shape: Shape = MaterialTheme.shapes.small,
     border: BorderStroke? = null,
-    colors: ButtonColors = ButtonConstants.defaultTextButtonColors(),
-    contentPadding: PaddingValues = ButtonConstants.DefaultTextContentPadding,
+    colors: ButtonColors = ButtonDefaults.textButtonColors(),
+    contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding,
     noinline content: @Composable RowScope.() -> Unit
 ) = Button(
     onClick = onClick,
@@ -268,7 +268,7 @@
 /**
  * Represents the elevation for a button in different states.
  *
- * See [ButtonConstants.defaultElevation] for the default elevation used in a [Button].
+ * See [ButtonDefaults.elevation] for the default elevation used in a [Button].
  */
 @ExperimentalMaterialApi
 @Stable
@@ -285,10 +285,10 @@
 /**
  * Represents the background and content colors used in a button in different states.
  *
- * See [ButtonConstants.defaultButtonColors] for the default colors used in a [Button].
- * See [ButtonConstants.defaultOutlinedButtonColors] for the default colors used in a
+ * See [ButtonDefaults.buttonColors] for the default colors used in a [Button].
+ * See [ButtonDefaults.outlinedButtonColors] for the default colors used in a
  * [OutlinedButton].
- * See [ButtonConstants.defaultTextButtonColors] for the default colors used in a [TextButton].
+ * See [ButtonDefaults.textButtonColors] for the default colors used in a [TextButton].
  */
 @ExperimentalMaterialApi
 @Stable
@@ -311,6 +311,13 @@
 /**
  * Contains the default values used by [Button]
  */
+@Deprecated(
+    "ButtonConstants has been replaced with ButtonDefaults",
+    ReplaceWith(
+        "ButtonDefaults",
+        "androidx.compose.material.ButtonDefaults"
+    )
+)
 object ButtonConstants {
     private val ButtonHorizontalPadding = 16.dp
     private val ButtonVerticalPadding = 8.dp
@@ -364,6 +371,13 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "ButtonConstants has been replaced with ButtonDefaults",
+        ReplaceWith(
+            "ButtonDefaults.elevation(elevation, pressedElevation, disabledElevation)",
+            "androidx.compose.material.ButtonDefaults"
+        )
+    )
     fun defaultElevation(
         defaultElevation: Dp = 2.dp,
         pressedElevation: Dp = 8.dp,
@@ -393,6 +407,14 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "ButtonConstants has been replaced with ButtonDefaults",
+        ReplaceWith(
+            "ButtonDefaults.buttonColors(backgroundColor, disabledBackgroundColor, contentColor, " +
+                "disabledContentColor)",
+            "androidx.compose.material.ButtonDefaults"
+        )
+    )
     fun defaultButtonColors(
         backgroundColor: Color = MaterialTheme.colors.primary,
         disabledBackgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
@@ -417,6 +439,14 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "ButtonConstants has been replaced with ButtonDefaults",
+        ReplaceWith(
+            "ButtonDefaults.outlinedButtonColors(backgroundColor, disabledBackgroundColor, " +
+                "contentColor, disabledContentColor)",
+            "androidx.compose.material.ButtonDefaults"
+        )
+    )
     fun defaultOutlinedButtonColors(
         backgroundColor: Color = MaterialTheme.colors.surface,
         contentColor: Color = MaterialTheme.colors.primary,
@@ -439,6 +469,14 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "ButtonConstants has been replaced with ButtonDefaults",
+        ReplaceWith(
+            "ButtonDefaults.textButtonColors(backgroundColor, disabledBackgroundColor, " +
+                "contentColor, disabledContentColor)",
+            "androidx.compose.material.ButtonDefaults"
+        )
+    )
     fun defaultTextButtonColors(
         backgroundColor: Color = Color.Transparent,
         contentColor: Color = MaterialTheme.colors.primary,
@@ -464,8 +502,8 @@
     /**
      * The default disabled content color used by all types of [Button]s
      */
-    @Composable
     val defaultOutlinedBorder: BorderStroke
+        @Composable
         get() = BorderStroke(
             OutlinedBorderSize, MaterialTheme.colors.onSurface.copy(alpha = OutlinedBorderOpacity)
         )
@@ -482,6 +520,179 @@
 }
 
 /**
+ * Contains the default values used by [Button]
+ */
+object ButtonDefaults {
+    private val ButtonHorizontalPadding = 16.dp
+    private val ButtonVerticalPadding = 8.dp
+
+    /**
+     * The default content padding used by [Button]
+     */
+    val ContentPadding = PaddingValues(
+        start = ButtonHorizontalPadding,
+        top = ButtonVerticalPadding,
+        end = ButtonHorizontalPadding,
+        bottom = ButtonVerticalPadding
+    )
+
+    /**
+     * The default min width applied for the [Button].
+     * Note that you can override it by applying Modifier.widthIn directly on [Button].
+     */
+    val MinWidth = 64.dp
+
+    /**
+     * The default min width applied for the [Button].
+     * Note that you can override it by applying Modifier.heightIn directly on [Button].
+     */
+    val MinHeight = 36.dp
+
+    /**
+     * The default size of the icon when used inside a [Button].
+     *
+     * @sample androidx.compose.material.samples.ButtonWithIconSample
+     */
+    val IconSize = 18.dp
+
+    /**
+     * The default size of the spacing between an icon and a text when they used inside a [Button].
+     *
+     * @sample androidx.compose.material.samples.ButtonWithIconSample
+     */
+    val IconSpacing = 8.dp
+
+    // TODO: b/152525426 add support for focused and hovered states
+    /**
+     * Creates a [ButtonElevation] that will animate between the provided values according to the
+     * Material specification for a [Button].
+     *
+     * @param defaultElevation the elevation to use when the [Button] is enabled, and has no
+     * other [Interaction]s.
+     * @param pressedElevation the elevation to use when the [Button] is enabled and
+     * is [Interaction.Pressed].
+     * @param disabledElevation the elevation to use when the [Button] is not enabled.
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun elevation(
+        defaultElevation: Dp = 2.dp,
+        pressedElevation: Dp = 8.dp,
+        // focused: Dp = 4.dp,
+        // hovered: Dp = 4.dp,
+        disabledElevation: Dp = 0.dp
+    ): ButtonElevation {
+        val clock = AmbientAnimationClock.current.asDisposableClock()
+        return remember(defaultElevation, pressedElevation, disabledElevation, clock) {
+            DefaultButtonElevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                disabledElevation = disabledElevation,
+                clock = clock
+            )
+        }
+    }
+
+    /**
+     * Creates a [ButtonColors] that represents the default background and content colors used in
+     * a [Button].
+     *
+     * @param backgroundColor the background color of this [Button] when enabled
+     * @param disabledBackgroundColor the background color of this [Button] when not enabled
+     * @param contentColor the content color of this [Button] when enabled
+     * @param disabledContentColor the content color of this [Button] when not enabled
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun buttonColors(
+        backgroundColor: Color = MaterialTheme.colors.primary,
+        disabledBackgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
+            .compositeOver(MaterialTheme.colors.surface),
+        contentColor: Color = contentColorFor(backgroundColor),
+        disabledContentColor: Color = MaterialTheme.colors.onSurface
+            .copy(alpha = ContentAlpha.disabled)
+    ): ButtonColors = DefaultButtonColors(
+        backgroundColor,
+        disabledBackgroundColor,
+        contentColor,
+        disabledContentColor
+    )
+
+    /**
+     * Creates a [ButtonColors] that represents the default background and content colors used in
+     * an [OutlinedButton].
+     *
+     * @param backgroundColor the background color of this [OutlinedButton]
+     * @param contentColor the content color of this [OutlinedButton] when enabled
+     * @param disabledContentColor the content color of this [OutlinedButton] when not enabled
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun outlinedButtonColors(
+        backgroundColor: Color = MaterialTheme.colors.surface,
+        contentColor: Color = MaterialTheme.colors.primary,
+        disabledContentColor: Color = MaterialTheme.colors.onSurface
+            .copy(alpha = ContentAlpha.disabled)
+    ): ButtonColors = DefaultButtonColors(
+        backgroundColor,
+        backgroundColor,
+        contentColor,
+        disabledContentColor
+    )
+
+    /**
+     * Creates a [ButtonColors] that represents the default background and content colors used in
+     * a [TextButton].
+     *
+     * @param backgroundColor the background color of this [TextButton]
+     * @param contentColor the content color of this [TextButton] when enabled
+     * @param disabledContentColor the content color of this [TextButton] when not enabled
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun textButtonColors(
+        backgroundColor: Color = Color.Transparent,
+        contentColor: Color = MaterialTheme.colors.primary,
+        disabledContentColor: Color = MaterialTheme.colors.onSurface
+            .copy(alpha = ContentAlpha.disabled)
+    ): ButtonColors = DefaultButtonColors(
+        backgroundColor,
+        backgroundColor,
+        contentColor,
+        disabledContentColor
+    )
+
+    /**
+     * The default color opacity used for an [OutlinedButton]'s border color
+     */
+    const val OutlinedBorderOpacity = 0.12f
+
+    /**
+     * The default [OutlinedButton]'s border size
+     */
+    val OutlinedBorderSize = 1.dp
+
+    /**
+     * The default disabled content color used by all types of [Button]s
+     */
+    val outlinedBorder: BorderStroke
+        @Composable
+        get() = BorderStroke(
+            OutlinedBorderSize, MaterialTheme.colors.onSurface.copy(alpha = OutlinedBorderOpacity)
+        )
+
+    private val TextButtonHorizontalPadding = 8.dp
+
+    /**
+     * The default content padding used by [TextButton]
+     */
+    val TextButtonContentPadding = ContentPadding.copy(
+        start = TextButtonHorizontalPadding,
+        end = TextButtonHorizontalPadding
+    )
+}
+
+/**
  * Default [ButtonElevation] implementation.
  */
 @OptIn(ExperimentalMaterialApi::class)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
index 6b8cfc3..811d694 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
@@ -76,7 +76,7 @@
  * [InteractionState] if you want to read the [InteractionState] and customize the appearance /
  * behavior of this Checkbox in different [Interaction]s.
  * @param colors [CheckboxColors] that will be used to determine the color of the checkmark / box
- * / border in different states. See [CheckboxConstants.defaultColors].
+ * / border in different states. See [CheckboxDefaults.colors].
  */
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
@@ -86,7 +86,7 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     interactionState: InteractionState = remember { InteractionState() },
-    colors: CheckboxColors = CheckboxConstants.defaultColors()
+    colors: CheckboxColors = CheckboxDefaults.colors()
 ) {
     TriStateCheckbox(
         state = ToggleableState(checked),
@@ -120,7 +120,7 @@
  * [InteractionState] if you want to read the [InteractionState] and customize the appearance /
  * behavior of this TriStateCheckbox in different [Interaction]s.
  * @param colors [CheckboxColors] that will be used to determine the color of the checkmark / box
- * / border in different states. See [CheckboxConstants.defaultColors].
+ * / border in different states. See [CheckboxDefaults.colors].
  */
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
@@ -130,7 +130,7 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     interactionState: InteractionState = remember { InteractionState() },
-    colors: CheckboxColors = CheckboxConstants.defaultColors()
+    colors: CheckboxColors = CheckboxDefaults.colors()
 ) {
     CheckboxImpl(
         enabled = enabled,
@@ -155,7 +155,7 @@
  * Represents the colors used by the three different sections (checkmark, box, and border) of a
  * [Checkbox] or [TriStateCheckbox] in different states.
  *
- * See [CheckboxConstants.defaultColors] for the default implementation that follows Material
+ * See [CheckboxDefaults.colors] for the default implementation that follows Material
  * specifications.
  */
 @ExperimentalMaterialApi
@@ -190,6 +190,13 @@
 /**
  * Constants used in [Checkbox] and [TriStateCheckbox].
  */
+@Deprecated(
+    "CheckboxConstants has been replaced with CheckboxDefaults",
+    ReplaceWith(
+        "CheckboxDefaults",
+        "androidx.compose.material.CheckboxDefaults"
+    )
+)
 object CheckboxConstants {
     /**
      * Creates a [CheckboxColors] that will animate between the provided colors according to the
@@ -204,6 +211,14 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "CheckboxConstants has been replaced with CheckboxDefaults",
+        ReplaceWith(
+            "CheckboxDefaults.colors(checkedColor, uncheckedColor, checkmarkColor, disabledColor," +
+                " disabledIndeterminateColor)",
+            "androidx.compose.material.CheckboxDefaults"
+        )
+    )
     fun defaultColors(
         checkedColor: Color = MaterialTheme.colors.secondary,
         uncheckedColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
@@ -238,6 +253,57 @@
     }
 }
 
+/**
+ * Defaults used in [Checkbox] and [TriStateCheckbox].
+ */
+object CheckboxDefaults {
+    /**
+     * Creates a [CheckboxColors] that will animate between the provided colors according to the
+     * Material specification.
+     *
+     * @param checkedColor the color that will be used for the border and box when checked
+     * @param uncheckedColor color that will be used for the border when unchecked
+     * @param checkmarkColor color that will be used for the checkmark when checked
+     * @param disabledColor color that will be used for the box and border when disabled
+     * @param disabledIndeterminateColor color that will be used for the box and
+     * border in a [TriStateCheckbox] when disabled AND in an [ToggleableState.Indeterminate] state.
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun colors(
+        checkedColor: Color = MaterialTheme.colors.secondary,
+        uncheckedColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
+        checkmarkColor: Color = MaterialTheme.colors.surface,
+        disabledColor: Color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
+        disabledIndeterminateColor: Color = checkedColor.copy(alpha = ContentAlpha.disabled)
+    ): CheckboxColors {
+        val clock = AmbientAnimationClock.current.asDisposableClock()
+        return remember(
+            checkedColor,
+            uncheckedColor,
+            checkmarkColor,
+            disabledColor,
+            disabledIndeterminateColor,
+            clock
+        ) {
+            DefaultCheckboxColors(
+                checkedBorderColor = checkedColor,
+                checkedBoxColor = checkedColor,
+                checkedCheckmarkColor = checkmarkColor,
+                uncheckedCheckmarkColor = checkmarkColor.copy(alpha = 0f),
+                uncheckedBoxColor = checkedColor.copy(alpha = 0f),
+                disabledCheckedBoxColor = disabledColor,
+                disabledUncheckedBoxColor = disabledColor.copy(alpha = 0f),
+                disabledIndeterminateBoxColor = disabledIndeterminateColor,
+                uncheckedBorderColor = uncheckedColor,
+                disabledBorderColor = disabledColor,
+                disabledIndeterminateBorderColor = disabledIndeterminateColor,
+                clock = clock
+            )
+        }
+    }
+}
+
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
 private fun CheckboxImpl(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ContentAlpha.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ContentAlpha.kt
index f4e8a3b..1796da7 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ContentAlpha.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ContentAlpha.kt
@@ -31,8 +31,8 @@
      * A high level of content alpha, used to represent high emphasis text such as input text in a
      * selected [TextField].
      */
-    @Composable
     val high: Float
+        @Composable
         get() = contentAlpha(
             highContrastAlpha = HighContrastContentAlpha.high,
             lowContrastAlpha = LowContrastContentAlpha.high
@@ -42,8 +42,8 @@
      * A medium level of content alpha, used to represent medium emphasis text such as
      * placeholder text in a [TextField].
      */
-    @Composable
     val medium: Float
+        @Composable
         get() = contentAlpha(
             highContrastAlpha = HighContrastContentAlpha.medium,
             lowContrastAlpha = LowContrastContentAlpha.medium
@@ -53,8 +53,8 @@
      * A low level of content alpha used to represent disabled components, such as text in a
      * disabled [Button].
      */
-    @Composable
     val disabled: Float
+        @Composable
         get() = contentAlpha(
             highContrastAlpha = HighContrastContentAlpha.disabled,
             lowContrastAlpha = LowContrastContentAlpha.disabled
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
index 5cb78fb..770964a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
@@ -342,10 +342,10 @@
     drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
     gesturesEnabled: Boolean = true,
     drawerShape: Shape = MaterialTheme.shapes.large,
-    drawerElevation: Dp = DrawerConstants.DefaultElevation,
+    drawerElevation: Dp = DrawerDefaults.Elevation,
     drawerBackgroundColor: Color = MaterialTheme.colors.surface,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
-    scrimColor: Color = DrawerConstants.defaultScrimColor,
+    scrimColor: Color = DrawerDefaults.scrimColor,
     bodyContent: @Composable () -> Unit
 ) {
     WithConstraints(modifier.fillMaxSize()) {
@@ -445,10 +445,10 @@
     drawerState: BottomDrawerState = rememberBottomDrawerState(BottomDrawerValue.Closed),
     gesturesEnabled: Boolean = true,
     drawerShape: Shape = MaterialTheme.shapes.large,
-    drawerElevation: Dp = DrawerConstants.DefaultElevation,
+    drawerElevation: Dp = DrawerDefaults.Elevation,
     drawerBackgroundColor: Color = MaterialTheme.colors.surface,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
-    scrimColor: Color = DrawerConstants.defaultScrimColor,
+    scrimColor: Color = DrawerDefaults.scrimColor,
     bodyContent: @Composable () -> Unit
 ) {
     WithConstraints(modifier.fillMaxSize()) {
@@ -530,6 +530,13 @@
 /**
  * Object to hold default values for [ModalDrawerLayout] and [BottomDrawerLayout]
  */
+@Deprecated(
+    "DrawerConstants has been replaced with DrawerDefaults",
+    ReplaceWith(
+        "DrawerDefaults",
+        "androidx.compose.material.DrawerDefaults"
+    )
+)
 object DrawerConstants {
 
     /**
@@ -537,8 +544,8 @@
      */
     val DefaultElevation = 16.dp
 
-    @Composable
     val defaultScrimColor: Color
+        @Composable
         get() = MaterialTheme.colors.onSurface.copy(alpha = ScrimDefaultOpacity)
 
     /**
@@ -547,6 +554,26 @@
     const val ScrimDefaultOpacity = 0.32f
 }
 
+/**
+ * Object to hold default values for [ModalDrawerLayout] and [BottomDrawerLayout]
+ */
+object DrawerDefaults {
+
+    /**
+     * Default Elevation for drawer sheet as specified in material specs
+     */
+    val Elevation = 16.dp
+
+    val scrimColor: Color
+        @Composable
+        get() = MaterialTheme.colors.onSurface.copy(alpha = ScrimOpacity)
+
+    /**
+     * Default alpha for scrim color
+     */
+    const val ScrimOpacity = 0.32f
+}
+
 private fun calculateFraction(a: Float, b: Float, pos: Float) =
     ((pos - a) / (b - a)).coerceIn(0f, 1f)
 
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
index 781185d..daa9ef7 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
@@ -29,8 +29,8 @@
 /**
  * Animates the [Dp] value of [this] between [from] and [to] [Interaction]s, to [target]. The
  * [AnimationSpec] used depends on the values for [from] and [to], see
- * [ElevationConstants.incomingAnimationSpecForInteraction] and
- * [ElevationConstants.outgoingAnimationSpecForInteraction] for more details.
+ * [ElevationDefaults.incomingAnimationSpecForInteraction] and
+ * [ElevationDefaults.outgoingAnimationSpecForInteraction] for more details.
  *
  * @param from the previous [Interaction] that was used to calculate elevation. `null` if there
  * was no previous [Interaction], such as when the component is in its default state.
@@ -47,9 +47,9 @@
 ) {
     val spec = when {
         // Moving to a new state
-        to != null -> ElevationConstants.incomingAnimationSpecForInteraction(to)
+        to != null -> ElevationDefaults.incomingAnimationSpecForInteraction(to)
         // Moving to default, from a previous state
-        from != null -> ElevationConstants.outgoingAnimationSpecForInteraction(from)
+        from != null -> ElevationDefaults.outgoingAnimationSpecForInteraction(from)
         // Loading the initial state, or moving back to the baseline state from a disabled /
         // unknown state, so just snap to the final value.
         else -> null
@@ -62,11 +62,18 @@
  *
  * Typically you should use [animateElevation] instead, which uses these [AnimationSpec]s
  * internally. [animateElevation] in turn is used by the defaults for [Button] and
- * [FloatingActionButton] - inside [ButtonConstants.defaultElevation] and
- * [FloatingActionButtonConstants.defaultElevation] respectively.
+ * [FloatingActionButton] - inside [ButtonDefaults.elevation] and
+ * [FloatingActionButtonDefaults.elevation] respectively.
  *
  * @see animateElevation
  */
+@Deprecated(
+    "ElevationConstants has been replaced with ElevationDefaults",
+    ReplaceWith(
+        "ElevationDefaults",
+        "androidx.compose.material.ElevationDefaults"
+    )
+)
 object ElevationConstants {
     /**
      * Returns the [AnimationSpec]s used when animating elevation to [interaction], either from a
@@ -99,6 +106,48 @@
     }
 }
 
+/**
+ * Contains default [AnimationSpec]s used for animating elevation between different [Interaction]s.
+ *
+ * Typically you should use [animateElevation] instead, which uses these [AnimationSpec]s
+ * internally. [animateElevation] in turn is used by the defaults for [Button] and
+ * [FloatingActionButton] - inside [ButtonDefaults.elevation] and
+ * [FloatingActionButtonDefaults.elevation] respectively.
+ *
+ * @see animateElevation
+ */
+object ElevationDefaults {
+    /**
+     * Returns the [AnimationSpec]s used when animating elevation to [interaction], either from a
+     * previous [Interaction], or from the default state. If [interaction] is unknown, then
+     * returns `null`.
+     *
+     * @param interaction the [Interaction] that is being animated to
+     */
+    fun incomingAnimationSpecForInteraction(interaction: Interaction): AnimationSpec<Dp>? {
+        return when (interaction) {
+            is Interaction.Pressed -> DefaultIncomingSpec
+            is Interaction.Dragged -> DefaultIncomingSpec
+            else -> null
+        }
+    }
+
+    /**
+     * Returns the [AnimationSpec]s used when animating elevation away from [interaction], to the
+     * default state. If [interaction] is unknown, then returns `null`.
+     *
+     * @param interaction the [Interaction] that is being animated away from
+     */
+    fun outgoingAnimationSpecForInteraction(interaction: Interaction): AnimationSpec<Dp>? {
+        return when (interaction) {
+            is Interaction.Pressed -> DefaultOutgoingSpec
+            is Interaction.Dragged -> DefaultOutgoingSpec
+            // TODO: use [HoveredOutgoingSpec] when hovered
+            else -> null
+        }
+    }
+}
+
 private val DefaultIncomingSpec = TweenSpec<Dp>(
     durationMillis = 120,
     easing = FastOutSlowInEasing
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Emphasis.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Emphasis.kt
index 307565a..e92850d 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Emphasis.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Emphasis.kt
@@ -80,18 +80,15 @@
     /**
      * Emphasis used to express high emphasis, such as for selected text fields.
      */
-    @Composable
-    val high: Emphasis
+    val high: Emphasis @Composable get
     /**
      * Emphasis used to express medium emphasis, such as for placeholder text in a text field.
      */
-    @Composable
-    val medium: Emphasis
+    val medium: Emphasis @Composable get
     /**
      * Emphasis used to express disabled state, such as for a disabled button.
      */
-    @Composable
-    val disabled: Emphasis
+    val disabled: Emphasis @Composable get
 }
 
 /**
@@ -148,24 +145,24 @@
         }
     }
 
-    @Composable
     override val high: Emphasis
+        @Composable
         get() = AlphaEmphasis(
             lightTheme = MaterialTheme.colors.isLight,
             highContrastAlpha = HighContrastAlphaLevels.high,
             reducedContrastAlpha = ReducedContrastAlphaLevels.high
         )
 
-    @Composable
     override val medium: Emphasis
+        @Composable
         get() = AlphaEmphasis(
             lightTheme = MaterialTheme.colors.isLight,
             highContrastAlpha = HighContrastAlphaLevels.medium,
             reducedContrastAlpha = ReducedContrastAlphaLevels.medium
         )
 
-    @Composable
     override val disabled: Emphasis
+        @Composable
         get() = AlphaEmphasis(
             lightTheme = MaterialTheme.colors.isLight,
             highContrastAlpha = HighContrastAlphaLevels.disabled,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
index ab7e7e6..8c39374 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
@@ -79,7 +79,7 @@
     shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
     backgroundColor: Color = MaterialTheme.colors.secondary,
     contentColor: Color = contentColorFor(backgroundColor),
-    elevation: FloatingActionButtonElevation = FloatingActionButtonConstants.defaultElevation(),
+    elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
     content: @Composable () -> Unit
 ) {
     // TODO(aelias): Avoid manually managing the ripple once http://b/157687898
@@ -151,7 +151,7 @@
     shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
     backgroundColor: Color = MaterialTheme.colors.secondary,
     contentColor: Color = contentColorFor(backgroundColor),
-    elevation: FloatingActionButtonElevation = FloatingActionButtonConstants.defaultElevation()
+    elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation()
 ) {
     FloatingActionButton(
         modifier = modifier.preferredSizeIn(
@@ -188,7 +188,7 @@
 /**
  * Represents the elevation for a floating action button in different states.
  *
- * See [FloatingActionButtonConstants.defaultElevation] for the default elevation used in a
+ * See [FloatingActionButtonDefaults.elevation] for the default elevation used in a
  * [FloatingActionButton] and [ExtendedFloatingActionButton].
  */
 @ExperimentalMaterialApi
@@ -205,6 +205,13 @@
 /**
  * Contains the default values used by [FloatingActionButton]
  */
+@Deprecated(
+    "FloatingActionButtonConstants has been replaced with FloatingActionButtonDefaults",
+    ReplaceWith(
+        "FloatingActionButtonDefaults",
+        "androidx.compose.material.FloatingActionButtonDefaults"
+    )
+)
 object FloatingActionButtonConstants {
     // TODO: b/152525426 add support for focused and hovered states
     /**
@@ -218,6 +225,15 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "FloatingActionButtonConstants has been replaced with " +
+            "FloatingActionButtonDefaults",
+        ReplaceWith(
+            "FloatingActionButtonDefaults.elevation(elevation, pressedElevation, " +
+                "disabledElevation)",
+            "androidx.compose.material.FloatingActionButtonDefaults"
+        )
+    )
     fun defaultElevation(
         defaultElevation: Dp = 6.dp,
         pressedElevation: Dp = 12.dp
@@ -236,6 +252,39 @@
 }
 
 /**
+ * Contains the default values used by [FloatingActionButton]
+ */
+object FloatingActionButtonDefaults {
+    // TODO: b/152525426 add support for focused and hovered states
+    /**
+     * Creates a [FloatingActionButtonElevation] that will animate between the provided values
+     * according to the Material specification.
+     *
+     * @param defaultElevation the elevation to use when the [FloatingActionButton] has no
+     * [Interaction]s
+     * @param pressedElevation the elevation to use when the [FloatingActionButton] is
+     * [Interaction.Pressed].
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun elevation(
+        defaultElevation: Dp = 6.dp,
+        pressedElevation: Dp = 12.dp
+        // focused: Dp = 8.dp,
+        // hovered: Dp = 8.dp,
+    ): FloatingActionButtonElevation {
+        val clock = AmbientAnimationClock.current.asDisposableClock()
+        return remember(defaultElevation, pressedElevation, clock) {
+            DefaultFloatingActionButtonElevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                clock = clock
+            )
+        }
+    }
+}
+
+/**
  * Default [FloatingActionButtonElevation] implementation.
  */
 @OptIn(ExperimentalMaterialApi::class)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ListItem.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ListItem.kt
index 48e02b7..40b52dc 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ListItem.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ListItem.kt
@@ -53,12 +53,15 @@
  * - three-line items
  * @sample androidx.compose.material.samples.ThreeLineListItems
  *
+ * You can combine this component with a checkbox or switch as in the following examples:
+ * @sample androidx.compose.material.samples.ClickableListItems
+ *
  * @param modifier Modifier to be applied to the list item
  * @param icon The leading supporting visual of the list item
  * @param secondaryText The secondary text of the list item
  * @param singleLineSecondaryText Whether the secondary text is single line
  * @param overlineText The text displayed above the primary text
- * @param trailing The trailing meta text or meta icon of the list item
+ * @param trailing The trailing meta text, icon, switch or checkbox
  * @param text The primary text of the list item
  */
 @Composable
@@ -108,20 +111,20 @@
 private object OneLine {
     // TODO(popam): support wide icons
     // TODO(popam): convert these to sp
-    // List item related constants.
+    // List item related defaults.
     private val MinHeight = 48.dp
     private val MinHeightWithIcon = 56.dp
 
-    // Icon related constants.
+    // Icon related defaults.
     private val IconMinPaddedWidth = 40.dp
     private val IconLeftPadding = 16.dp
     private val IconVerticalPadding = 8.dp
 
-    // Content related constants.
+    // Content related defaults.
     private val ContentLeftPadding = 16.dp
     private val ContentRightPadding = 16.dp
 
-    // Trailing related constants.
+    // Trailing related defaults.
     private val TrailingRightPadding = 16.dp
 
     @Composable
@@ -163,16 +166,16 @@
 }
 
 private object TwoLine {
-    // List item related constants.
+    // List item related defaults.
     private val MinHeight = 64.dp
     private val MinHeightWithIcon = 72.dp
 
-    // Icon related constants.
+    // Icon related defaults.
     private val IconMinPaddedWidth = 40.dp
     private val IconLeftPadding = 16.dp
     private val IconVerticalPadding = 16.dp
 
-    // Content related constants.
+    // Content related defaults.
     private val ContentLeftPadding = 16.dp
     private val ContentRightPadding = 16.dp
     private val OverlineBaselineOffset = 24.dp
@@ -182,7 +185,7 @@
     private val PrimaryToSecondaryBaselineOffsetNoIcon = 20.dp
     private val PrimaryToSecondaryBaselineOffsetWithIcon = 20.dp
 
-    // Trailing related constants.
+    // Trailing related defaults.
     private val TrailingRightPadding = 16.dp
 
     @Composable
@@ -264,15 +267,15 @@
 }
 
 private object ThreeLine {
-    // List item related constants.
+    // List item related defaults.
     private val MinHeight = 88.dp
 
-    // Icon related constants.
+    // Icon related defaults.
     private val IconMinPaddedWidth = 40.dp
     private val IconLeftPadding = 16.dp
     private val IconThreeLineVerticalPadding = 16.dp
 
-    // Content related constants.
+    // Content related defaults.
     private val ContentLeftPadding = 16.dp
     private val ContentRightPadding = 16.dp
     private val ThreeLineBaselineFirstOffset = 28.dp
@@ -280,7 +283,7 @@
     private val ThreeLineBaselineThirdOffset = 20.dp
     private val ThreeLineTrailingTopPadding = 16.dp
 
-    // Trailing related constants.
+    // Trailing related defaults.
     private val TrailingRightPadding = 16.dp
 
     @Composable
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt
index 0a1c321..8615164 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt
@@ -94,9 +94,9 @@
      *
      * @sample androidx.compose.material.samples.ThemeColorSample
      */
-    @Composable
-    @ComposableContract(readonly = true)
     val colors: Colors
+        @Composable
+        @ComposableContract(readonly = true)
         get() = AmbientColors.current
 
     /**
@@ -104,17 +104,17 @@
      *
      * @sample androidx.compose.material.samples.ThemeTextStyleSample
      */
-    @Composable
-    @ComposableContract(readonly = true)
     val typography: Typography
+        @Composable
+        @ComposableContract(readonly = true)
         get() = AmbientTypography.current
 
     /**
      * Retrieves the current [Shapes] at the call site's position in the hierarchy.
      */
-    @Composable
-    @ComposableContract(readonly = true)
     val shapes: Shapes
+        @Composable
+        @ComposableContract(readonly = true)
         get() = AmbientShapes.current
 }
 
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
index f075e67..30cbd64 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
@@ -74,7 +74,8 @@
  * the [toggle], and then screen end-aligned. Vertically, it will try to expand to the bottom
  * of the [toggle], then from the top of the [toggle], and then screen top-aligned. A
  * [dropdownOffset] can be provided to adjust the positioning of the menu for cases when the
- * layout bounds of the [toggle] do not coincide with its visual bounds.
+ * layout bounds of the [toggle] do not coincide with its visual bounds. Note the offset will be
+ * applied in the direction in which the menu will decide to expand.
  *
  * Example usage:
  * @sample androidx.compose.material.samples.MenuSample
@@ -199,7 +200,7 @@
     }
 }
 
-// Size constants.
+// Size defaults.
 private val MenuElevation = 8.dp
 private val MenuVerticalMargin = 32.dp
 private val DropdownMenuHorizontalPadding = 16.dp
@@ -305,8 +306,8 @@
         val contentOffsetY = with(density) { contentOffset.y.toIntPx() }
 
         // Compute horizontal position.
-        val toRight = parentGlobalBounds.right + contentOffsetX
-        val toLeft = parentGlobalBounds.left - contentOffsetX - popupContentSize.width
+        val toRight = parentGlobalBounds.left + contentOffsetX
+        val toLeft = parentGlobalBounds.right - contentOffsetX - popupContentSize.width
         val toDisplayRight = windowGlobalBounds.width - popupContentSize.width
         val toDisplayLeft = 0
         val x = if (layoutDirection == LayoutDirection.Ltr) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 6e3b519..d316933 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -79,7 +79,7 @@
 class ModalBottomSheetState(
     initialValue: ModalBottomSheetValue,
     clock: AnimationClockObservable,
-    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
     confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
 ) : SwipeableState<ModalBottomSheetValue>(
     initialValue = initialValue,
@@ -167,7 +167,7 @@
 fun rememberModalBottomSheetState(
     initialValue: ModalBottomSheetValue,
     clock: AnimationClockObservable = AmbientAnimationClock.current,
-    animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
     confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
 ): ModalBottomSheetState {
     val disposableClock = clock.asDisposableClock()
@@ -219,10 +219,10 @@
     sheetState: ModalBottomSheetState =
         rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
     sheetShape: Shape = MaterialTheme.shapes.large,
-    sheetElevation: Dp = ModalBottomSheetConstants.DefaultElevation,
+    sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
     sheetBackgroundColor: Color = MaterialTheme.colors.surface,
     sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
-    scrimColor: Color = ModalBottomSheetConstants.DefaultScrimColor,
+    scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
     content: @Composable () -> Unit
 ) = BottomSheetStack(
     modifier = modifier,
@@ -322,6 +322,13 @@
 /**
  * Contains useful constants for [ModalBottomSheetLayout].
  */
+@Deprecated(
+    "ModalBottomSheetConstants has been replaced with ModalBottomSheetDefaults",
+    ReplaceWith(
+        "ModalBottomSheetDefaults",
+        "androidx.compose.material.ModalBottomSheetDefaults"
+    )
+)
 object ModalBottomSheetConstants {
 
     /**
@@ -332,7 +339,25 @@
     /**
      * The default scrim color used by [ModalBottomSheetLayout].
      */
-    @Composable
     val DefaultScrimColor: Color
+        @Composable
+        get() = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
+}
+
+/**
+ * Contains useful Defaults for [ModalBottomSheetLayout].
+ */
+object ModalBottomSheetDefaults {
+
+    /**
+     * The default elevation used by [ModalBottomSheetLayout].
+     */
+    val Elevation = 16.dp
+
+    /**
+     * The default scrim color used by [ModalBottomSheetLayout].
+     */
+    val scrimColor: Color
+        @Composable
         get() = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
 }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
index 09a97e0..096155b 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
@@ -16,22 +16,21 @@
 
 package androidx.compose.material
 
-import androidx.compose.animation.core.AnimationConstants.Infinite
 import androidx.compose.animation.core.CubicBezierEasing
 import androidx.compose.animation.core.FloatPropKey
 import androidx.compose.animation.core.IntPropKey
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.infiniteRepeatable
 import androidx.compose.animation.core.keyframes
-import androidx.compose.animation.core.repeatable
 import androidx.compose.animation.core.transitionDefinition
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.transition
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.foundation.progressSemantics
-import androidx.compose.material.ProgressIndicatorConstants.DefaultIndicatorBackgroundOpacity
+import androidx.compose.material.ProgressIndicatorDefaults.IndicatorBackgroundOpacity
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -53,7 +52,7 @@
  * A determinate linear progress indicator that represents progress by drawing a horizontal line.
  *
  * By default there is no animation between [progress] values. You can use
- * [ProgressIndicatorConstants.DefaultProgressAnimationSpec] as the default recommended
+ * [ProgressIndicatorDefaults.ProgressAnimationSpec] as the default recommended
  * [AnimationSpec] when animating progress, such as in the following example:
  *
  * @sample androidx.compose.material.samples.LinearProgressIndicatorSample
@@ -69,14 +68,14 @@
     @FloatRange(from = 0.0, to = 1.0) progress: Float,
     modifier: Modifier = Modifier,
     color: Color = MaterialTheme.colors.primary,
-    backgroundColor: Color = color.copy(alpha = DefaultIndicatorBackgroundOpacity)
+    backgroundColor: Color = color.copy(alpha = IndicatorBackgroundOpacity)
 ) {
     Canvas(
         modifier
             .progressSemantics(progress)
             .preferredSize(LinearIndicatorWidth, LinearIndicatorHeight)
     ) {
-        val strokeWidth = ProgressIndicatorConstants.DefaultStrokeWidth.toPx()
+        val strokeWidth = ProgressIndicatorDefaults.StrokeWidth.toPx()
         drawLinearIndicatorBackground(backgroundColor, strokeWidth)
         drawLinearIndicator(0f, progress, color, strokeWidth)
     }
@@ -94,7 +93,7 @@
 fun LinearProgressIndicator(
     modifier: Modifier = Modifier,
     color: Color = MaterialTheme.colors.primary,
-    backgroundColor: Color = color.copy(alpha = DefaultIndicatorBackgroundOpacity)
+    backgroundColor: Color = color.copy(alpha = IndicatorBackgroundOpacity)
 ) {
     val state = transition(
         definition = LinearIndeterminateTransition,
@@ -110,7 +109,7 @@
         val firstLineTail = state[FirstLineTailProp]
         val secondLineHead = state[SecondLineHeadProp]
         val secondLineTail = state[SecondLineTailProp]
-        val strokeWidth = ProgressIndicatorConstants.DefaultStrokeWidth.toPx()
+        val strokeWidth = ProgressIndicatorDefaults.StrokeWidth.toPx()
         drawLinearIndicatorBackground(backgroundColor, strokeWidth)
         if (firstLineHead - firstLineTail > 0) {
             drawLinearIndicator(
@@ -160,7 +159,7 @@
  * 0 to 360 degrees.
  *
  * By default there is no animation between [progress] values. You can use
- * [ProgressIndicatorConstants.DefaultProgressAnimationSpec] as the default recommended
+ * [ProgressIndicatorDefaults.ProgressAnimationSpec] as the default recommended
  * [AnimationSpec] when animating progress, such as in the following example:
  *
  * @sample androidx.compose.material.samples.CircularProgressIndicatorSample
@@ -175,7 +174,7 @@
     @FloatRange(from = 0.0, to = 1.0) progress: Float,
     modifier: Modifier = Modifier,
     color: Color = MaterialTheme.colors.primary,
-    strokeWidth: Dp = ProgressIndicatorConstants.DefaultStrokeWidth
+    strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth
 ) {
     val stroke = with(AmbientDensity.current) {
         Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Butt)
@@ -203,7 +202,7 @@
 fun CircularProgressIndicator(
     modifier: Modifier = Modifier,
     color: Color = MaterialTheme.colors.primary,
-    strokeWidth: Dp = ProgressIndicatorConstants.DefaultStrokeWidth
+    strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth
 ) {
     val stroke = with(AmbientDensity.current) {
         Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Square)
@@ -259,6 +258,13 @@
 /**
  * Contains the default values used for [LinearProgressIndicator] and [CircularProgressIndicator].
  */
+@Deprecated(
+    "ProgressIndicatorConstants has been replaced with ProgressIndicatorDefaults",
+    ReplaceWith(
+        "ProgressIndicatorDefaults",
+        "androidx.compose.material.ProgressIndicatorDefaults"
+    )
+)
 object ProgressIndicatorConstants {
     /**
      * Default stroke width for [CircularProgressIndicator], and default height for
@@ -288,6 +294,38 @@
     )
 }
 
+/**
+ * Contains the default values used for [LinearProgressIndicator] and [CircularProgressIndicator].
+ */
+object ProgressIndicatorDefaults {
+    /**
+     * Default stroke width for [CircularProgressIndicator], and default height for
+     * [LinearProgressIndicator].
+     *
+     * This can be customized with the `strokeWidth` parameter on [CircularProgressIndicator],
+     * and by passing a layout modifier setting the height for [LinearProgressIndicator].
+     */
+    val StrokeWidth = 4.dp
+
+    /**
+     * The default opacity applied to the indicator color to create the background color in a
+     * [LinearProgressIndicator].
+     */
+    const val IndicatorBackgroundOpacity = 0.24f
+
+    /**
+     * The default [AnimationSpec] that should be used when animating between progress in a
+     * determinate progress indicator.
+     */
+    val ProgressAnimationSpec = SpringSpec(
+        dampingRatio = Spring.DampingRatioNoBouncy,
+        stiffness = Spring.StiffnessVeryLow,
+        // The default threshold is 0.01, or 1% of the overall progress range, which is quite
+        // large and noticeable.
+        visibilityThreshold = 1 / 1000f
+    )
+}
+
 private fun DrawScope.drawDeterminateCircularIndicator(
     startAngle: Float,
     sweep: Float,
@@ -322,7 +360,7 @@
 // LinearProgressIndicator Material specs
 // TODO: there are currently 3 fixed widths in Android, should this be flexible? Material says
 // the width should be 240dp here.
-private val LinearIndicatorHeight = ProgressIndicatorConstants.DefaultStrokeWidth
+private val LinearIndicatorHeight = ProgressIndicatorDefaults.StrokeWidth
 private val LinearIndicatorWidth = 240.dp
 
 // CircularProgressIndicator Material specs
@@ -373,32 +411,28 @@
     }
 
     transition(fromState = 0, toState = 1) {
-        FirstLineHeadProp using repeatable(
-            iterations = Infinite,
+        FirstLineHeadProp using infiniteRepeatable(
             animation = keyframes {
                 durationMillis = LinearAnimationDuration
                 0f at FirstLineHeadDelay with FirstLineHeadEasing
                 1f at FirstLineHeadDuration + FirstLineHeadDelay
             }
         )
-        FirstLineTailProp using repeatable(
-            iterations = Infinite,
+        FirstLineTailProp using infiniteRepeatable(
             animation = keyframes {
                 durationMillis = LinearAnimationDuration
                 0f at FirstLineTailDelay with FirstLineTailEasing
                 1f at FirstLineTailDuration + FirstLineTailDelay
             }
         )
-        SecondLineHeadProp using repeatable(
-            iterations = Infinite,
+        SecondLineHeadProp using infiniteRepeatable(
             animation = keyframes {
                 durationMillis = LinearAnimationDuration
                 0f at SecondLineHeadDelay with SecondLineHeadEasing
                 1f at SecondLineHeadDuration + SecondLineHeadDelay
             }
         )
-        SecondLineTailProp using repeatable(
-            iterations = Infinite,
+        SecondLineTailProp using infiniteRepeatable(
             animation = keyframes {
                 durationMillis = LinearAnimationDuration
                 0f at SecondLineTailDelay with SecondLineTailEasing
@@ -462,30 +496,26 @@
     }
 
     transition(fromState = 0, toState = 1) {
-        IterationProp using repeatable(
-            iterations = Infinite,
+        IterationProp using infiniteRepeatable(
             animation = tween(
                 durationMillis = RotationDuration * RotationsPerCycle,
                 easing = LinearEasing
             )
         )
-        BaseRotationProp using repeatable(
-            iterations = Infinite,
+        BaseRotationProp using infiniteRepeatable(
             animation = tween(
                 durationMillis = RotationDuration,
                 easing = LinearEasing
             )
         )
-        HeadRotationProp using repeatable(
-            iterations = Infinite,
+        HeadRotationProp using infiniteRepeatable(
             animation = keyframes {
                 durationMillis = HeadAndTailAnimationDuration + HeadAndTailDelayDuration
                 0f at 0 with CircularEasing
                 JumpRotationAngle at HeadAndTailAnimationDuration
             }
         )
-        TailRotationProp using repeatable(
-            iterations = Infinite,
+        TailRotationProp using infiniteRepeatable(
             animation = keyframes {
                 durationMillis = HeadAndTailAnimationDuration + HeadAndTailDelayDuration
                 0f at HeadAndTailDelayDuration with CircularEasing
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
index 6e9d2ed..61ba138 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
@@ -66,7 +66,7 @@
  * [InteractionState] if you want to read the [InteractionState] and customize the appearance /
  * behavior of this RadioButton in different [Interaction]s.
  * @param colors [RadioButtonColors] that will be used to resolve the color used for this
- * RadioButton in different states. See [RadioButtonConstants.defaultColors].
+ * RadioButton in different states. See [RadioButtonDefaults.colors].
  */
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
@@ -76,7 +76,7 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     interactionState: InteractionState = remember { InteractionState() },
-    colors: RadioButtonColors = RadioButtonConstants.defaultColors()
+    colors: RadioButtonColors = RadioButtonDefaults.colors()
 ) {
     val dotRadius = animate(
         target = if (selected) RadioButtonDotSize / 2 else 0.dp,
@@ -106,7 +106,7 @@
 /**
  * Represents the color used by a [RadioButton] in different states.
  *
- * See [RadioButtonConstants.defaultColors] for the default implementation that follows Material
+ * See [RadioButtonDefaults.colors] for the default implementation that follows Material
  * specifications.
  */
 @ExperimentalMaterialApi
@@ -125,6 +125,13 @@
 /**
  * Constants used in [RadioButton].
  */
+@Deprecated(
+    "RadioButtonConstants has been replaced with RadioButtonDefaults",
+    ReplaceWith(
+        "RadioButtonDefaults",
+        "androidx.compose.material.RadioButtonDefaults"
+    )
+)
 object RadioButtonConstants {
     /**
      * Creates a [RadioButtonColors] that will animate between the provided colors according to
@@ -137,6 +144,13 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "RadioButtonConstants has been replaced with RadioButtonDefaults",
+        ReplaceWith(
+            "RadioButtonDefaults.colors(selectedColor, unselectedColor, disabledColor)",
+            "androidx.compose.material.RadioButtonDefaults"
+        )
+    )
     fun defaultColors(
         selectedColor: Color = MaterialTheme.colors.secondary,
         unselectedColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
@@ -154,6 +168,38 @@
     }
 }
 
+/**
+ * Defaults used in [RadioButton].
+ */
+object RadioButtonDefaults {
+    /**
+     * Creates a [RadioButtonColors] that will animate between the provided colors according to
+     * the Material specification.
+     *
+     * @param selectedColor the color to use for the RadioButton when selected and enabled.
+     * @param unselectedColor the color to use for the RadioButton when unselected and enabled.
+     * @param disabledColor the color to use for the RadioButton when disabled.
+     * @return the resulting [Color] used for the RadioButton
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun colors(
+        selectedColor: Color = MaterialTheme.colors.secondary,
+        unselectedColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
+        disabledColor: Color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
+    ): RadioButtonColors {
+        val clock = AmbientAnimationClock.current.asDisposableClock()
+        return remember(
+            selectedColor,
+            unselectedColor,
+            disabledColor,
+            clock
+        ) {
+            DefaultRadioButtonColors(selectedColor, unselectedColor, disabledColor, clock)
+        }
+    }
+}
+
 private fun DrawScope.drawRadio(color: Color, dotRadius: Dp) {
     val strokeWidth = RadioStrokeWidth.toPx()
     drawCircle(color, RadioRadius.toPx() - strokeWidth / 2, style = Stroke(strokeWidth))
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 33f7dd15..00f6b24 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
@@ -160,10 +160,10 @@
     drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
     drawerGesturesEnabled: Boolean = true,
     drawerShape: Shape = MaterialTheme.shapes.large,
-    drawerElevation: Dp = DrawerConstants.DefaultElevation,
+    drawerElevation: Dp = DrawerDefaults.Elevation,
     drawerBackgroundColor: Color = MaterialTheme.colors.surface,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
-    drawerScrimColor: Color = DrawerConstants.defaultScrimColor,
+    drawerScrimColor: Color = DrawerDefaults.scrimColor,
     backgroundColor: Color = MaterialTheme.colors.background,
     contentColor: Color = contentColorFor(backgroundColor),
     bodyContent: @Composable (PaddingValues) -> Unit
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index 31d5c18..463ab88 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -40,8 +40,8 @@
 import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.foundation.layout.preferredWidthIn
 import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.SliderConstants.InactiveTrackColorAlpha
-import androidx.compose.material.SliderConstants.TickColorAlpha
+import androidx.compose.material.SliderDefaults.InactiveTrackColorAlpha
+import androidx.compose.material.SliderDefaults.TickColorAlpha
 import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -196,6 +196,13 @@
 /**
  * Object to hold constants used by the [Slider]
  */
+@Deprecated(
+    "SliderConstants has been replaced with SliderDefaults",
+    ReplaceWith(
+        "SliderDefaults",
+        "androidx.compose.material.SliderDefaults"
+    )
+)
 object SliderConstants {
     /**
      * Default alpha of the inactive part of the track
@@ -208,6 +215,21 @@
     const val TickColorAlpha = 0.54f
 }
 
+/**
+ * Object to hold defaults used by [Slider]
+ */
+object SliderDefaults {
+    /**
+     * Default alpha of the inactive part of the track
+     */
+    const val InactiveTrackColorAlpha = 0.24f
+
+    /**
+     * Default alpha of the ticks that are drawn on top of the track
+     */
+    const val TickColorAlpha = 0.54f
+}
+
 @Composable
 private fun SliderImpl(
     positionFraction: Float,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
index 41a9958..dcdafed 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
@@ -59,7 +59,7 @@
  *
  * @param modifier modifiers for the the Snackbar layout
  * @param action action / button component to add as an action to the snackbar. Consider using
- * [SnackbarConstants.defaultActionPrimaryColor] as the color for the action, if you do not
+ * [SnackbarDefaults.primaryActionColor] as the color for the action, if you do not
  * have a predefined color you wish to use instead.
  * @param actionOnNewLine whether or not action should be put on the separate line. Recommended
  * for action with long action text
@@ -79,7 +79,7 @@
     action: @Composable (() -> Unit)? = null,
     actionOnNewLine: Boolean = false,
     shape: Shape = MaterialTheme.shapes.small,
-    backgroundColor: Color = SnackbarConstants.defaultBackgroundColor,
+    backgroundColor: Color = SnackbarDefaults.backgroundColor,
     contentColor: Color = MaterialTheme.colors.surface,
     elevation: Dp = 6.dp,
     text: @Composable () -> Unit
@@ -147,16 +147,16 @@
     modifier: Modifier = Modifier,
     actionOnNewLine: Boolean = false,
     shape: Shape = MaterialTheme.shapes.small,
-    backgroundColor: Color = SnackbarConstants.defaultBackgroundColor,
+    backgroundColor: Color = SnackbarDefaults.backgroundColor,
     contentColor: Color = MaterialTheme.colors.surface,
-    actionColor: Color = SnackbarConstants.defaultActionPrimaryColor,
+    actionColor: Color = SnackbarDefaults.primaryActionColor,
     elevation: Dp = 6.dp
 ) {
     val actionLabel = snackbarData.actionLabel
     val actionComposable: (@Composable () -> Unit)? = if (actionLabel != null) {
         {
             TextButton(
-                colors = ButtonConstants.defaultTextButtonColors(contentColor = actionColor),
+                colors = ButtonDefaults.textButtonColors(contentColor = actionColor),
                 onClick = { snackbarData.performAction() },
                 content = { Text(actionLabel) }
             )
@@ -179,6 +179,13 @@
 /**
  * Object to hold constants used by the [Snackbar]
  */
+@Deprecated(
+    "SnackbarConstants has been replaced with SnackbarDefaults",
+    ReplaceWith(
+        "SnackbarDefaults",
+        "androidx.compose.material.SnackbarDefaults"
+    )
+)
 object SnackbarConstants {
 
     /**
@@ -189,8 +196,8 @@
     /**
      * Default background color of the [Snackbar]
      */
-    @Composable
     val defaultBackgroundColor: Color
+        @Composable
         get() =
             MaterialTheme.colors.onSurface
                 .copy(alpha = SnackbarOverlayAlpha)
@@ -210,8 +217,57 @@
      * [MaterialTheme.colors] to attempt to reduce the contrast, and when in a dark theme this
      * function uses [Colors.primaryVariant].
      */
-    @Composable
     val defaultActionPrimaryColor: Color
+        @Composable
+        get() {
+            val colors = MaterialTheme.colors
+            return if (colors.isLight) {
+                val primary = colors.primary
+                val overlayColor = colors.surface.copy(alpha = 0.6f)
+
+                overlayColor.compositeOver(primary)
+            } else {
+                colors.primaryVariant
+            }
+        }
+}
+
+/**
+ * Object to hold defaults used by [Snackbar]
+ */
+object SnackbarDefaults {
+
+    /**
+     * Default alpha of the overlay applied to the [backgroundColor]
+     */
+    private const val SnackbarOverlayAlpha = 0.8f
+
+    /**
+     * Default background color of the [Snackbar]
+     */
+    val backgroundColor: Color
+        @Composable
+        get() =
+            MaterialTheme.colors.onSurface
+                .copy(alpha = SnackbarOverlayAlpha)
+                .compositeOver(MaterialTheme.colors.surface)
+
+    /**
+     * Provides a best-effort 'primary' color to be used as the primary color inside a [Snackbar].
+     * Given that [Snackbar]s have an 'inverted' theme, i.e in a light theme they appear dark, and
+     * in a dark theme they appear light, just using [Colors.primary] will not work, and has
+     * incorrect contrast.
+     *
+     * If your light theme has a corresponding dark theme, you should instead directly use
+     * [Colors.primary] from the dark theme when in a light theme, and use
+     * [Colors.primaryVariant] from the dark theme when in a dark theme.
+     *
+     * When in a light theme, this function applies a color overlay to [Colors.primary] from
+     * [MaterialTheme.colors] to attempt to reduce the contrast, and when in a dark theme this
+     * function uses [Colors.primaryVariant].
+     */
+    val primaryActionColor: Color
+        @Composable
         get() {
             val colors = MaterialTheme.colors
             return if (colors.isLight) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt
index b0b7b05..200bd21c2 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeToDismiss.kt
@@ -28,8 +28,8 @@
 import androidx.compose.material.DismissValue.Default
 import androidx.compose.material.DismissValue.DismissedToEnd
 import androidx.compose.material.DismissValue.DismissedToStart
-import androidx.compose.material.SwipeableConstants.StandardResistanceFactor
-import androidx.compose.material.SwipeableConstants.StiffResistanceFactor
+import androidx.compose.material.SwipeableDefaults.StandardResistanceFactor
+import androidx.compose.material.SwipeableDefaults.StiffResistanceFactor
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.savedinstancestate.Saver
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Swipeable.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Swipeable.kt
index 858066e..b8419e1 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Swipeable.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Swipeable.kt
@@ -25,10 +25,10 @@
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.foundation.InteractionState
 import androidx.compose.foundation.gestures.draggable
-import androidx.compose.material.SwipeableConstants.DefaultAnimationSpec
-import androidx.compose.material.SwipeableConstants.DefaultVelocityThreshold
-import androidx.compose.material.SwipeableConstants.StandardResistanceFactor
-import androidx.compose.material.SwipeableConstants.defaultResistanceConfig
+import androidx.compose.material.SwipeableDefaults.AnimationSpec
+import androidx.compose.material.SwipeableDefaults.VelocityThreshold
+import androidx.compose.material.SwipeableDefaults.StandardResistanceFactor
+import androidx.compose.material.SwipeableDefaults.resistanceConfig
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -73,7 +73,7 @@
 open class SwipeableState<T>(
     initialValue: T,
     clock: AnimationClockObservable,
-    internal val animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
     internal val confirmStateChange: (newValue: T) -> Boolean = { true }
 ) {
     /**
@@ -333,7 +333,7 @@
 @ExperimentalMaterialApi
 fun <T : Any> rememberSwipeableState(
     initialValue: T,
-    animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = AnimationSpec,
     confirmStateChange: (newValue: T) -> Boolean = { true }
 ): SwipeableState<T> {
     val clock = AmbientAnimationClock.current.asDisposableClock()
@@ -367,7 +367,7 @@
 internal fun <T : Any> rememberSwipeableStateFor(
     value: T,
     onValueChange: (T) -> Unit,
-    animationSpec: AnimationSpec<Float> = DefaultAnimationSpec
+    animationSpec: AnimationSpec<Float> = AnimationSpec
 ): SwipeableState<T> {
     val swipeableState = rememberSwipeableState(
         initialValue = value,
@@ -433,8 +433,8 @@
     reverseDirection: Boolean = false,
     interactionState: InteractionState? = null,
     thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
-    resistance: ResistanceConfig? = defaultResistanceConfig(anchors.keys),
-    velocityThreshold: Dp = DefaultVelocityThreshold
+    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+    velocityThreshold: Dp = VelocityThreshold
 ) = composed(
     inspectorInfo = debugInspectorInfo {
         name = "swipeable"
@@ -548,10 +548,10 @@
  *
  * The resistance basis is usually either the size of the component which [swipeable] is applied
  * to, or the distance between the minimum and maximum anchors. For a constructor in which the
- * resistance basis defaults to the latter, consider using [defaultResistanceConfig].
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
  *
  * You may specify different resistance factors for each bound. Consider using one of the default
- * resistance factors in [SwipeableConstants]: `StandardResistanceFactor` to convey that the user
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user
  * has run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe
  * this right now. Also, you can set either factor to 0 to disable resistance at that bound.
  *
@@ -673,6 +673,13 @@
 /**
  * Contains useful constants for [swipeable] and [SwipeableState].
  */
+@Deprecated(
+    "SwipeableConstants has been replaced with SwipeableDefaults",
+    ReplaceWith(
+        "SwipeableDefaults",
+        "androidx.compose.material.SwipeableDefaults"
+    )
+)
 object SwipeableConstants {
     /**
      * The default animation used by [SwipeableState].
@@ -712,4 +719,48 @@
             ResistanceConfig(basis, factorAtMin, factorAtMax)
         }
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Contains useful defaults for [swipeable] and [SwipeableState].
+ */
+object SwipeableDefaults {
+    /**
+     * The default animation used by [SwipeableState].
+     */
+    val AnimationSpec = SpringSpec<Float>()
+
+    /**
+     * The default velocity threshold (1.8 dp per millisecond) used by [swipeable].
+     */
+    val VelocityThreshold = 125.dp
+
+    /**
+     * A stiff resistance factor which indicates that swiping isn't available right now.
+     */
+    const val StiffResistanceFactor = 20f
+
+    /**
+     * A standard resistance factor which indicates that the user has run out of things to see.
+     */
+    const val StandardResistanceFactor = 10f
+
+    /**
+     * The default resistance config used by [swipeable].
+     *
+     * This returns `null` if there is one anchor. If there are at least two anchors, it returns
+     * a [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+     */
+    fun resistanceConfig(
+        anchors: Set<Float>,
+        factorAtMin: Float = StandardResistanceFactor,
+        factorAtMax: Float = StandardResistanceFactor
+    ): ResistanceConfig? {
+        return if (anchors.size <= 1) {
+            null
+        } else {
+            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+            ResistanceConfig(basis, factorAtMin, factorAtMax)
+        }
+    }
+}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
index 9881050..c60a39c 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
@@ -65,7 +65,7 @@
  * [InteractionState] if you want to read the [InteractionState] and customize the appearance /
  * behavior of this Switch in different [Interaction]s.
  * @param colors [SwitchColors] that will be used to determine the color of the thumb and track
- * in different states. See [SwitchConstants.defaultColors].
+ * in different states. See [SwitchDefaults.colors].
  */
 @Composable
 @OptIn(ExperimentalMaterialApi::class)
@@ -75,7 +75,7 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     interactionState: InteractionState = remember { InteractionState() },
-    colors: SwitchColors = SwitchConstants.defaultColors()
+    colors: SwitchColors = SwitchDefaults.colors()
 ) {
     val minBound = 0f
     val maxBound = with(AmbientDensity.current) { ThumbPathLength.toPx() }
@@ -117,7 +117,7 @@
 /**
  * Represents the colors used by a [Switch] in different states
  *
- * See [SwitchConstants.defaultColors] for the default implementation that follows Material
+ * See [SwitchDefaults.colors] for the default implementation that follows Material
  * specifications.
  */
 @ExperimentalMaterialApi
@@ -208,6 +208,13 @@
 /**
  * Contains the default values used by [Switch]
  */
+@Deprecated(
+    "SwitchConstants has been replaced with SwitchDefaults",
+    ReplaceWith(
+        "SwitchDefaults",
+        "androidx.compose.material.SwitchDefaults"
+    )
+)
 object SwitchConstants {
     /**
      * Creates a [SwitchColors] that represents the different colors used in a [Switch] in
@@ -228,6 +235,16 @@
      */
     @OptIn(ExperimentalMaterialApi::class)
     @Composable
+    @Deprecated(
+        "SwitchConstants has been replaced with SwitchDefaults",
+        ReplaceWith(
+            "SwitchDefaults.colors(checkedThumbColor, checkedTrackColor, checkedTrackAlpha, " +
+                "uncheckedThumbColor, uncheckedTrackColor, uncheckedTrackAlpha, " +
+                "disabledCheckedThumbColor, disabledCheckedTrackColor, " +
+                "disabledUncheckedThumbColor, disabledUncheckedTrackColor)",
+            "androidx.compose.material.SwitchDefaults"
+        )
+    )
     fun defaultColors(
         checkedThumbColor: Color = MaterialTheme.colors.secondaryVariant,
         checkedTrackColor: Color = checkedThumbColor,
@@ -260,6 +277,60 @@
 }
 
 /**
+ * Contains the default values used by [Switch]
+ */
+object SwitchDefaults {
+    /**
+     * Creates a [SwitchColors] that represents the different colors used in a [Switch] in
+     * different states.
+     *
+     * @param checkedThumbColor the color used for the thumb when enabled and checked
+     * @param checkedTrackColor the color used for the track when enabled and checked
+     * @param checkedTrackAlpha the alpha applied to [checkedTrackColor] and
+     * [disabledCheckedTrackColor]
+     * @param uncheckedThumbColor the color used for the thumb when enabled and unchecked
+     * @param uncheckedTrackColor the color used for the track when enabled and unchecked
+     * @param uncheckedTrackAlpha the alpha applied to [uncheckedTrackColor] and
+     * [disabledUncheckedTrackColor]
+     * @param disabledCheckedThumbColor the color used for the thumb when disabled and checked
+     * @param disabledCheckedTrackColor the color used for the track when disabled and checked
+     * @param disabledUncheckedThumbColor the color used for the thumb when disabled and unchecked
+     * @param disabledUncheckedTrackColor the color used for the track when disabled and unchecked
+     */
+    @OptIn(ExperimentalMaterialApi::class)
+    @Composable
+    fun colors(
+        checkedThumbColor: Color = MaterialTheme.colors.secondaryVariant,
+        checkedTrackColor: Color = checkedThumbColor,
+        checkedTrackAlpha: Float = 0.54f,
+        uncheckedThumbColor: Color = MaterialTheme.colors.surface,
+        uncheckedTrackColor: Color = MaterialTheme.colors.onSurface,
+        uncheckedTrackAlpha: Float = 0.38f,
+        disabledCheckedThumbColor: Color = checkedThumbColor
+            .copy(alpha = ContentAlpha.disabled)
+            .compositeOver(MaterialTheme.colors.surface),
+        disabledCheckedTrackColor: Color = checkedTrackColor
+            .copy(alpha = ContentAlpha.disabled)
+            .compositeOver(MaterialTheme.colors.surface),
+        disabledUncheckedThumbColor: Color = uncheckedThumbColor
+            .copy(alpha = ContentAlpha.disabled)
+            .compositeOver(MaterialTheme.colors.surface),
+        disabledUncheckedTrackColor: Color = uncheckedTrackColor
+            .copy(alpha = ContentAlpha.disabled)
+            .compositeOver(MaterialTheme.colors.surface)
+    ): SwitchColors = DefaultSwitchColors(
+        checkedThumbColor = checkedThumbColor,
+        checkedTrackColor = checkedTrackColor.copy(alpha = checkedTrackAlpha),
+        uncheckedThumbColor = uncheckedThumbColor,
+        uncheckedTrackColor = uncheckedTrackColor.copy(alpha = uncheckedTrackAlpha),
+        disabledCheckedThumbColor = disabledCheckedThumbColor,
+        disabledCheckedTrackColor = disabledCheckedTrackColor.copy(alpha = checkedTrackAlpha),
+        disabledUncheckedThumbColor = disabledUncheckedThumbColor,
+        disabledUncheckedTrackColor = disabledUncheckedTrackColor.copy(alpha = uncheckedTrackAlpha)
+    )
+}
+
+/**
  * Default [SwitchColors] implementation.
  */
 @OptIn(ExperimentalMaterialApi::class)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
index 175501d..8d4ed29 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
@@ -165,6 +165,13 @@
 /**
  * Contains default values used by tabs from the Material specification.
  */
+@Deprecated(
+    "TabConstants has been replaced with TabDefaults",
+    ReplaceWith(
+        "TabDefaults",
+        "androidx.compose.material.TabDefaults"
+    )
+)
 object TabConstants {
     /**
      * Default [Divider], which will be positioned at the bottom of the [TabRow], underneath the
@@ -254,6 +261,98 @@
     val DefaultScrollableTabRowPadding = 52.dp
 }
 
+/**
+ * Contains default values used by tabs from the Material specification.
+ */
+object TabDefaults {
+    /**
+     * Default [Divider], which will be positioned at the bottom of the [TabRow], underneath the
+     * indicator.
+     *
+     * @param modifier modifier for the divider's layout
+     * @param thickness thickness of the divider
+     * @param color color of the divider
+     */
+    @Composable
+    fun Divider(
+        modifier: Modifier = Modifier,
+        thickness: Dp = DividerThickness,
+        color: Color = AmbientContentColor.current.copy(alpha = DividerOpacity)
+    ) {
+        androidx.compose.material.Divider(modifier = modifier, thickness = thickness, color = color)
+    }
+
+    /**
+     * Default indicator, which will be positioned at the bottom of the [TabRow], on top of the
+     * divider.
+     *
+     * @param modifier modifier for the indicator's layout
+     * @param height height of the indicator
+     * @param color color of the indicator
+     */
+    @Composable
+    fun Indicator(
+        modifier: Modifier = Modifier,
+        height: Dp = IndicatorHeight,
+        color: Color = AmbientContentColor.current
+    ) {
+        Box(
+            modifier
+                .fillMaxWidth()
+                .preferredHeight(height)
+                .background(color = color)
+        )
+    }
+
+    /**
+     * [Modifier] that takes up all the available width inside the [TabRow], and then animates
+     * the offset of the indicator it is applied to, depending on the [currentTabPosition].
+     *
+     * @param currentTabPosition [TabPosition] of the currently selected tab. This is used to
+     * calculate the offset of the indicator this modifier is applied to, as well as its width.
+     */
+    fun Modifier.tabIndicatorOffset(
+        currentTabPosition: TabPosition
+    ): Modifier = composed(
+        inspectorInfo = debugInspectorInfo {
+            name = "tabIndicatorOffset"
+            value = currentTabPosition
+        }
+    ) {
+        // TODO: should we animate the width of the indicator as it moves between tabs of different
+        // sizes inside a scrollable tab row?
+        val currentTabWidth = currentTabPosition.width
+        val indicatorOffset = animate(
+            target = currentTabPosition.left,
+            animSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
+        )
+        fillMaxWidth()
+            .wrapContentSize(Alignment.BottomStart)
+            .offset(x = indicatorOffset)
+            .preferredWidth(currentTabWidth)
+    }
+
+    /**
+     * Default opacity for the color of [Divider]
+     */
+    const val DividerOpacity = 0.12f
+
+    /**
+     * Default thickness for [Divider]
+     */
+    val DividerThickness = 1.dp
+
+    /**
+     * Default height for [Indicator]
+     */
+    val IndicatorHeight = 2.dp
+
+    /**
+     * The default padding from the starting edge before a tab in a [ScrollableTabRow].
+     */
+    val ScrollableTabRowPadding = 52.dp
+}
+
 private val TabTintColor = ColorPropKey()
 
 /**
@@ -396,7 +495,7 @@
 
     // Total offset between the last text baseline and the bottom of the Tab layout
     val totalOffset = with(density) {
-        baselineOffset.toIntPx() + TabConstants.DefaultIndicatorHeight.toIntPx()
+        baselineOffset.toIntPx() + TabDefaults.IndicatorHeight.toIntPx()
     }
 
     val textPlaceableY = tabHeight - lastBaseline - totalOffset
@@ -425,7 +524,7 @@
 
     // Total offset between the last text baseline and the bottom of the Tab layout
     val textOffset = with(density) {
-        baselineOffset.toIntPx() + TabConstants.DefaultIndicatorHeight.toIntPx()
+        baselineOffset.toIntPx() + TabDefaults.IndicatorHeight.toIntPx()
     }
 
     // How much space there is between the top of the icon (essentially the top of this layout)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
index 4486035..04c42c9 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
@@ -24,7 +24,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.rememberScrollState
-import androidx.compose.material.TabConstants.defaultTabIndicatorOffset
+import androidx.compose.material.TabDefaults.tabIndicatorOffset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.remember
@@ -69,7 +69,7 @@
  *
  * @sample androidx.compose.material.samples.FancyIndicator
  *
- * We can reuse [TabConstants.defaultTabIndicatorOffset] and just provide this indicator,
+ * We can reuse [TabDefaults.tabIndicatorOffset] and just provide this indicator,
  * as we aren't changing how the size and position of the indicator changes between tabs:
  *
  * @sample androidx.compose.material.samples.FancyIndicatorTabs
@@ -96,9 +96,9 @@
  * Defaults to either the matching `onFoo` color for [backgroundColor], or if [backgroundColor] is
  * not a color from the theme, this will keep the same value set above this TabRow.
  * @param indicator the indicator that represents which tab is currently selected. By default this
- * will be a [TabConstants.DefaultIndicator], using a [TabConstants.defaultTabIndicatorOffset]
+ * will be a [TabDefaults.Indicator], using a [TabDefaults.tabIndicatorOffset]
  * modifier to animate its position. Note that this indicator will be forced to fill up the
- * entire TabRow, so you should use [TabConstants.defaultTabIndicatorOffset] or similar to
+ * entire TabRow, so you should use [TabDefaults.tabIndicatorOffset] or similar to
  * animate the actual drawn indicator inside this space, and provide an offset from the start.
  * @param divider the divider displayed at the bottom of the TabRow. This provides a layer of
  * separation between the TabRow and the content displayed underneath.
@@ -113,12 +113,12 @@
     backgroundColor: Color = MaterialTheme.colors.primarySurface,
     contentColor: Color = contentColorFor(backgroundColor),
     indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = { tabPositions ->
-        TabConstants.DefaultIndicator(
-            Modifier.defaultTabIndicatorOffset(tabPositions[selectedTabIndex])
+        TabDefaults.Indicator(
+            Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
         )
     },
     divider: @Composable () -> Unit = {
-        TabConstants.DefaultDivider()
+        TabDefaults.Divider()
     },
     tabs: @Composable () -> Unit
 ) {
@@ -176,9 +176,9 @@
  * the tabs inside the ScrollableTabRow. This padding helps inform the user that this tab row can
  * be scrolled, unlike a [TabRow].
  * @param indicator the indicator that represents which tab is currently selected. By default this
- * will be a [TabConstants.DefaultIndicator], using a [TabConstants.defaultTabIndicatorOffset]
+ * will be a [TabDefaults.Indicator], using a [TabDefaults.tabIndicatorOffset]
  * modifier to animate its position. Note that this indicator will be forced to fill up the
- * entire ScrollableTabRow, so you should use [TabConstants.defaultTabIndicatorOffset] or similar to
+ * entire ScrollableTabRow, so you should use [TabDefaults.tabIndicatorOffset] or similar to
  * animate the actual drawn indicator inside this space, and provide an offset from the start.
  * @param divider the divider displayed at the bottom of the ScrollableTabRow. This provides a layer
  * of separation between the ScrollableTabRow and the content displayed underneath.
@@ -192,14 +192,14 @@
     modifier: Modifier = Modifier,
     backgroundColor: Color = MaterialTheme.colors.primarySurface,
     contentColor: Color = contentColorFor(backgroundColor),
-    edgePadding: Dp = TabConstants.DefaultScrollableTabRowPadding,
+    edgePadding: Dp = TabDefaults.ScrollableTabRowPadding,
     indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = { tabPositions ->
-        TabConstants.DefaultIndicator(
-            Modifier.defaultTabIndicatorOffset(tabPositions[selectedTabIndex])
+        TabDefaults.Indicator(
+            Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
         )
     },
     divider: @Composable () -> Unit = {
-        TabConstants.DefaultDivider()
+        TabDefaults.Divider()
     },
     tabs: @Composable () -> Unit
 ) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
index 78e0f16..1bacb70 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
@@ -25,6 +25,7 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.transition
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Interaction
 import androidx.compose.foundation.InteractionState
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
@@ -35,16 +36,11 @@
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Providers
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.focus.ExperimentalFocus
 import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.isFocused
-import androidx.compose.ui.focusObserver
 import androidx.compose.ui.focusRequester
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
@@ -113,7 +109,7 @@
 
     val keyboardController: Ref<SoftwareKeyboardController> = remember { Ref() }
 
-    var isFocused by remember { mutableStateOf(false) }
+    val isFocused = interactionState.contains(Interaction.Focused)
     val inputState = when {
         isFocused -> InputPhase.Focused
         value.text.isEmpty() -> InputPhase.UnfocusedEmpty
@@ -135,6 +131,7 @@
                 visualTransformation = visualTransformation,
                 keyboardOptions = keyboardOptions,
                 maxLines = maxLines,
+                interactionState = interactionState,
                 onImeActionPerformed = {
                     onImeActionPerformed(it, keyboardController.value)
                 },
@@ -150,7 +147,6 @@
     val focusRequester = FocusRequester()
     val textFieldModifier = modifier
         .focusRequester(focusRequester)
-        .focusObserver { isFocused = it.isFocused }
         .let {
             it.clickable(interactionState = interactionState, indication = null) {
                 focusRequester.requestFocus()
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 dba48a2..704b120 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
@@ -143,7 +143,7 @@
             "Primary composable lambda parameter not named `content`",
             "Composable functions with only one composable lambda parameter should use the name " +
                 "`content` for the parameter.",
-            Category.CORRECTNESS, 3, Severity.WARNING,
+            Category.CORRECTNESS, 3, Severity.IGNORE,
             Implementation(
                 ComposableLambdaParameterDetector::class.java,
                 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
@@ -156,7 +156,7 @@
             "Composable functions with only one composable lambda parameter should place the " +
                 "parameter at the end of the parameter list, so it can be used as a trailing " +
                 "lambda.",
-            Category.CORRECTNESS, 3, Severity.WARNING,
+            Category.CORRECTNESS, 3, Severity.IGNORE,
             Implementation(
                 ComposableLambdaParameterDetector::class.java,
                 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
diff --git a/compose/runtime/runtime-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RestorableStateHolder.kt b/compose/runtime/runtime-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RestorableStateHolder.kt
index ad90774..f72bd7f 100644
--- a/compose/runtime/runtime-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RestorableStateHolder.kt
+++ b/compose/runtime/runtime-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RestorableStateHolder.kt
@@ -104,7 +104,7 @@
                 content = content
             )
             onActive {
-                require(key !in registryHolders)
+                require(key !in registryHolders) { "Key $key was used multiple times " }
                 savedStates -= key
                 registryHolders[key] = registryHolder
                 onDispose {
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 835bdc3..2c59a67 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.compose.runtime {
 
-  @androidx.compose.runtime.ExperimentalComposeApi public abstract class AbstractApplier<T> implements androidx.compose.runtime.Applier<T> {
+  public abstract class AbstractApplier<T> implements androidx.compose.runtime.Applier<T> {
     ctor public AbstractApplier(T? root);
     method public final void clear();
     method public void down(T? node);
@@ -24,8 +24,8 @@
   }
 
   @androidx.compose.runtime.Stable public abstract sealed class Ambient<T> {
-    method public final inline T! getCurrent();
-    property public final inline T! current;
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final inline T! getCurrent();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final inline T! current;
   }
 
   public final class AmbientKt {
@@ -41,11 +41,12 @@
     property public final boolean valid;
   }
 
-  @androidx.compose.runtime.ExperimentalComposeApi public interface Applier<N> {
+  public interface Applier<N> {
     method public void clear();
     method public void down(N? node);
     method public N! getCurrent();
-    method public void insert(int index, N? instance);
+    method public void insertBottomUp(int index, N? instance);
+    method public void insertTopDown(int index, N? instance);
     method public void move(int from, int to, int count);
     method public default void onBeginChanges();
     method public default void onEndChanges();
@@ -61,10 +62,10 @@
     method public void onDispose(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
   }
 
-  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
   }
 
-  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ComposableContract {
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ComposableContract {
     method public abstract boolean preventCapture() default false;
     method public abstract boolean readonly() default false;
     method public abstract boolean restartable() default true;
@@ -84,7 +85,7 @@
   }
 
   public final class Composer<N> {
-    ctor public Composer(androidx.compose.runtime.SlotTable slotTable, @androidx.compose.runtime.ComposeCompilerApi androidx.compose.runtime.Applier<N> applier, androidx.compose.runtime.CompositionReference parentReference);
+    ctor public Composer(androidx.compose.runtime.SlotTable slotTable, @kotlin.PublishedApi androidx.compose.runtime.Applier<N> applier, androidx.compose.runtime.CompositionReference parentReference);
     method @androidx.compose.runtime.InternalComposeApi public void applyChanges();
     method @androidx.compose.runtime.ComposeCompilerApi public inline <T> T! cache(boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(Object? value);
@@ -97,23 +98,18 @@
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(double value);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(int value);
     method @androidx.compose.runtime.InternalComposeApi public void collectKeySourceInformation();
+    method @androidx.compose.runtime.InternalComposeApi public void collectParameterInformation();
     method @androidx.compose.runtime.InternalComposeApi public void composeInitial(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method @androidx.compose.runtime.ComposeCompilerApi public <T extends N> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
-    method @androidx.compose.runtime.ComposeCompilerApi public void emitNode(Object? node);
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
-    method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
-    method public androidx.compose.runtime.Applier<N> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method public int getCurrentCompoundKeyHash();
     method public boolean getDefaultsInvalid();
-    method public boolean getInserting();
     method public boolean getSkipping();
     method public androidx.compose.runtime.SlotTable getSlotTable();
     method @androidx.compose.runtime.ComposeCompilerApi public Object joinKey(Object? left, Object? right);
-    method @androidx.compose.runtime.ComposeCompilerApi public Object? nextSlot();
     method @androidx.compose.runtime.InternalComposeApi public boolean recompose();
     method @androidx.compose.runtime.InternalComposeApi public void recordModificationsOf(java.util.Set<?> values);
     method @androidx.compose.runtime.InternalComposeApi public void recordReadOf(Object value);
@@ -123,23 +119,19 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startRestartGroup(int key);
     method @androidx.compose.runtime.ComposeCompilerApi public void startRestartGroup(int key, String? sourceInformation);
-    method @androidx.compose.runtime.ComposeCompilerApi public N! useNode();
-    property public final androidx.compose.runtime.Applier<N> applier;
     property @org.jetbrains.annotations.TestOnly public final kotlin.coroutines.CoroutineContext applyCoroutineContext;
     property public final int currentCompoundKeyHash;
     property public final boolean defaultsInvalid;
-    property public final boolean inserting;
     property public final boolean skipping;
     property public final androidx.compose.runtime.SlotTable slotTable;
   }
 
   public final class ComposerKt {
-    method public static androidx.compose.runtime.Composer<?> getCurrentComposer();
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.Composer<?> getCurrentComposer();
   }
 
   public interface Composition {
@@ -178,7 +170,7 @@
 
   public final class EffectsKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.CompositionReference compositionReference();
-    method public static kotlin.jvm.functions.Function0<kotlin.Unit> getInvalidate();
+    method @androidx.compose.runtime.Composable public static kotlin.jvm.functions.Function0<kotlin.Unit> getInvalidate();
     method @androidx.compose.runtime.Composable public static void onActive(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
     method @androidx.compose.runtime.Composable public static inline void onCommit(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
     method @androidx.compose.runtime.Composable public static <V1> void onCommit(V1? v1, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
@@ -273,19 +265,6 @@
   @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface NoLiveLiterals {
   }
 
-  public final class ObserverMap<K, V> {
-    ctor public ObserverMap();
-    method public void add(K key, V value);
-    method public void clear();
-    method public void clearValues(kotlin.jvm.functions.Function1<? super V,java.lang.Boolean> predicate);
-    method public boolean contains(K key, V value);
-    method public operator java.util.List<V> get(Iterable<? extends K> keys);
-    method public java.util.List<V> getValueOf(K key);
-    method public void remove(K key);
-    method public void remove(K key, V value);
-    method public void removeValue(V value);
-  }
-
   public final class ProduceStateKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> produceState(T? initialValue, @kotlin.BuilderInference kotlin.jvm.functions.Function2<? super androidx.compose.runtime.ProduceStateScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> producer);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> produceState(T? initialValue, Object? subject, @kotlin.BuilderInference kotlin.jvm.functions.Function2<? super androidx.compose.runtime.ProduceStateScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> producer);
@@ -359,12 +338,7 @@
   }
 
   public final class SkippableUpdater<T> {
-    ctor public SkippableUpdater(androidx.compose.runtime.Composer<?> composer, T? node);
-    method public androidx.compose.runtime.Composer<?> getComposer();
-    method public T! getNode();
     method public inline void update(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,kotlin.Unit> block);
-    property public final androidx.compose.runtime.Composer<?> composer;
-    property public final T! node;
   }
 
   @androidx.compose.runtime.InternalComposeApi public final class SlotReader {
@@ -545,16 +519,11 @@
   }
 
   public final class Updater<T> {
-    ctor public Updater(androidx.compose.runtime.Composer<?> composer, T? node);
-    method public androidx.compose.runtime.Composer<?> getComposer();
-    method public T! getNode();
     method public inline void reconcile(kotlin.jvm.functions.Function1<? super T,kotlin.Unit> block);
     method public inline void set(int value, kotlin.jvm.functions.Function2<? super T,? super java.lang.Integer,kotlin.Unit> block);
     method public inline <reified V> void set(V? value, kotlin.jvm.functions.Function2<? super T,? super V,? extends kotlin.Unit> block);
     method public inline void update(int value, kotlin.jvm.functions.Function2<? super T,? super java.lang.Integer,kotlin.Unit> block);
     method public inline <reified V> void update(V? value, kotlin.jvm.functions.Function2<? super T,? super V,? extends kotlin.Unit> block);
-    property public final androidx.compose.runtime.Composer<?> composer;
-    property public final T! node;
   }
 
 }
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 835bdc3..2c59a67 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.compose.runtime {
 
-  @androidx.compose.runtime.ExperimentalComposeApi public abstract class AbstractApplier<T> implements androidx.compose.runtime.Applier<T> {
+  public abstract class AbstractApplier<T> implements androidx.compose.runtime.Applier<T> {
     ctor public AbstractApplier(T? root);
     method public final void clear();
     method public void down(T? node);
@@ -24,8 +24,8 @@
   }
 
   @androidx.compose.runtime.Stable public abstract sealed class Ambient<T> {
-    method public final inline T! getCurrent();
-    property public final inline T! current;
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final inline T! getCurrent();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final inline T! current;
   }
 
   public final class AmbientKt {
@@ -41,11 +41,12 @@
     property public final boolean valid;
   }
 
-  @androidx.compose.runtime.ExperimentalComposeApi public interface Applier<N> {
+  public interface Applier<N> {
     method public void clear();
     method public void down(N? node);
     method public N! getCurrent();
-    method public void insert(int index, N? instance);
+    method public void insertBottomUp(int index, N? instance);
+    method public void insertTopDown(int index, N? instance);
     method public void move(int from, int to, int count);
     method public default void onBeginChanges();
     method public default void onEndChanges();
@@ -61,10 +62,10 @@
     method public void onDispose(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
   }
 
-  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
   }
 
-  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ComposableContract {
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ComposableContract {
     method public abstract boolean preventCapture() default false;
     method public abstract boolean readonly() default false;
     method public abstract boolean restartable() default true;
@@ -84,7 +85,7 @@
   }
 
   public final class Composer<N> {
-    ctor public Composer(androidx.compose.runtime.SlotTable slotTable, @androidx.compose.runtime.ComposeCompilerApi androidx.compose.runtime.Applier<N> applier, androidx.compose.runtime.CompositionReference parentReference);
+    ctor public Composer(androidx.compose.runtime.SlotTable slotTable, @kotlin.PublishedApi androidx.compose.runtime.Applier<N> applier, androidx.compose.runtime.CompositionReference parentReference);
     method @androidx.compose.runtime.InternalComposeApi public void applyChanges();
     method @androidx.compose.runtime.ComposeCompilerApi public inline <T> T! cache(boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(Object? value);
@@ -97,23 +98,18 @@
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(double value);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(int value);
     method @androidx.compose.runtime.InternalComposeApi public void collectKeySourceInformation();
+    method @androidx.compose.runtime.InternalComposeApi public void collectParameterInformation();
     method @androidx.compose.runtime.InternalComposeApi public void composeInitial(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method @androidx.compose.runtime.ComposeCompilerApi public <T extends N> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
-    method @androidx.compose.runtime.ComposeCompilerApi public void emitNode(Object? node);
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
-    method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
-    method public androidx.compose.runtime.Applier<N> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method public int getCurrentCompoundKeyHash();
     method public boolean getDefaultsInvalid();
-    method public boolean getInserting();
     method public boolean getSkipping();
     method public androidx.compose.runtime.SlotTable getSlotTable();
     method @androidx.compose.runtime.ComposeCompilerApi public Object joinKey(Object? left, Object? right);
-    method @androidx.compose.runtime.ComposeCompilerApi public Object? nextSlot();
     method @androidx.compose.runtime.InternalComposeApi public boolean recompose();
     method @androidx.compose.runtime.InternalComposeApi public void recordModificationsOf(java.util.Set<?> values);
     method @androidx.compose.runtime.InternalComposeApi public void recordReadOf(Object value);
@@ -123,23 +119,19 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startRestartGroup(int key);
     method @androidx.compose.runtime.ComposeCompilerApi public void startRestartGroup(int key, String? sourceInformation);
-    method @androidx.compose.runtime.ComposeCompilerApi public N! useNode();
-    property public final androidx.compose.runtime.Applier<N> applier;
     property @org.jetbrains.annotations.TestOnly public final kotlin.coroutines.CoroutineContext applyCoroutineContext;
     property public final int currentCompoundKeyHash;
     property public final boolean defaultsInvalid;
-    property public final boolean inserting;
     property public final boolean skipping;
     property public final androidx.compose.runtime.SlotTable slotTable;
   }
 
   public final class ComposerKt {
-    method public static androidx.compose.runtime.Composer<?> getCurrentComposer();
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.Composer<?> getCurrentComposer();
   }
 
   public interface Composition {
@@ -178,7 +170,7 @@
 
   public final class EffectsKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.CompositionReference compositionReference();
-    method public static kotlin.jvm.functions.Function0<kotlin.Unit> getInvalidate();
+    method @androidx.compose.runtime.Composable public static kotlin.jvm.functions.Function0<kotlin.Unit> getInvalidate();
     method @androidx.compose.runtime.Composable public static void onActive(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
     method @androidx.compose.runtime.Composable public static inline void onCommit(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
     method @androidx.compose.runtime.Composable public static <V1> void onCommit(V1? v1, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
@@ -273,19 +265,6 @@
   @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface NoLiveLiterals {
   }
 
-  public final class ObserverMap<K, V> {
-    ctor public ObserverMap();
-    method public void add(K key, V value);
-    method public void clear();
-    method public void clearValues(kotlin.jvm.functions.Function1<? super V,java.lang.Boolean> predicate);
-    method public boolean contains(K key, V value);
-    method public operator java.util.List<V> get(Iterable<? extends K> keys);
-    method public java.util.List<V> getValueOf(K key);
-    method public void remove(K key);
-    method public void remove(K key, V value);
-    method public void removeValue(V value);
-  }
-
   public final class ProduceStateKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> produceState(T? initialValue, @kotlin.BuilderInference kotlin.jvm.functions.Function2<? super androidx.compose.runtime.ProduceStateScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> producer);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.runtime.State<T> produceState(T? initialValue, Object? subject, @kotlin.BuilderInference kotlin.jvm.functions.Function2<? super androidx.compose.runtime.ProduceStateScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> producer);
@@ -359,12 +338,7 @@
   }
 
   public final class SkippableUpdater<T> {
-    ctor public SkippableUpdater(androidx.compose.runtime.Composer<?> composer, T? node);
-    method public androidx.compose.runtime.Composer<?> getComposer();
-    method public T! getNode();
     method public inline void update(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,kotlin.Unit> block);
-    property public final androidx.compose.runtime.Composer<?> composer;
-    property public final T! node;
   }
 
   @androidx.compose.runtime.InternalComposeApi public final class SlotReader {
@@ -545,16 +519,11 @@
   }
 
   public final class Updater<T> {
-    ctor public Updater(androidx.compose.runtime.Composer<?> composer, T? node);
-    method public androidx.compose.runtime.Composer<?> getComposer();
-    method public T! getNode();
     method public inline void reconcile(kotlin.jvm.functions.Function1<? super T,kotlin.Unit> block);
     method public inline void set(int value, kotlin.jvm.functions.Function2<? super T,? super java.lang.Integer,kotlin.Unit> block);
     method public inline <reified V> void set(V? value, kotlin.jvm.functions.Function2<? super T,? super V,? extends kotlin.Unit> block);
     method public inline void update(int value, kotlin.jvm.functions.Function2<? super T,? super java.lang.Integer,kotlin.Unit> block);
     method public inline <reified V> void update(V? value, kotlin.jvm.functions.Function2<? super T,? super V,? extends kotlin.Unit> block);
-    property public final androidx.compose.runtime.Composer<?> composer;
-    property public final T! node;
   }
 
 }
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 3d9fadf..875970d 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.compose.runtime {
 
-  @androidx.compose.runtime.ExperimentalComposeApi public abstract class AbstractApplier<T> implements androidx.compose.runtime.Applier<T> {
+  public abstract class AbstractApplier<T> implements androidx.compose.runtime.Applier<T> {
     ctor public AbstractApplier(T? root);
     method public final void clear();
     method public void down(T? node);
@@ -24,8 +24,8 @@
   }
 
   @androidx.compose.runtime.Stable public abstract sealed class Ambient<T> {
-    method public final inline T! getCurrent();
-    property public final inline T! current;
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final inline T! getCurrent();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public final inline T! current;
   }
 
   public final class AmbientKt {
@@ -41,11 +41,12 @@
     property public final boolean valid;
   }
 
-  @androidx.compose.runtime.ExperimentalComposeApi public interface Applier<N> {
+  public interface Applier<N> {
     method public void clear();
     method public void down(N? node);
     method public N! getCurrent();
-    method public void insert(int index, N? instance);
+    method public void insertBottomUp(int index, N? instance);
+    method public void insertTopDown(int index, N? instance);
     method public void move(int from, int to, int count);
     method public default void onBeginChanges();
     method public default void onEndChanges();
@@ -61,10 +62,10 @@
     method public void onDispose(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
   }
 
-  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Composable {
   }
 
-  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ComposableContract {
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ComposableContract {
     method public abstract boolean preventCapture() default false;
     method public abstract boolean readonly() default false;
     method public abstract boolean restartable() default true;
@@ -84,7 +85,7 @@
   }
 
   public final class Composer<N> {
-    ctor public Composer(androidx.compose.runtime.SlotTable slotTable, @androidx.compose.runtime.ComposeCompilerApi androidx.compose.runtime.Applier<N> applier, androidx.compose.runtime.CompositionReference parentReference);
+    ctor public Composer(androidx.compose.runtime.SlotTable slotTable, @kotlin.PublishedApi androidx.compose.runtime.Applier<N> applier, androidx.compose.runtime.CompositionReference parentReference);
     method @androidx.compose.runtime.InternalComposeApi public void applyChanges();
     method @androidx.compose.runtime.ComposeCompilerApi public inline <T> T! cache(boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(Object? value);
@@ -97,24 +98,22 @@
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(double value);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(int value);
     method @androidx.compose.runtime.InternalComposeApi public void collectKeySourceInformation();
+    method @androidx.compose.runtime.InternalComposeApi public void collectParameterInformation();
     method @androidx.compose.runtime.InternalComposeApi public void composeInitial(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method @androidx.compose.runtime.ComposeCompilerApi @kotlin.PublishedApi internal <T> T! consume(androidx.compose.runtime.Ambient<T> key);
-    method @androidx.compose.runtime.ComposeCompilerApi public <T extends N> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
-    method @androidx.compose.runtime.ComposeCompilerApi public void emitNode(Object? node);
+    method @kotlin.PublishedApi internal <T> T! consume(androidx.compose.runtime.Ambient<T> key);
+    method @kotlin.PublishedApi internal void emitNode(Object? node);
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
-    method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
+    method @kotlin.PublishedApi internal void endNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
-    method public androidx.compose.runtime.Applier<N> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method public int getCurrentCompoundKeyHash();
     method public boolean getDefaultsInvalid();
-    method public boolean getInserting();
     method public boolean getSkipping();
     method public androidx.compose.runtime.SlotTable getSlotTable();
     method @androidx.compose.runtime.ComposeCompilerApi public Object joinKey(Object? left, Object? right);
-    method @androidx.compose.runtime.ComposeCompilerApi public Object? nextSlot();
+    method @kotlin.PublishedApi internal Object? nextSlot();
     method @androidx.compose.runtime.InternalComposeApi public boolean recompose();
     method @androidx.compose.runtime.InternalComposeApi public void recordModificationsOf(java.util.Set<?> values);
     method @androidx.compose.runtime.InternalComposeApi public void recordReadOf(Object value);
@@ -124,24 +123,23 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
+    method @kotlin.PublishedApi internal void startNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startRestartGroup(int key);
     method @androidx.compose.runtime.ComposeCompilerApi public void startRestartGroup(int key, String? sourceInformation);
-    method @androidx.compose.runtime.ComposeCompilerApi @kotlin.PublishedApi internal void updateValue(Object? value);
-    method @androidx.compose.runtime.ComposeCompilerApi public N! useNode();
-    property public final androidx.compose.runtime.Applier<N> applier;
+    method @kotlin.PublishedApi internal void updateValue(Object? value);
+    method @kotlin.PublishedApi internal N! useNode();
     property @org.jetbrains.annotations.TestOnly public final kotlin.coroutines.CoroutineContext applyCoroutineContext;
     property public final int currentCompoundKeyHash;
     property public final boolean defaultsInvalid;
-    property public final boolean inserting;
     property public final boolean skipping;
     property public final androidx.compose.runtime.SlotTable slotTable;
+    field @kotlin.PublishedApi internal boolean inserting;
   }
 
   public final class ComposerKt {
-    method public static androidx.compose.runtime.Composer<?> getCurrentComposer();
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.Composer<?> getCurrentComposer();
     field @kotlin.PublishedApi internal static final androidx.compose.runtime.OpaqueKey ambientMap;
     field @kotlin.PublishedApi internal static final int ambientMapKey = 202; // 0xca
     field @kotlin.PublishedApi internal static final androidx.compose.runtime.OpaqueKey invocation;
@@ -198,7 +196,7 @@
 
   public final class EffectsKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.CompositionReference compositionReference();
-    method public static kotlin.jvm.functions.Function0<kotlin.Unit> getInvalidate();
+    method @androidx.compose.runtime.Composable public static kotlin.jvm.functions.Function0<kotlin.Unit> getInvalidate();
     method @androidx.compose.runtime.Composable public static void onActive(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
     method @androidx.compose.runtime.Composable public static inline void onCommit(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
     method @androidx.compose.runtime.Composable public static <V1> void onCommit(V1? v1, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> callback);
@@ -294,19 +292,6 @@
   @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface NoLiveLiterals {
   }
 
-  public final class ObserverMap<K, V> {
-    ctor public ObserverMap();
-    method public void add(K key, V value);
-    method public void clear();
-    method public void clearValues(kotlin.jvm.functions.Function1<? super V,java.lang.Boolean> predicate);
-    method public boolean contains(K key, V value);
-    method public operator java.util.List<V> get(Iterable<? extends K> keys);
-    method public java.util.List<V> getValueOf(K key);
-    method public void remove(K key);
-    method public void remove(K key, V value);
-    method public void removeValue(V value);
-  }
-
   @kotlin.PublishedApi internal final class PreCommitScopeImpl implements androidx.compose.runtime.CommitScope androidx.compose.runtime.CompositionLifecycleObserver {
     ctor public PreCommitScopeImpl(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.CommitScope,kotlin.Unit> onCommit);
     method public void onDispose(kotlin.jvm.functions.Function0<kotlin.Unit> callback);
@@ -385,12 +370,8 @@
   }
 
   public final class SkippableUpdater<T> {
-    ctor public SkippableUpdater(androidx.compose.runtime.Composer<?> composer, T? node);
-    method public androidx.compose.runtime.Composer<?> getComposer();
-    method public T! getNode();
+    ctor @kotlin.PublishedApi internal SkippableUpdater(@kotlin.PublishedApi androidx.compose.runtime.Composer<?> composer, @kotlin.PublishedApi T? node);
     method public inline void update(kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,kotlin.Unit> block);
-    property public final androidx.compose.runtime.Composer<?> composer;
-    property public final T! node;
   }
 
   @androidx.compose.runtime.InternalComposeApi public final class SlotReader {
@@ -573,16 +554,12 @@
   }
 
   public final class Updater<T> {
-    ctor public Updater(androidx.compose.runtime.Composer<?> composer, T? node);
-    method public androidx.compose.runtime.Composer<?> getComposer();
-    method public T! getNode();
+    ctor @kotlin.PublishedApi internal Updater(@kotlin.PublishedApi androidx.compose.runtime.Composer<?> composer, @kotlin.PublishedApi T? node);
     method public inline void reconcile(kotlin.jvm.functions.Function1<? super T,kotlin.Unit> block);
     method public inline void set(int value, kotlin.jvm.functions.Function2<? super T,? super java.lang.Integer,kotlin.Unit> block);
     method public inline <reified V> void set(V? value, kotlin.jvm.functions.Function2<? super T,? super V,? extends kotlin.Unit> block);
     method public inline void update(int value, kotlin.jvm.functions.Function2<? super T,? super java.lang.Integer,kotlin.Unit> block);
     method public inline <reified V> void update(V? value, kotlin.jvm.functions.Function2<? super T,? super V,? extends kotlin.Unit> block);
-    property public final androidx.compose.runtime.Composer<?> composer;
-    property public final T! node;
   }
 
 }
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt
index b044cc9..b6ec566 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt
+++ b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt
@@ -16,9 +16,6 @@
 
 package androidx.compose.runtime.benchmark
 
-import android.app.Activity
-import android.view.View
-import android.view.ViewGroup
 import androidx.benchmark.junit4.BenchmarkRule
 import androidx.benchmark.junit4.measureRepeated
 import androidx.compose.runtime.Composable
@@ -32,7 +29,6 @@
 import androidx.compose.runtime.snapshots.SnapshotReadObserver
 import androidx.compose.runtime.snapshots.SnapshotWriteObserver
 import androidx.compose.runtime.snapshots.takeMutableSnapshot
-import androidx.compose.ui.platform.AndroidOwner
 import androidx.compose.ui.platform.setContent
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
@@ -189,25 +185,3 @@
         updateModelCb = block
     }
 }
-
-// TODO(chuckj): Consider refacgtoring to use AndroidTestCaseRunner from UI
-// This code is copied from AndroidTestCaseRunner.kt
-private fun findComposeView(activity: Activity): AndroidOwner? {
-    return findComposeView(activity.findViewById(android.R.id.content) as ViewGroup)
-}
-
-private fun findComposeView(view: View): AndroidOwner? {
-    if (view is AndroidOwner) {
-        return view
-    }
-
-    if (view is ViewGroup) {
-        for (i in 0 until view.childCount) {
-            val composeView = findComposeView(view.getChildAt(i))
-            if (composeView != null) {
-                return composeView
-            }
-        }
-    }
-    return null
-}
\ No newline at end of file
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt
index 9adeb79..d932950 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt
@@ -17,10 +17,9 @@
 @file:OptIn(ExperimentalComposeApi::class)
 package androidx.compose.runtime
 
+import android.view.View
 import android.widget.TextView
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.platform.subcomposeInto
+import androidx.compose.ui.node.UiApplier
 import androidx.compose.ui.viewinterop.emitView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -497,18 +496,20 @@
     }
 
     @Composable fun deferredSubCompose(block: @Composable () -> Unit): () -> Unit {
-        @OptIn(ExperimentalLayoutNodeApi::class)
-        val container = remember { LayoutNode() }
+        val container = remember { View(activity) }
         val ref = Ref<CompositionReference>()
         narrowInvalidateForReference(ref = ref)
         return {
             @OptIn(ExperimentalComposeApi::class)
             // TODO(b/150390669): Review use of @ComposableContract(tracked = false)
-            subcomposeInto(
+            compositionFor(
                 container,
+                UiApplier(container),
                 ref.value
-            ) @ComposableContract(tracked = false) {
-                block()
+            ).apply {
+                setContent @ComposableContract(tracked = false) {
+                    block()
+                }
             }
         }
     }
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt
index 0f74688..51a66a2 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt
@@ -21,14 +21,13 @@
 import android.os.Bundle
 import android.os.Looper
 import android.view.Choreographer
+import android.view.View
 import android.view.ViewGroup
 import android.widget.LinearLayout
 import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.node.UiApplier
 import androidx.compose.ui.platform.AmbientContext
 import androidx.compose.ui.platform.setViewContent
-import androidx.compose.ui.platform.subcomposeInto
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlin.test.assertTrue
@@ -117,16 +116,18 @@
 
     @Composable
     fun subCompose(block: @Composable () -> Unit) {
-        @OptIn(ExperimentalLayoutNodeApi::class)
-        val container = remember { LayoutNode() }
+        val container = remember { View(activity) }
         val reference = compositionReference()
         // TODO(b/150390669): Review use of @ComposableContract(tracked = false)
         @OptIn(ExperimentalComposeApi::class)
-        subcomposeInto(
+        compositionFor(
             container,
+            UiApplier(container),
             reference
-        ) @ComposableContract(tracked = false) {
-            block()
+        ).apply {
+            setContent @ComposableContract(tracked = false) {
+                block()
+            }
         }
     }
 }
diff --git a/compose/runtime/runtime/lint-baseline.xml b/compose/runtime/runtime/lint-baseline.xml
deleted file mode 100644
index 76c954a..0000000
--- a/compose/runtime/runtime/lint-baseline.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-alpha15" client="gradle" variant="debug" version="4.2.0-alpha15">
-
-    <issue
-        id="UnknownNullness"
-        message="Should explicitly declare type here since implicit type does not specify nullness"
-        errorLine1="    override fun removeAt(index: Int) = get(index).also { update { it.removeAt(index) } }"
-        errorLine2="                 ~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt"
-            line="91"
-            column="18"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Should explicitly declare type here since implicit type does not specify nullness"
-        errorLine1="    override fun set(index: Int, element: T) = get(index).also { update { it.set(index, element) } }"
-        errorLine2="                 ~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt"
-            line="93"
-            column="18"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt
index 8eed09e..2d29baf 100644
--- a/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt
+++ b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt
@@ -41,10 +41,14 @@
     // We would implement an Applier class like the following, which would teach compose how to
     // manage a tree of Nodes.
     class NodeApplier(root: Node) : AbstractApplier<Node>(root) {
-        override fun insert(index: Int, instance: Node) {
+        override fun insertTopDown(index: Int, instance: Node) {
             current.children.add(index, instance)
         }
 
+        override fun insertBottomUp(index: Int, instance: Node) {
+            // Ignored as the tree is built top-down.
+        }
+
         override fun remove(index: Int, count: Int) {
             current.children.remove(index, count)
         }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Ambient.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Ambient.kt
index 9bfa638..d01c5d3 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Ambient.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Ambient.kt
@@ -69,9 +69,10 @@
      * @sample androidx.compose.runtime.samples.consumeAmbient
      */
     @OptIn(ComposeCompilerApi::class)
-    @ComposableContract(readonly = true)
-    @Composable
-    inline val current: T get() = currentComposer.consume(this)
+    inline val current: T
+        @ComposableContract(readonly = true)
+        @Composable
+        get() = currentComposer.consume(this)
 }
 
 /**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt
index 1f9410b..2d1be12 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt
@@ -30,7 +30,6 @@
  * @see Composer
  * @see emit
  */
-@ExperimentalComposeApi
 interface Applier<N> {
     /**
      * The node that operations will be applied on at any given time. It is expected that the
@@ -65,9 +64,98 @@
     fun up()
 
     /**
-     * Indicates that [instance] should be inserted as a child to [current] at [index]
+     * Indicates that [instance] should be inserted as a child to [current] at [index]. An applier
+     * should insert the node into the tree either in [insertTopDown] or [insertBottomUp], not both.
+     *
+     * The [insertTopDown] method is called before the children of [instance] have been created and
+     * inserted into it. [insertBottomUp] is called after all children have been created and
+     * inserted.
+     *
+     * Some trees are faster to build top-down, in which case the [insertTopDown] method should
+     * be used to insert the [instance]. Other tress are faster to build bottom-up in which case
+     * [insertBottomUp] should be used.
+     *
+     * To give example of building a tree top-down vs. bottom-up consider the following tree,
+     *
+     * ```
+     *      R
+     *      |
+     *      B
+     *     / \
+     *    A   C
+     *  ```
+     *
+     *  where the node `B` is being inserted into the tree at `R`. Top-down building of the tree
+     *  first inserts `B` into `R`, then inserts `A` into `B` followed by inserting `C` into B`.
+     *  For example,
+     *
+     *  ```
+     *      1           2           3
+     *      R           R           R
+     *      |           |           |
+     *      B           B           B
+     *                 /           / \
+     *                A           A   C
+     * ```
+     *
+     * A bottom-up building of the tree starts with inserting `A` and `C` into `B` then inserts
+     * `B` tree into `R`.
+     *
+     * ```
+     *    1           2           3
+     *    B           B           R
+     *    |          / \          |
+     *    A         A   C         B
+     *                           / \
+     *                          A   C
+     * ```
+     *
+     * To see how building top-down vs. bottom-up can differ significantly in performance
+     * consider a tree where whenever a child is added to the tree all parent nodes, up to the root,
+     * are notified of the new child entering the tree. If the tree is built top-down,
+     *
+     *  1. `R` is notified of `B` entering.
+     *  2. `B` is notified of `A` entering, `R` is notified of `A` entering.
+     *  3. `B` is notified of `C` entering, `R` is notified of `C` entering.
+     *
+     *  for a total of 5 notifications. The number of notifications grows exponentially with the
+     *  number of inserts.
+     *
+     *  For bottom-up, the notifications are,
+     *
+     *  1. `B` is notified `A` entering.
+     *  2. `B` is notified `C` entering.
+     *  3. `R` is notified `B` entering.
+     *
+     *  The notifications are linear to the number of nodes inserted.
+     *
+     *  If, on the other hand, all children are notified when the parent enters a tree, then the
+     *  notifications are, for top-down,
+     *
+     *  1. `B` is notified it is entering `R`.
+     *  2. `A` is notified it is entering `B`.
+     *  3. `C` is notified it is entering `B`.
+     *
+     *  which is linear to the number of nodes inserted.
+     *
+     *  For bottom-up, the notifications look like,
+     *
+     *  1. `A` is notified it is entering `B`.
+     *  2. `C` is notified it is entering `B`.
+     *  3. `B` is notified it is entering `R`, `A` is notified it is entering `R`,
+     *     `C` is notified it is entering `R`.
+     *
+     *  which exponential to the number of nodes inserted.
      */
-    fun insert(index: Int, instance: N)
+    fun insertTopDown(index: Int, instance: N)
+
+    /**
+     * Indicates that [instance] should be inserted as a child of [current] at [index]. An applier
+     * should insert the node into the tree either in [insertTopDown] or [insertBottomUp], not
+     * both. See the description of [insertTopDown] to which describes when to implement
+     * [insertTopDown] and when to use [insertBottomUp].
+     */
+    fun insertBottomUp(index: Int, instance: N)
 
     /**
      * Indicates that the children of [current] from [index] to [index] + [count] should be removed.
@@ -75,10 +163,9 @@
     fun remove(index: Int, count: Int)
 
     /**
-     * Indicates that the children of [current] from [from] to [from] + [count] should be moved
-     * to [to] + [count].
+     * Indicates that [count] children of [current] should be moved from index [from] to index [to].
      *
-     * The [to] index is related to the position before the change, so, for example, to move an
+     * The [to] index is relative to the position before the change, so, for example, to move an
      * element at position 1 to after the element at position 2, [from] should be `1` and [to]
      * should be `3`. If the elements were A B C D E, calling `move(1, 3, 1)` would result in the
      * elements being reordered to A C B D E.
@@ -93,7 +180,7 @@
 }
 
 /**
- * An abstract [Applier] implementation that builds the tree "top down".
+ * An abstract [Applier] implementation.
  *
  * @sample androidx.compose.runtime.samples.CustomTreeComposition
  *
@@ -102,7 +189,6 @@
  * @see Composer
  * @see emit
  */
-@ExperimentalComposeApi
 abstract class AbstractApplier<T>(val root: T) : Applier<T> {
     private val stack = mutableListOf<T>()
     override var current: T = root
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composable.kt
index de94b47..f2375f85 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composable.kt
@@ -50,8 +50,14 @@
     // foo: (@Composable () -> Unit) -> Unit
     AnnotationTarget.TYPE_PARAMETER,
 
-    // composable property declarations
+    // (DEPRECATED) composable property declarations
     // @Composable val foo: Int get() { ... }
-    AnnotationTarget.PROPERTY
+    AnnotationTarget.PROPERTY,
+
+    // composable property getters and setters
+    // val foo: Int @Composable get() { ... }
+    // var bar: Int
+    //   @Composable get() { ... }
+    AnnotationTarget.PROPERTY_GETTER
 )
 annotation class Composable
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposableContract.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposableContract.kt
index 4a820a8..213e507 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposableContract.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposableContract.kt
@@ -45,7 +45,8 @@
 @Retention(AnnotationRetention.BINARY)
 @Target(
     AnnotationTarget.FUNCTION,
-    AnnotationTarget.PROPERTY,
+    AnnotationTarget.PROPERTY, // (DEPRECATED)
+    AnnotationTarget.PROPERTY_GETTER,
     AnnotationTarget.TYPE
 )
 annotation class ComposableContract(
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index b3ea2bc..fa5be4d 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -61,7 +61,6 @@
  * changed. It is used to determine how to update the nodes and the slot table when changes to the
  * structure of the tree is detected.
  */
-@OptIn(InternalComposeApi::class)
 private class Pending(
     val keyInfos: MutableList<KeyInfo>,
     val startIndex: Int
@@ -78,7 +77,9 @@
         val result = hashMapOf<Int, GroupInfo>()
         for (index in 0 until keyInfos.size) {
             val keyInfo = keyInfos[index]
+            @OptIn(InternalComposeApi::class)
             result[keyInfo.location] = GroupInfo(index, runningNodeIndex, keyInfo.nodes)
+            @OptIn(InternalComposeApi::class)
             runningNodeIndex += keyInfo.nodes
         }
         result
@@ -147,6 +148,7 @@
         }
     }
 
+    @OptIn(InternalComposeApi::class)
     fun registerInsert(keyInfo: KeyInfo, insertIndex: Int) {
         groupInfos[keyInfo.location] = GroupInfo(-1, insertIndex, 0)
     }
@@ -168,8 +170,13 @@
         return false
     }
 
+    @OptIn(InternalComposeApi::class)
     fun slotPositionOf(keyInfo: KeyInfo) = groupInfos[keyInfo.location]?.slotIndex ?: -1
+
+    @OptIn(InternalComposeApi::class)
     fun nodePositionOf(keyInfo: KeyInfo) = groupInfos[keyInfo.location]?.nodeIndex ?: -1
+
+    @OptIn(InternalComposeApi::class)
     fun updatedNodeCountOf(keyInfo: KeyInfo) =
         groupInfos[keyInfo.location]?.nodeCount ?: keyInfo.nodes
 }
@@ -351,8 +358,7 @@
     /**
      * An adapter that applies changes to the tree using the Applier abstraction.
      */
-    @ComposeCompilerApi
-    val applier: Applier<N>,
+    @PublishedApi internal val applier: Applier<N>,
 
     /**
      * Parent of this composition; a [Recomposer] for root-level compositions.
@@ -373,7 +379,7 @@
     private var nodeCountOverrides: IntArray? = null
     private var nodeCountVirtualOverrides: HashMap<Int, Int>? = null
     private var collectKeySources = false
-
+    private var collectParameterInformation = false
     private var nodeExpected = false
     private val observations: MutableList<Any> = mutableListOf()
     private val observationsProcessed: MutableList<Any> = mutableListOf()
@@ -395,7 +401,6 @@
 
     private var reader: SlotReader = slotTable.openReader().also { it.close() }
 
-    @OptIn(InternalComposeApi::class)
     internal val insertTable = SlotTable()
 
     private var writer: SlotWriter = insertTable.openWriter().also { it.close() }
@@ -538,7 +543,7 @@
      * Start the composition. This should be called, and only be called, as the first group in
      * the composition.
      */
-    @OptIn(ComposeCompilerApi::class, InternalComposeApi::class)
+    @OptIn(InternalComposeApi::class)
     private fun startRoot() {
         reader = slotTable.openReader()
         startGroup(rootKey)
@@ -549,6 +554,7 @@
         providersInvalidStack.push(providersInvalid.asInt())
         providersInvalid = changed(parentProvider)
         collectKeySources = parentReference.collectingKeySources
+        collectParameterInformation = parentReference.collectingParameterInformation
         resolveAmbient(InspectionTables, parentProvider)?.let {
             it.add(slotTable)
             parentReference.recordInspectionTable(it)
@@ -560,7 +566,6 @@
      * End the composition. This should be called, and only be called, to end the first group in
      * the composition.
      */
-    @ComposeCompilerApi
     @OptIn(InternalComposeApi::class)
     private fun endRoot() {
         endGroup()
@@ -596,8 +601,8 @@
      * first composition this is always true. During recomposition this is true when new nodes
      * are being scheduled to be added to the tree.
      */
-    @ComposeCompilerApi
-    var inserting: Boolean = false
+    @PublishedApi
+    internal var inserting: Boolean = false
         private set
 
     /**
@@ -628,6 +633,15 @@
     }
 
     /**
+     * Start collecting parameter information. This enables the tools API to always be able to
+     * determine the parameter values of composable calls.
+     */
+    @InternalComposeApi
+    fun collectParameterInformation() {
+        collectParameterInformation = true
+    }
+
+    /**
      * Record that [value] was read from. If [recordWriteOf] or [recordModificationsOf] is called
      * with [value] then the corresponding [currentRecomposeScope] is invalidated.
      *
@@ -763,7 +777,7 @@
      * Apply the changes to the tree that were collected during the last composition.
      */
     @InternalComposeApi
-    @OptIn(ComposeCompilerApi::class)
+    @OptIn(ExperimentalComposeApi::class)
     fun applyChanges() {
         trace("Compose:applyChanges") {
             invalidateStack.clear()
@@ -812,7 +826,7 @@
     }
 
     @ExperimentalComposeApi
-    @OptIn(ComposeCompilerApi::class, InternalComposeApi::class)
+    @OptIn(InternalComposeApi::class)
     internal fun dispose() {
         trace("Compose:Composer.dispose") {
             parentReference.unregisterComposer(this)
@@ -845,16 +859,13 @@
      *
      *  @param key The key for the group
      */
-    @ComposeCompilerApi
     internal fun startGroup(key: Int) = start(key, null, false, null)
 
-    @ComposeCompilerApi
     internal fun startGroup(key: Int, dataKey: Any?) = start(key, dataKey, false, null)
 
     /**
      * End the current group.
      */
-    @ComposeCompilerApi
     internal fun endGroup() = end(false)
 
     @OptIn(InternalComposeApi::class)
@@ -869,8 +880,8 @@
      * current position if found, if no such node is found the composition switches into insert
      * mode and a the node is scheduled to be inserted at the current location.
      */
-    @ComposeCompilerApi
-    fun startNode() {
+    @PublishedApi
+    internal fun startNode() {
         start(nodeKey, null, true, null)
         nodeExpected = true
     }
@@ -880,9 +891,8 @@
      * call when the composer is inserting.
      */
     @Suppress("UNUSED")
-    @ComposeCompilerApi
-    @OptIn(ExperimentalComposeApi::class, InternalComposeApi::class)
-    fun <T : N> createNode(factory: () -> T) {
+    @OptIn(ExperimentalComposeApi::class)
+    internal fun <T : N> createNode(factory: () -> T) {
         validateNodeExpected()
         check(inserting) { "createNode() can only be called when inserting" }
         val insertIndex = nodeIndexStack.peek()
@@ -891,18 +901,23 @@
         recordFixup { applier, slots, _ ->
             val node = factory()
             slots.node = node
-            applier.insert(insertIndex, node)
+            applier.insertTopDown(insertIndex, node)
             applier.down(node)
         }
+        recordInsertUp { applier, _, _ ->
+            val nodeToInsert = applier.current
+            applier.up()
+            applier.insertBottomUp(insertIndex, nodeToInsert)
+        }
     }
 
     /**
      * Schedule the given node to be inserted. This is only valid to call when the composer is
      * inserting.
      */
-    @ComposeCompilerApi
+    @PublishedApi
     @OptIn(ExperimentalComposeApi::class)
-    fun emitNode(node: Any?) {
+    internal fun emitNode(node: Any?) {
         validateNodeExpected()
         check(inserting) { "emitNode() called when not inserting" }
         val insertIndex = nodeIndexStack.peek()
@@ -911,9 +926,14 @@
         @Suppress("UNCHECKED_CAST")
         writer.node = node as N
         recordApplierOperation { applier, _, _ ->
-            applier.insert(insertIndex, node)
+            applier.insertTopDown(insertIndex, node)
             applier.down(node)
         }
+        recordInsertUp { applier, _, _ ->
+            val nodeToInsert = applier.current
+            applier.up()
+            applier.insertBottomUp(insertIndex, nodeToInsert)
+        }
     }
 
     /**
@@ -921,8 +941,9 @@
      * valid to call when the composition is not inserting. This must be called at the same
      * location as [emitNode] or [createNode] as called even if the value is unused.
      */
-    @ComposeCompilerApi
-    fun useNode(): N {
+    @PublishedApi
+    @OptIn(InternalComposeApi::class)
+    internal fun useNode(): N {
         validateNodeExpected()
         check(!inserting) { "useNode() called while inserting" }
         val result = reader.node
@@ -933,8 +954,8 @@
     /**
      * Called to end the node group.
      */
-    @ComposeCompilerApi
-    fun endNode() = end(true)
+    @PublishedApi
+    internal fun endNode() = end(true)
 
     /**
      * Schedule a change to be applied to a node's property. This change will be applied to the
@@ -961,9 +982,9 @@
     /**
      * Return the next value in the slot table and advance the current location.
      */
-    @ComposeCompilerApi
+    @PublishedApi
     @OptIn(InternalComposeApi::class)
-    fun nextSlot(): Any? = if (inserting) {
+    internal fun nextSlot(): Any? = if (inserting) {
         validateNodeNotExpected()
         EMPTY
     } else reader.next()
@@ -1099,9 +1120,8 @@
      *
      * @param value the value to schedule to be written to the slot table.
      */
-    @OptIn(InternalComposeApi::class)
     @PublishedApi
-    @ComposeCompilerApi
+    @OptIn(InternalComposeApi::class)
     internal fun updateValue(value: Any?) {
         if (inserting) {
             writer.update(value)
@@ -1137,8 +1157,6 @@
     /**
      * Return the current ambient scope which was provided by a parent group.
      */
-    @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class)
     private fun currentAmbientScope(): AmbientMap {
         if (inserting && hasProvider) {
             var current = writer.parent
@@ -1173,8 +1191,6 @@
      * compose which might be inserting the sub-composition. In that case the current scope
      * is the correct scope.
      */
-    @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class)
     private fun ambientScopeAt(location: Int): AmbientMap {
         if (isComposing) {
             // The sub-composer is being composed as part of a nested composition then use the
@@ -1204,7 +1220,6 @@
      * scope followed by the map used to augment the parent scope. Both are needed to detect
      * inserts, updates and deletes to the providers.
      */
-    @ComposeCompilerApi
     private fun updateProviderMapGroup(
         parentScope: AmbientMap,
         currentProviders: AmbientMap
@@ -1217,8 +1232,6 @@
         return providerScope
     }
 
-    @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class)
     internal fun startProviders(values: Array<out ProvidedValue<*>>) {
         val parentScope = currentAmbientScope()
         startGroup(providerKey, provider)
@@ -1268,22 +1281,18 @@
         start(ambientMapKey, ambientMap, false, providers)
     }
 
-    @ComposeCompilerApi
     internal fun endProviders() {
         endGroup()
         endGroup()
         providersInvalid = providersInvalidStack.pop().asBool()
     }
 
-    @ComposeCompilerApi
     @PublishedApi
     internal fun <T> consume(key: Ambient<T>): T = resolveAmbient(key, currentAmbientScope())
 
     /**
      * Create or use a memoized `CompositionReference` instance at this position in the slot table.
      */
-    @ComposeCompilerApi
-    @OptIn(ExperimentalComposeApi::class)
     internal fun buildReference(): CompositionReference {
         startGroup(referenceKey, reference)
 
@@ -1292,7 +1301,12 @@
             val scope = invalidateStack.peek()
             scope.used = true
             ref = CompositionReferenceHolder(
-                CompositionReferenceImpl(scope, currentCompoundKeyHash, collectKeySources)
+                CompositionReferenceImpl(
+                    scope,
+                    currentCompoundKeyHash,
+                    collectKeySources,
+                    collectParameterInformation
+                )
             )
             updateValue(ref)
         }
@@ -1304,10 +1318,8 @@
     private fun <T> resolveAmbient(key: Ambient<T>, scope: AmbientMap): T =
         if (scope.contains(key)) scope.getValueOf(key) else parentReference.getAmbient(key)
 
-    @ComposeCompilerApi
     internal fun <T> parentAmbient(key: Ambient<T>): T = resolveAmbient(key, currentAmbientScope())
 
-    @ComposeCompilerApi
     private fun <T> parentAmbient(key: Ambient<T>, location: Int): T =
         resolveAmbient(key, ambientScopeAt(location))
 
@@ -1324,7 +1336,6 @@
             if (childrenComposing == 0 && it.isNotEmpty()) it.peek() else null
         }
 
-    @OptIn(InternalComposeApi::class)
     private fun ensureWriter() {
         if (writer.closed) {
             writer = insertTable.openWriter()
@@ -1337,7 +1348,6 @@
     /**
      * Start the reader group updating the data of the group if necessary
      */
-    @OptIn(InternalComposeApi::class)
     private fun startReaderGroup(isNode: Boolean, data: Any?) {
         if (isNode) {
             reader.startNode()
@@ -1351,8 +1361,6 @@
         }
     }
 
-    @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class)
     private fun start(key: Int, objectKey: Any?, isNode: Boolean, data: Any?) {
         validateNodeNotExpected()
 
@@ -1478,7 +1486,6 @@
         groupNodeCount = 0
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun exitGroup(expectedNodeCount: Int, inserting: Boolean) {
         // Restore the parent's state updating them if they have changed based on changes in the
         // children. For example, if a group generates nodes then the number of generated nodes will
@@ -1493,8 +1500,6 @@
         this.groupNodeCount = this.groupNodeCountStack.pop() + expectedNodeCount
     }
 
-    @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class, ExperimentalComposeApi::class)
     private fun end(isNode: Boolean) {
         // All the changes to the group (or node) have been recorded. All new nodes have been
         // inserted but it has yet to determine which need to be removed or moved. Note that the
@@ -1608,7 +1613,7 @@
         val inserting = inserting
         if (inserting) {
             if (isNode) {
-                recordInsertUp()
+                registerInsertUp()
                 expectedNodeCount = 1
             }
             reader.endEmpty()
@@ -1629,8 +1634,8 @@
             if (isNode) recordUp()
             recordEndGroup()
             val parentGroup = reader.parent
-            val parentNodecount = updatedNodeCount(parentGroup)
-            if (expectedNodeCount != parentNodecount) {
+            val parentNodeCount = updatedNodeCount(parentGroup)
+            if (expectedNodeCount != parentNodeCount) {
                 updateNodeCountOverrides(parentGroup, expectedNodeCount)
             }
             if (isNode) {
@@ -1649,7 +1654,6 @@
      * called instead of [skipReaderToGroupEnd] if any child groups are invalid. If no children
      * are invalid it will call [skipReaderToGroupEnd].
      */
-    @OptIn(InternalComposeApi::class, ExperimentalComposeApi::class)
     private fun recomposeToGroupEnd() {
         val wasComposing = isComposing
         isComposing = true
@@ -1738,7 +1742,6 @@
      * updates that count and then updates any parent groups that include the nodes this group
      * emits.
      */
-    @OptIn(InternalComposeApi::class)
     private fun updateNodeCountOverrides(group: Int, newCount: Int) {
         // The value of group can be negative which indicates it is tracking an inserted group
         // instead of an existing group. The index is a virtual index calculated by
@@ -1778,7 +1781,6 @@
      * [recomposeIndex] allows the calculation to exit early if there is no node group between
      * [group] and [recomposeGroup].
      */
-    @OptIn(InternalComposeApi::class)
     private fun nodeIndexOf(
         groupLocation: Int,
         group: Int,
@@ -1814,7 +1816,6 @@
         return index
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun updatedNodeCount(group: Int): Int {
         if (group < 0) return nodeCountVirtualOverrides?.let { it[group] } ?: 0
         val nodeCounts = nodeCountOverrides
@@ -1825,7 +1826,6 @@
         return reader.nodeCount(group)
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun updateNodeCount(group: Int, count: Int) {
         if (updatedNodeCount(group) != count) {
             if (group < 0) {
@@ -1856,7 +1856,6 @@
      * Records the operations necessary to move the applier the node affected by the previous
      * group to the new group.
      */
-    @OptIn(InternalComposeApi::class)
     private fun recordUpsAndDowns(oldGroup: Int, newGroup: Int, commonRoot: Int) {
         val reader = reader
         val nearestCommonRoot = reader.nearestCommonRootOf(
@@ -1876,7 +1875,6 @@
         doRecordDownsFor(newGroup, nearestCommonRoot)
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun doRecordDownsFor(group: Int, nearestCommonRoot: Int) {
         if (group > 0 && group != nearestCommonRoot) {
             doRecordDownsFor(reader.parent(group), nearestCommonRoot)
@@ -1889,7 +1887,6 @@
      * for [group]. Passing in the [recomposeGroup] and [recomposeKey] allows this method to exit
      * early.
      */
-    @OptIn(InternalComposeApi::class)
     private fun compoundKeyOf(group: Int, recomposeGroup: Int, recomposeKey: Int): Int {
         return if (group == recomposeGroup) recomposeKey else (
             compoundKeyOf(
@@ -1909,12 +1906,10 @@
      * back into a known good state after a period of time when snapshot changes were not
      * being observed.
      */
-    @OptIn(InternalComposeApi::class)
     internal fun invalidateAll() {
         slotTable.slots.forEach { (it as? RecomposeScope)?.invalidate() }
     }
 
-    @OptIn(InternalComposeApi::class, ExperimentalComposeApi::class)
     internal fun invalidate(scope: RecomposeScope): InvalidationResult {
         if (scope.defaultsInScope) {
             scope.defaultsInvalid = true
@@ -1940,7 +1935,6 @@
      * composition is not inserting.
      */
     @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class, ExperimentalComposeApi::class)
     fun skipCurrentGroup() {
         if (invalidations.isEmpty()) {
             skipGroup()
@@ -1956,7 +1950,6 @@
         }
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun skipReaderToGroupEnd() {
         groupNodeCount = reader.parentNodes
         reader.skipToGroupEnd()
@@ -1994,8 +1987,6 @@
         addRecomposeScope()
     }
 
-    @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class)
     private fun addRecomposeScope() {
         if (inserting) {
             val scope = RecomposeScope(this)
@@ -2016,14 +2007,13 @@
      * [endRestartGroup]).
      */
     @ComposeCompilerApi
-    @OptIn(InternalComposeApi::class)
     fun endRestartGroup(): ScopeUpdateScope? {
         // This allows for the invalidate stack to be out of sync since this might be called during exception stack
         // unwinding that might have not called the doneJoin/endRestartGroup in the wrong order.
         val scope = if (invalidateStack.isNotEmpty()) invalidateStack.pop()
         else null
         scope?.requiresRecompose = false
-        val result = if (scope != null && (scope.used || collectKeySources)) {
+        val result = if (scope != null && (scope.used || collectParameterInformation)) {
             if (scope.anchor == null) {
                 scope.anchor = if (inserting) {
                     writer.anchor(writer.parent)
@@ -2045,7 +2035,6 @@
      * which must be applied by [applyChanges] to build the tree implied by [block].
      */
     @InternalComposeApi
-    @OptIn(ComposeCompilerApi::class)
     fun composeInitial(block: @Composable () -> Unit) {
         trace("Compose:recompose") {
             var complete = false
@@ -2070,7 +2059,6 @@
      * applied by [applyChanges] to have an effect.
      */
     @InternalComposeApi
-    @OptIn(ComposeCompilerApi::class)
     fun recompose(): Boolean {
         if (invalidations.isNotEmpty()) {
             trace("Compose:recompose") {
@@ -2095,15 +2083,15 @@
 
     internal fun hasInvalidations() = invalidations.isNotEmpty()
 
-    @OptIn(InternalComposeApi::class)
     @Suppress("UNCHECKED_CAST")
+    @OptIn(InternalComposeApi::class)
     private var SlotWriter.node
         get() = node(currentGroup) as N
         set(value) { updateParentNode(value) }
-    @OptIn(InternalComposeApi::class)
+
     @Suppress("UNCHECKED_CAST")
     private val SlotReader.node get() = node(parent) as N
-    @OptIn(InternalComposeApi::class)
+
     @Suppress("UNCHECKED_CAST")
     private fun SlotReader.nodeAt(index: Int) = node(index) as N
 
@@ -2169,7 +2157,6 @@
     private var pendingUps = 0
     private var downNodes = Stack<N>()
 
-    @OptIn(ExperimentalComposeApi::class)
     private fun realizeUps() {
         val count = pendingUps
         if (count > 0) {
@@ -2178,7 +2165,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeApi::class)
     private fun realizeDowns(nodes: Array<N>) {
         record { applier, _, _ ->
             for (index in nodes.indices) {
@@ -2208,21 +2194,32 @@
         }
     }
 
-    private var pendingInsertUps = 0
+    private var insertUpRequests = Stack<Change<N>>()
 
-    private fun recordInsertUp() {
-        pendingInsertUps++
+    private var pendingInsertUps = mutableListOf<Change<N>>()
+
+    private fun registerInsertUp() {
+        pendingInsertUps.add(insertUpRequests.pop())
     }
 
-    @OptIn(ExperimentalComposeApi::class)
     private fun realizeInsertUps() {
-        if (pendingInsertUps > 0) {
-            val count = pendingInsertUps
-            record { applier, _, _ -> repeat(count) { applier.up() } }
-            pendingInsertUps = 0
+        if (pendingInsertUps.isNotEmpty()) {
+            pendingInsertUps.forEach { record(it) }
+            pendingInsertUps.clear()
         }
     }
 
+    /**
+     * Record a change that will be added after all the changes for the node and all its children
+     * have been performed.
+     *
+     * This is used to implement calling [Applier.insertBottomUp] to allow a tree to be built
+     * bottom-up instead of top-down.
+     */
+    private fun recordInsertUp(change: Change<N>) {
+        insertUpRequests.push(change)
+    }
+
     // Navigating the writer slot is performed relatively as the location of a group in the writer
     // might be different than it is in the reader as groups can be inserted, deleted, or moved.
     //
@@ -2259,7 +2256,6 @@
      */
     private val startedGroups = IntStack()
 
-    @OptIn(InternalComposeApi::class)
     private fun realizeOperationLocation(forParent: Boolean = false) {
         val location = if (forParent) reader.parent else reader.currentGroup
         val distance = location - writersReaderDelta
@@ -2270,7 +2266,6 @@
         }
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun recordInsert(anchor: Anchor) {
         if (insertFixups.isEmpty()) {
             recordSlotEditingOperation { _, slots, _ ->
@@ -2294,7 +2289,6 @@
         }
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun recordFixup(change: Change<N>) {
         realizeInsertUps()
         val anchor = insertAnchor
@@ -2312,7 +2306,6 @@
      * writer and reader are tracking the same slot we advance the [writersReaderDelta] to
      * account for the removal.
      */
-    @OptIn(InternalComposeApi::class)
     private fun recordDelete() {
         recordSlotEditingOperation(change = removeCurrentGroupInstance)
         writersReaderDelta += reader.groupSize
@@ -2321,7 +2314,6 @@
     /**
      * Called when reader current is moved directly, such as when a group moves, to [location].
      */
-    @OptIn(InternalComposeApi::class)
     private fun recordReaderMoving(location: Int) {
         val distance = reader.currentGroup - writersReaderDelta
 
@@ -2329,7 +2321,6 @@
         writersReaderDelta = location - distance
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun recordSlotEditing() {
         // During initial composition (when the slot table is empty), no group needs
         // to be started.
@@ -2350,7 +2341,6 @@
         }
     }
 
-    @OptIn(InternalComposeApi::class)
     private fun recordEndGroup() {
         val location = reader.parent
         val currentStartedGroup = startedGroups.peekOr(-1)
@@ -2376,7 +2366,6 @@
         cleanUpCompose()
     }
 
-    @OptIn(ExperimentalComposeApi::class)
     private fun cleanUpCompose() {
         pending = null
         nodeIndex = 0
@@ -2421,7 +2410,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeApi::class)
     private fun realizeMovement() {
         val count = previousCount
         previousCount = 0
@@ -2457,7 +2445,8 @@
     private inner class CompositionReferenceImpl(
         val scope: RecomposeScope,
         override val compoundHashKey: Int,
-        override val collectingKeySources: Boolean
+        override val collectingKeySources: Boolean,
+        override val collectingParameterInformation: Boolean
     ) : CompositionReference() {
         var inspectionTables: MutableSet<MutableSet<SlotTable>>? = null
         val composers = mutableSetOf<Composer<*>>()
@@ -2504,8 +2493,6 @@
             invalidate(scope)
         }
 
-        @ComposeCompilerApi
-        @OptIn(InternalComposeApi::class)
         override fun <T> getAmbient(key: Ambient<T>): T {
             val anchor = scope.anchor
             return if (anchor != null && anchor.valid) {
@@ -2517,8 +2504,6 @@
             }
         }
 
-        @ComposeCompilerApi
-        @OptIn(InternalComposeApi::class)
         override fun getAmbientScope(): AmbientMap {
             return ambientScopeAt(scope.anchor?.toIndexFor(slotTable) ?: 0)
         }
@@ -2540,7 +2525,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeApi::class)
     private fun updateCompoundKeyWhenWeEnterGroup(groupKey: Int, dataKey: Any?) {
         if (dataKey == null)
             updateCompoundKeyWhenWeEnterGroupKeyHash(groupKey)
@@ -2568,8 +2552,10 @@
 }
 
 @Suppress("UNCHECKED_CAST")
-/*inline */ class Updater<T>(val composer: Composer<*>, val node: T) {
-    @OptIn(ComposeCompilerApi::class)
+/*inline */ class Updater<T> @PublishedApi internal constructor(
+    @PublishedApi internal val composer: Composer<*>,
+    @PublishedApi internal val node: T
+) {
     inline fun set(
         value: Int,
         /*crossinline*/
@@ -2583,7 +2569,6 @@
         }
     }
 
-    @OptIn(ComposeCompilerApi::class)
     inline fun <reified V> set(
         value: V,
         /*crossinline*/
@@ -2597,7 +2582,6 @@
         }
     }
 
-    @OptIn(ComposeCompilerApi::class)
     inline fun update(
         value: Int,
         /*crossinline*/
@@ -2611,7 +2595,6 @@
         }
     }
 
-    @OptIn(ComposeCompilerApi::class)
     inline fun <reified V> update(
         value: V,
         /*crossinline*/
@@ -2632,8 +2615,10 @@
     }
 }
 
-class SkippableUpdater<T>(val composer: Composer<*>, val node: T) {
-    @OptIn(ComposeCompilerApi::class)
+class SkippableUpdater<T> @PublishedApi internal constructor(
+    @PublishedApi internal val composer: Composer<*>,
+    @PublishedApi internal val node: T
+) {
     inline fun update(block: Updater<T>.() -> Unit) {
         composer.startReplaceableGroup(0x1e65194f)
         Updater(composer, node).block()
@@ -2641,7 +2626,6 @@
     }
 }
 
-@OptIn(InternalComposeApi::class)
 private fun SlotWriter.removeCurrentGroup(lifecycleManager: LifecycleManager) {
     // Notify the lifecycle manager of any observers leaving the slot table
     // The notification order should ensure that listeners are notified of leaving
@@ -2696,7 +2680,7 @@
 
 // Observation helpers
 
-// These helpers enable storing observaction pairs of value to scope instances in a list sorted by
+// These helpers enable storing observation pairs of value to scope instances in a list sorted by
 // the value hash as a primary key and the scope hash as the secondary key. This results in a
 // multi-set that allows finding an observation/scope pairs in O(log N) and a worst case of
 // insert into and remove from the array of O(log N) + O(N) where N is the number of total pairs.
@@ -2876,8 +2860,7 @@
 private fun Boolean.asInt() = if (this) 1 else 0
 private fun Int.asBool() = this != 0
 
-@Composable
-val currentComposer: Composer<*> get() {
+val currentComposer: Composer<*> @Composable get() {
     throw NotImplementedError("Implemented as an intrinsic")
 }
 
@@ -2896,7 +2879,6 @@
     return realFn(composer, 1)
 }
 
-@OptIn(InternalComposeApi::class)
 private fun SlotReader.distanceFrom(index: Int, root: Int): Int {
     var count = 0
     var current = index
@@ -2908,7 +2890,6 @@
 }
 
 // find the nearest common root
-@OptIn(InternalComposeApi::class)
 private fun SlotReader.nearestCommonRootOf(a: Int, b: Int, common: Int): Int {
     // Early outs, to avoid calling distanceFrom in trivial cases
     if (a == b) return a // A group is the nearest common root of itself
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionReference.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionReference.kt
index 51f7320..26940dd 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionReference.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionReference.kt
@@ -36,6 +36,7 @@
 abstract class CompositionReference internal constructor() {
     internal abstract val compoundHashKey: Int
     internal abstract val collectingKeySources: Boolean
+    internal abstract val collectingParameterInformation: Boolean
     internal abstract val effectCoroutineContext: CoroutineContext
     internal abstract fun composeInitial(composer: Composer<*>, composable: @Composable () -> Unit)
     internal abstract fun invalidate(composer: Composer<*>)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt
index ea257ff..5f2a2b37 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt
@@ -197,8 +197,7 @@
  * An Effect to get the nearest invalidation lambda to the current point of composition. This can be used to
  * trigger an invalidation on the composition locally to cause a recompose.
  */
-@Composable
-val invalidate: () -> Unit get() {
+val invalidate: () -> Unit @Composable get() {
     val scope = currentComposer.currentRecomposeScope ?: error("no recompose scope found")
     scope.used = true
     return { scope.invalidate() }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Emit.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Emit.kt
index d027d69..67ebb1b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Emit.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Emit.kt
@@ -95,6 +95,32 @@
     currentComposer.endNode()
 }
 
+/**
+ * Emits a node into the composition of type [T]. Nodes emitted inside of [content] will become
+ * children of the emitted node.
+ *
+ * This function will throw a runtime exception if [E] is not a subtype of the applier of the
+ * [currentComposer].
+ *
+ * @sample androidx.compose.runtime.samples.CustomTreeComposition
+ *
+ * @param ctor A function which will create a new instance of [T]. This function is NOT
+ * guaranteed to be called in place.
+ * @param update A function to perform updates on the node. This will run every time emit is
+ * executed. This function is called in place and will be inlined.
+ * @param skippableUpdate A function to perform updates on the node. Unlike [update], this
+ * function is Composable and will therefore be skipped unless it has been invalidated by some
+ * other mechanism. This can be useful to perform expensive calculations for updating the node
+ * where the calculations are likely to have the same inputs over time, so the function's
+ * execution can be skipped.
+ * @param content the composable content that will emit the "children" of this node.
+ *
+ * @see Updater
+ * @see SkippableUpdater
+ * @see Applier
+ * @see emit
+ * @see compositionFor
+ */
 @Suppress("ComposableNaming")
 @OptIn(ComposeCompilerApi::class)
 @Composable @ComposableContract(readonly = true)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
index bfc4e99..31c52b0 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
@@ -32,19 +32,6 @@
 
 internal expect inline fun <R> synchronized(lock: Any, block: () -> R): R
 
-expect open class WeakReference<T> : Reference<T> {
-    constructor(referent: T)
-    constructor(referent: T, q: ReferenceQueue<in T>?)
-}
-
-expect abstract class Reference<T> {
-    open fun get(): T?
-}
-
-expect open class ReferenceQueue<T>() {
-    open fun poll(): Reference<out T>?
-}
-
 expect class AtomicReference<V>(value: V) {
     fun get(): V
     fun set(value: V)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ObserverMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ObserverMap.kt
deleted file mode 100644
index 1616cfc..0000000
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ObserverMap.kt
+++ /dev/null
@@ -1,195 +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.runtime
-
-/**
- * A map from a key to a set of values used for keeping the relation between some
- * entities and a models changes of which this entities are observing.
- *
- * Two main differences from a regular Map<K, Set<V>>:
- * 1) Object.hashCode is not used, so the values can be mutable and change their hashCode value
- * 2) Objects are stored with WeakReference to prevent leaking them.
-*/
-class ObserverMap<K : Any, V : Any> {
-    private val keyToValue =
-        hashMapOf<IdentityWeakReference<K>, MutableSet<IdentityWeakReference<V>>>()
-    private val valueToKey =
-        hashMapOf<IdentityWeakReference<V>, MutableSet<IdentityWeakReference<K>>>()
-    private val keyQueue = ReferenceQueue<K>()
-    private val valueQueue = ReferenceQueue<V>()
-
-    /**
-     * Adds a [value] into a set associated with this [key].
-     */
-    fun add(key: K, value: V) {
-        clearReferences()
-        val weakKey = IdentityWeakReference(key, keyQueue)
-        val weakValue = IdentityWeakReference(value, valueQueue)
-        addToSet(keyToValue, weakKey, weakValue)
-        addToSet(valueToKey, weakValue, weakKey)
-    }
-
-    /**
-     * Removes all the values associated with this [key].
-     *
-     * @return the list of values removed from the set as a result of this operation.
-     */
-    fun remove(key: K) {
-        clearReferences()
-        val weakKey = IdentityWeakReference(key)
-        removeFromSet(keyToValue, valueToKey, weakKey)
-    }
-
-    /**
-     * Removes exact [value] from the set associated with this [key].
-     */
-    fun remove(key: K, value: V) {
-        clearReferences()
-        val weakKey = IdentityWeakReference(key)
-        val weakValue = IdentityWeakReference(value)
-        keyToValue[weakKey]?.remove(weakValue)
-        valueToKey[weakValue]?.remove(weakKey)
-    }
-
-    /**
-     * Returns `true` when the map contains the given key and value
-     */
-    fun contains(key: K, value: V): Boolean {
-        clearReferences()
-        val set = keyToValue[IdentityWeakReference(key)]
-        return set?.contains(IdentityWeakReference(value)) ?: false
-    }
-
-    /**
-     * Clears all the keys and values from the map.
-     */
-    fun clear() {
-        keyToValue.clear()
-        valueToKey.clear()
-        clearReferences()
-    }
-
-    /**
-     * @return a list of values associated with the provided [keys].
-     */
-    operator fun get(keys: Iterable<K>): List<V> {
-        clearReferences()
-        val set = mutableSetOf<IdentityWeakReference<V>>()
-        keys.forEach { key ->
-            val weakKey = IdentityWeakReference(key)
-            keyToValue[weakKey]?.let(set::addAll)
-        }
-        return set.mapNotNull { it.get() }
-    }
-
-    /**
-     * @return a list of values associated with the provided [key]
-     */
-    fun getValueOf(key: K): List<V> {
-        clearReferences()
-        val weakKey = IdentityWeakReference(key)
-        return keyToValue[weakKey]?.mapNotNull { it.get() }?.toList() ?: emptyList<V>()
-    }
-
-    /**
-     * Clears all the values that match the given [predicate] from all the sets.
-     */
-    @Suppress("UNCHECKED_CAST")
-    fun clearValues(predicate: (V) -> Boolean) {
-        clearReferences()
-        val matching = mutableListOf<V>()
-        valueToKey.keys.forEach { value ->
-            val v = value.get()
-            if (v != null && predicate(v)) {
-                matching.add(v)
-            }
-        }
-        matching.forEach { removeValue(it) }
-    }
-
-    /**
-     * Removes all values matching [value].
-     */
-    fun removeValue(value: V) {
-        clearReferences()
-        val weakValue = IdentityWeakReference(value)
-        valueToKey.remove(weakValue)?.forEach { key ->
-            val valueSet = keyToValue[key]!!
-            valueSet.remove(weakValue)
-            if (valueSet.isEmpty()) {
-                keyToValue.remove(key)
-            }
-        }
-    }
-
-    private fun clearReferences() {
-        pollQueue(keyQueue, keyToValue, valueToKey)
-        pollQueue(valueQueue, valueToKey, keyToValue)
-    }
-
-    private fun <T, U> pollQueue(
-        queue: ReferenceQueue<T>,
-        keyMap: MutableMap<IdentityWeakReference<T>, MutableSet<IdentityWeakReference<U>>>,
-        valueMap: MutableMap<IdentityWeakReference<U>, MutableSet<IdentityWeakReference<T>>>
-    ) {
-        do {
-            val ref = queue.poll()
-            if (ref != null) {
-                @Suppress("UNCHECKED_CAST")
-                val weakKey = ref as IdentityWeakReference<T>
-                removeFromSet(keyMap, valueMap, weakKey)
-            }
-        } while (ref != null)
-    }
-
-    private fun <T, U> addToSet(
-        map: MutableMap<IdentityWeakReference<T>, MutableSet<IdentityWeakReference<U>>>,
-        key: IdentityWeakReference<T>,
-        value: IdentityWeakReference<U>
-    ) {
-        var set = map[key]
-        if (set == null) {
-            set = hashSetOf()
-            map.put(key, set)
-        }
-        set.add(value)
-    }
-
-    private fun <T, U> removeFromSet(
-        mapFromKey: MutableMap<IdentityWeakReference<T>, MutableSet<IdentityWeakReference<U>>>,
-        mapToKey: MutableMap<IdentityWeakReference<U>, MutableSet<IdentityWeakReference<T>>>,
-        key: IdentityWeakReference<T>
-    ) {
-        mapFromKey.remove(key)?.forEach { value ->
-            mapToKey[value]?.remove(key)
-        }
-    }
-}
-
-private class IdentityWeakReference<T>(value: T, queue: ReferenceQueue<T>? = null) :
-    WeakReference<T>(value, queue) {
-    val hash = identityHashCode(value)
-
-    override fun equals(other: Any?): Boolean {
-        if (other !is IdentityWeakReference<*>) {
-            return false
-        }
-        return hash == other.hash && get() === other.get()
-    }
-
-    override fun hashCode(): Int = hash
-}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index b543735..03bdc3d 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -433,6 +433,10 @@
     internal override val collectingKeySources: Boolean
         get() = false
 
+    // Collecting parameter happens at the level of a composer; starts as false
+    internal override val collectingParameterInformation: Boolean
+        get() = false
+
     internal override fun recordInspectionTable(table: MutableSet<SlotTable>) {
         // TODO: The root recomposer might be a better place to set up inspection
         // than the current configuration with an ambient
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index f5e27ed..b1f49c7 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -773,7 +773,7 @@
     fun reposition(index: Int) {
         require(emptyCount == 0) { "Cannot reposition while in an empty region" }
         currentGroup = index
-        val parent = groups.parentAnchor(index)
+        val parent = if (index < groupsSize) groups.parentAnchor(index) else -1
         this.parent = parent
         if (parent < 0)
             this.currentEnd = groupsSize
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
index 044bbab..e760c75 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
@@ -1177,7 +1177,7 @@
 private fun RecomposeScope?.replacableWith(other: RecomposeScope) =
     this == null || !this.valid || this == other || this.anchor == other.anchor
 
-@ComposeCompilerApi
+@OptIn(ComposeCompilerApi::class)
 private typealias CLambda = ComposableLambda<Any, Any, Any, Any, Any, Any, Any, Any, Any, Any,
     Any, Any, Any, Any, Any, Any, Any, Any, Any>
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
index aa29075..132b707 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
@@ -88,9 +88,11 @@
     override fun clear() = writable { list = persistentListOf() }
     override fun remove(element: T) = conditionalUpdate { it.remove(element) }
     override fun removeAll(elements: Collection<T>) = conditionalUpdate { it.removeAll(elements) }
-    override fun removeAt(index: Int) = get(index).also { update { it.removeAt(index) } }
+    override fun removeAt(index: Int): T = get(index).also { update { it.removeAt(index) } }
     override fun retainAll(elements: Collection<T>) = mutate { it.retainAll(elements) }
-    override fun set(index: Int, element: T) = get(index).also { update { it.set(index, element) } }
+    override fun set(index: Int, element: T): T = get(index).also {
+        update { it.set(index, element) }
+    }
 
     fun removeRange(fromIndex: Int, toIndex: Int) {
         mutate {
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt
index 927fa46..830f020 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.kt
@@ -45,10 +45,4 @@
     }
 }
 
-internal actual typealias Reference<T> = java.lang.ref.Reference<T>
-
-internal actual typealias ReferenceQueue<T> = java.lang.ref.ReferenceQueue<T>
-
-internal actual typealias WeakReference<T> = java.lang.ref.WeakReference<T>
-
 internal actual typealias TestOnly = org.jetbrains.annotations.TestOnly
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/AbstractApplierTest.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/AbstractApplierTest.kt
index e80dbdb..59fcfd2 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/AbstractApplierTest.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/AbstractApplierTest.kt
@@ -35,22 +35,22 @@
 
     @Test fun downGoesDown() {
         val one = Node("one")
-        applier.insert(0, one)
+        applier.insertTopDown(0, one)
         applier.down(one)
         assertSame(one, applier.current)
 
         val two = Node("two")
-        applier.insert(0, two)
+        applier.insertTopDown(0, two)
         applier.down(two)
         assertSame(two, applier.current)
     }
 
     @Test fun upGoesUp() {
         val one = Node("one")
-        applier.insert(0, one)
+        applier.insertTopDown(0, one)
         applier.down(one)
         val two = Node("two")
-        applier.insert(0, two)
+        applier.insertTopDown(0, two)
         applier.down(two)
 
         applier.up()
@@ -61,7 +61,7 @@
 
     @Test fun clearClearsAndPointsToRoot() {
         val child = Node("child")
-        applier.insert(0, child)
+        applier.insertTopDown(0, child)
         applier.down(child)
 
         applier.clear()
@@ -76,10 +76,10 @@
         val two = Node("two")
         val three = Node("three")
         val four = Node("four")
-        applier.insert(0, one)
-        applier.insert(1, two)
-        applier.insert(2, three)
-        applier.insert(3, four)
+        applier.insertTopDown(0, one)
+        applier.insertTopDown(1, two)
+        applier.insertTopDown(2, three)
+        applier.insertTopDown(3, four)
 
         applier.remove(1, 1) // Middle
         assertEquals(listOf(one, three, four), root.children)
@@ -99,13 +99,13 @@
         val five = Node("five")
         val six = Node("six")
         val seven = Node("seven")
-        applier.insert(0, one)
-        applier.insert(1, two)
-        applier.insert(2, three)
-        applier.insert(3, four)
-        applier.insert(4, five)
-        applier.insert(5, six)
-        applier.insert(6, seven)
+        applier.insertTopDown(0, one)
+        applier.insertTopDown(1, two)
+        applier.insertTopDown(2, three)
+        applier.insertTopDown(3, four)
+        applier.insertTopDown(4, five)
+        applier.insertTopDown(5, six)
+        applier.insertTopDown(6, seven)
 
         applier.remove(2, 2) // Middle
         assertEquals(listOf(one, two, five, six, seven), root.children)
@@ -121,9 +121,9 @@
         val one = Node("one")
         val two = Node("two")
         val three = Node("three")
-        applier.insert(0, one)
-        applier.insert(1, two)
-        applier.insert(2, three)
+        applier.insertTopDown(0, one)
+        applier.insertTopDown(1, two)
+        applier.insertTopDown(2, three)
 
         applier.move(0, 3, 1)
         assertEquals(listOf(two, three, one), root.children)
@@ -141,9 +141,9 @@
         val one = Node("one")
         val two = Node("two")
         val three = Node("three")
-        applier.insert(0, one)
-        applier.insert(1, two)
-        applier.insert(2, three)
+        applier.insertTopDown(0, one)
+        applier.insertTopDown(1, two)
+        applier.insertTopDown(2, three)
 
         applier.move(2, 0, 1)
         assertEquals(listOf(three, one, two), root.children)
@@ -162,10 +162,10 @@
         val two = Node("two")
         val three = Node("three")
         val four = Node("four")
-        applier.insert(0, one)
-        applier.insert(1, two)
-        applier.insert(2, three)
-        applier.insert(3, four)
+        applier.insertTopDown(0, one)
+        applier.insertTopDown(1, two)
+        applier.insertTopDown(2, three)
+        applier.insertTopDown(3, four)
 
         applier.move(0, 4, 2)
         assertEquals(listOf(three, four, one, two), root.children)
@@ -178,10 +178,10 @@
         val two = Node("two")
         val three = Node("three")
         val four = Node("four")
-        applier.insert(0, one)
-        applier.insert(1, two)
-        applier.insert(2, three)
-        applier.insert(3, four)
+        applier.insertTopDown(0, one)
+        applier.insertTopDown(1, two)
+        applier.insertTopDown(2, three)
+        applier.insertTopDown(3, four)
 
         applier.move(2, 0, 2)
         assertEquals(listOf(three, four, one, two), root.children)
@@ -195,10 +195,12 @@
 
 @OptIn(ExperimentalComposeApi::class)
 private class NodeApplier(root: Node) : AbstractApplier<Node>(root) {
-    override fun insert(index: Int, instance: Node) {
+    override fun insertTopDown(index: Int, instance: Node) {
         current.children.add(index, instance)
     }
 
+    override fun insertBottomUp(index: Int, instance: Node) { }
+
     override fun remove(index: Int, count: Int) {
         current.children.remove(index, count)
     }
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/ObserverMapTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/ObserverMapTests.kt
deleted file mode 100644
index 8828ff6..0000000
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/ObserverMapTests.kt
+++ /dev/null
@@ -1,164 +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.runtime
-
-import kotlin.test.BeforeTest
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-class ObserverMapTests {
-
-    private val node1 = 1
-    private val node2 = 2
-    private lateinit var map: ObserverMap<TestModel, Int>
-
-    @BeforeTest
-    fun setup() {
-        map = ObserverMap()
-    }
-
-    @Test
-    fun testMapContainsPreviouslyAddedModel() {
-        val model = TestModel()
-        map.add(model, node1)
-
-        map.assertNodes(model, node1)
-    }
-
-    @Test
-    fun testMapAssociateBothNodesWithTheModel() {
-        val model = TestModel()
-        map.add(model, node1)
-        map.add(model, node2)
-
-        map.assertNodes(model, node1, node2)
-    }
-
-    @Test
-    fun testMapContainsModelWithChangedHashCode() {
-        val model = TestModel("Original")
-        map.add(model, node1)
-        model.content = "Changed"
-
-        map.assertNodes(model, node1)
-    }
-
-    @Test
-    fun testMapRemovesTheModel() {
-        val model = TestModel()
-        map.add(model, node1)
-        map.add(model, node2)
-
-        map.remove(model)
-
-        map.assertNodes(model)
-    }
-
-    @Test
-    fun testMapRemovesTheNode() {
-        val model = TestModel()
-        map.add(model, node1)
-        map.add(model, node2)
-
-        map.remove(model, node1)
-
-        map.assertNodes(model, node2)
-    }
-
-    @Test
-    fun testMapClearsAllTheModels() {
-        val model1 = TestModel("Test1")
-        val model2 = TestModel("Test2")
-        map.add(model1, node1)
-        map.add(model2, node2)
-
-        map.clear()
-
-        map.assertNodes(model1)
-        map.assertNodes(model2)
-    }
-
-    @Test
-    fun testMapClearsTheValuesByPredicate() {
-        val model1 = TestModel("Test1")
-        val model2 = TestModel("Test2")
-        val node3 = 3
-        map.add(model1, node1)
-        map.add(model2, node2)
-        map.add(model2, node3)
-
-        map.clearValues { it == node1 || it == node3 }
-
-        map.assertNodes(model1)
-        map.assertNodes(model2, node2)
-    }
-
-    @Test
-    fun testGetForMultipleModels() {
-        val model1 = TestModel("Test1")
-        val model2 = TestModel("Test2")
-        val model3 = TestModel("Test2")
-        val node3 = 3
-        val node4 = 4
-        map.add(model1, node1)
-        map.add(model1, node2)
-        map.add(model2, node3)
-        map.add(model3, node4)
-
-        map.assertNodes(listOf(model1, model2, model3), node1, node2, node3, node4)
-    }
-
-    @Test
-    fun testGetFiltersDuplicates() {
-        val model1 = TestModel("Test1")
-        val model2 = TestModel("Test2")
-        map.add(model1, node1)
-        map.add(model2, node1)
-
-        map.assertNodes(listOf(model1, model2), node1)
-    }
-
-    @Test
-    fun testRemoveValue() {
-        val model1 = TestModel("Test1")
-        val model2 = TestModel("Test2")
-        map.add(model1, node1)
-        map.add(model2, node1)
-        map.add(model2, node2)
-
-        map.removeValue(node1)
-        map.assertNodes(listOf(model2), node2)
-    }
-
-    private data class TestModel(var content: String = "Test")
-
-    private fun ObserverMap<TestModel, Int>.assertNodes(
-        model: TestModel,
-        vararg nodes: Int
-    ) {
-        assertNodes(listOf(model), *nodes)
-    }
-
-    private fun ObserverMap<TestModel, Int>.assertNodes(
-        models: List<TestModel>,
-        vararg nodes: Int
-    ) {
-        val expected = nodes.toList().sorted()
-        val actual = get(models).sorted()
-        assertEquals(expected, actual)
-    }
-}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
index 2f61e12..5910f88 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
@@ -3047,6 +3047,26 @@
             }
         }
     }
+
+    @Test
+    fun canRepositionReaderPastEndOfTable() {
+        val slots = SlotTable().also {
+            it.write { writer ->
+                // Create exactly 256 groups
+                repeat(256) {
+                    writer.insert {
+                        writer.startGroup(0)
+                        writer.endGroup()
+                    }
+                }
+            }
+        }
+
+        slots.read { reader ->
+            reader.reposition(reader.size)
+            // Expect the above not to crash.
+        }
+    }
 }
 
 @OptIn(InternalComposeApi::class)
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/ViewApplier.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/ViewApplier.kt
index 535c550..ae08758 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/ViewApplier.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/ViewApplier.kt
@@ -33,7 +33,11 @@
     var onEndChangesCalled = 0
         private set
 
-    override fun insert(index: Int, instance: View) {
+    override fun insertTopDown(index: Int, instance: View) {
+        // Ignored as the tree is built bottom-up.
+    }
+
+    override fun insertBottomUp(index: Int, instance: View) {
         current.addAt(index, instance)
     }
 
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
index a6f913d..0efdc74 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
@@ -34,7 +34,7 @@
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.dispatch.MonotonicFrameClock
 import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.test.TestMonotonicFrameClock
 import androidx.compose.ui.test.frameDelayMillis
@@ -134,7 +134,7 @@
         }
 
         composition = activity.setContent(recomposer) { testCase!!.Content() }
-        view = findAndroidOwner(activity)!!.view
+        view = findViewRootForTest(activity)!!.view
         @OptIn(ExperimentalComposeApi::class)
         Snapshot.notifyObjectsInitialized()
         simulationState = SimulationState.EmitContentDone
@@ -305,18 +305,18 @@
     RecomposeDone
 }
 
-private fun findAndroidOwner(activity: Activity): AndroidOwner? {
-    return findAndroidOwner(activity.findViewById(android.R.id.content) as ViewGroup)
+private fun findViewRootForTest(activity: Activity): ViewRootForTest? {
+    return findViewRootForTest(activity.findViewById(android.R.id.content) as ViewGroup)
 }
 
-private fun findAndroidOwner(view: View): AndroidOwner? {
-    if (view is AndroidOwner) {
+private fun findViewRootForTest(view: View): ViewRootForTest? {
+    if (view is ViewRootForTest) {
         return view
     }
 
     if (view is ViewGroup) {
         for (i in 0 until view.childCount) {
-            val composeView = findAndroidOwner(view.getChildAt(i))
+            val composeView = findViewRootForTest(view.getChildAt(i))
             if (composeView != null) {
                 return composeView
             }
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 2b56325..b18ab3f 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -31,6 +31,7 @@
     method public androidx.compose.ui.graphics.ColorFilter? getColorFilter();
     method public androidx.compose.ui.graphics.FilterQuality getFilterQuality();
     method public android.graphics.PathEffect? getNativePathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public android.graphics.Shader? getShader();
     method public androidx.compose.ui.graphics.StrokeCap getStrokeCap();
     method public androidx.compose.ui.graphics.StrokeJoin getStrokeJoin();
@@ -45,6 +46,7 @@
     method public void setColorFilter(androidx.compose.ui.graphics.ColorFilter? value);
     method public void setFilterQuality(androidx.compose.ui.graphics.FilterQuality value);
     method public void setNativePathEffect(android.graphics.PathEffect? value);
+    method public void setPathEffect(androidx.compose.ui.graphics.PathEffect? value);
     method public void setShader(android.graphics.Shader? value);
     method public void setStrokeCap(androidx.compose.ui.graphics.StrokeCap value);
     method public void setStrokeJoin(androidx.compose.ui.graphics.StrokeJoin value);
@@ -58,6 +60,7 @@
     property public androidx.compose.ui.graphics.FilterQuality filterQuality;
     property public boolean isAntiAlias;
     property public android.graphics.PathEffect? nativePathEffect;
+    property public androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public android.graphics.Shader? shader;
     property public androidx.compose.ui.graphics.StrokeCap strokeCap;
     property public androidx.compose.ui.graphics.StrokeJoin strokeJoin;
@@ -104,6 +107,11 @@
     property public boolean isEmpty;
   }
 
+  public final class AndroidPathEffectKt {
+    method public static android.graphics.PathEffect asAndroidPathEffect(androidx.compose.ui.graphics.PathEffect);
+    method public static androidx.compose.ui.graphics.PathEffect toComposePathEffect(android.graphics.PathEffect);
+  }
+
   public final class AndroidPathKt {
     method public static androidx.compose.ui.graphics.Path Path();
     method public static inline android.graphics.Path asAndroidPath(androidx.compose.ui.graphics.Path);
@@ -430,7 +438,8 @@
     method public long getColor-0d7_KjU();
     method public androidx.compose.ui.graphics.ColorFilter? getColorFilter();
     method public androidx.compose.ui.graphics.FilterQuality getFilterQuality();
-    method public android.graphics.PathEffect? getNativePathEffect();
+    method @Deprecated public android.graphics.PathEffect? getNativePathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public android.graphics.Shader? getShader();
     method public androidx.compose.ui.graphics.StrokeCap getStrokeCap();
     method public androidx.compose.ui.graphics.StrokeJoin getStrokeJoin();
@@ -444,7 +453,8 @@
     method public void setColor-8_81llA(long p);
     method public void setColorFilter(androidx.compose.ui.graphics.ColorFilter? p);
     method public void setFilterQuality(androidx.compose.ui.graphics.FilterQuality p);
-    method public void setNativePathEffect(android.graphics.PathEffect? p);
+    method @Deprecated public void setNativePathEffect(android.graphics.PathEffect? p);
+    method public void setPathEffect(androidx.compose.ui.graphics.PathEffect? p);
     method public void setShader(android.graphics.Shader? p);
     method public void setStrokeCap(androidx.compose.ui.graphics.StrokeCap p);
     method public void setStrokeJoin(androidx.compose.ui.graphics.StrokeJoin p);
@@ -457,7 +467,8 @@
     property public abstract androidx.compose.ui.graphics.ColorFilter? colorFilter;
     property public abstract androidx.compose.ui.graphics.FilterQuality filterQuality;
     property public abstract boolean isAntiAlias;
-    property public abstract android.graphics.PathEffect? nativePathEffect;
+    property @Deprecated public abstract android.graphics.PathEffect? nativePathEffect;
+    property public abstract androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public abstract android.graphics.Shader? shader;
     property public abstract androidx.compose.ui.graphics.StrokeCap strokeCap;
     property public abstract androidx.compose.ui.graphics.StrokeJoin strokeJoin;
@@ -511,6 +522,17 @@
     method public androidx.compose.ui.graphics.Path combine(androidx.compose.ui.graphics.PathOperation operation, androidx.compose.ui.graphics.Path path1, androidx.compose.ui.graphics.Path path2);
   }
 
+  public interface PathEffect {
+    field public static final androidx.compose.ui.graphics.PathEffect.Companion Companion;
+  }
+
+  public static final class PathEffect.Companion {
+    method public androidx.compose.ui.graphics.PathEffect chainPathEffect(androidx.compose.ui.graphics.PathEffect outer, androidx.compose.ui.graphics.PathEffect inner);
+    method public androidx.compose.ui.graphics.PathEffect cornerPathEffect(float radius);
+    method public androidx.compose.ui.graphics.PathEffect dashPathEffect(float[] intervals, optional float phase);
+    method public androidx.compose.ui.graphics.PathEffect stampedPathEffect(androidx.compose.ui.graphics.Path shape, float advance, float phase, androidx.compose.ui.graphics.StampedPathEffectStyle style);
+  }
+
   public enum PathFillType {
     enum_constant public static final androidx.compose.ui.graphics.PathFillType EvenOdd;
     enum_constant public static final androidx.compose.ui.graphics.PathFillType NonZero;
@@ -612,6 +634,12 @@
     property public final long value;
   }
 
+  public enum StampedPathEffectStyle {
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Morph;
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Rotate;
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Translate;
+  }
+
   public enum StrokeCap {
     enum_constant public static final androidx.compose.ui.graphics.StrokeCap Butt;
     enum_constant public static final androidx.compose.ui.graphics.StrokeCap Round;
@@ -860,14 +888,14 @@
     method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-1-s4MmQ(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-QXZmVdc(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-UXw4dv4(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-IdEHoqk(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath-tilSWAQ(androidx.compose.ui.graphics.Path path, long color, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints-8s8raUw(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints-Aqy9O-k(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-IdEHoqk(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRoundRect-fNghmuc(long color, long topLeft, long size, long cornerRadius, androidx.compose.ui.graphics.drawscope.DrawStyle style, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
@@ -906,14 +934,14 @@
     method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-1-s4MmQ(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-QXZmVdc(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-UXw4dv4(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-IdEHoqk(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath-tilSWAQ(androidx.compose.ui.graphics.Path path, long color, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints-8s8raUw(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints-Aqy9O-k(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-IdEHoqk(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRoundRect-fNghmuc(long color, optional long topLeft, optional long size, optional long cornerRadius, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
@@ -982,23 +1010,23 @@
   }
 
   public final class Stroke extends androidx.compose.ui.graphics.drawscope.DrawStyle {
-    ctor public Stroke(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, android.graphics.PathEffect? pathEffect);
+    ctor public Stroke(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, androidx.compose.ui.graphics.PathEffect? pathEffect);
     ctor public Stroke();
     method public float component1();
     method public float component2();
     method public androidx.compose.ui.graphics.StrokeCap component3();
     method public androidx.compose.ui.graphics.StrokeJoin component4();
-    method public android.graphics.PathEffect? component5();
-    method public androidx.compose.ui.graphics.drawscope.Stroke copy(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, android.graphics.PathEffect? pathEffect);
+    method public androidx.compose.ui.graphics.PathEffect? component5();
+    method public androidx.compose.ui.graphics.drawscope.Stroke copy(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, androidx.compose.ui.graphics.PathEffect? pathEffect);
     method public androidx.compose.ui.graphics.StrokeCap getCap();
     method public androidx.compose.ui.graphics.StrokeJoin getJoin();
     method public float getMiter();
-    method public android.graphics.PathEffect? getPathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public float getWidth();
     property public final androidx.compose.ui.graphics.StrokeCap cap;
     property public final androidx.compose.ui.graphics.StrokeJoin join;
     property public final float miter;
-    property public final android.graphics.PathEffect? pathEffect;
+    property public final androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public final float width;
     field public static final androidx.compose.ui.graphics.drawscope.Stroke.Companion Companion;
     field public static final float DefaultMiter = 4.0f;
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index 2b56325..b18ab3f 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -31,6 +31,7 @@
     method public androidx.compose.ui.graphics.ColorFilter? getColorFilter();
     method public androidx.compose.ui.graphics.FilterQuality getFilterQuality();
     method public android.graphics.PathEffect? getNativePathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public android.graphics.Shader? getShader();
     method public androidx.compose.ui.graphics.StrokeCap getStrokeCap();
     method public androidx.compose.ui.graphics.StrokeJoin getStrokeJoin();
@@ -45,6 +46,7 @@
     method public void setColorFilter(androidx.compose.ui.graphics.ColorFilter? value);
     method public void setFilterQuality(androidx.compose.ui.graphics.FilterQuality value);
     method public void setNativePathEffect(android.graphics.PathEffect? value);
+    method public void setPathEffect(androidx.compose.ui.graphics.PathEffect? value);
     method public void setShader(android.graphics.Shader? value);
     method public void setStrokeCap(androidx.compose.ui.graphics.StrokeCap value);
     method public void setStrokeJoin(androidx.compose.ui.graphics.StrokeJoin value);
@@ -58,6 +60,7 @@
     property public androidx.compose.ui.graphics.FilterQuality filterQuality;
     property public boolean isAntiAlias;
     property public android.graphics.PathEffect? nativePathEffect;
+    property public androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public android.graphics.Shader? shader;
     property public androidx.compose.ui.graphics.StrokeCap strokeCap;
     property public androidx.compose.ui.graphics.StrokeJoin strokeJoin;
@@ -104,6 +107,11 @@
     property public boolean isEmpty;
   }
 
+  public final class AndroidPathEffectKt {
+    method public static android.graphics.PathEffect asAndroidPathEffect(androidx.compose.ui.graphics.PathEffect);
+    method public static androidx.compose.ui.graphics.PathEffect toComposePathEffect(android.graphics.PathEffect);
+  }
+
   public final class AndroidPathKt {
     method public static androidx.compose.ui.graphics.Path Path();
     method public static inline android.graphics.Path asAndroidPath(androidx.compose.ui.graphics.Path);
@@ -430,7 +438,8 @@
     method public long getColor-0d7_KjU();
     method public androidx.compose.ui.graphics.ColorFilter? getColorFilter();
     method public androidx.compose.ui.graphics.FilterQuality getFilterQuality();
-    method public android.graphics.PathEffect? getNativePathEffect();
+    method @Deprecated public android.graphics.PathEffect? getNativePathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public android.graphics.Shader? getShader();
     method public androidx.compose.ui.graphics.StrokeCap getStrokeCap();
     method public androidx.compose.ui.graphics.StrokeJoin getStrokeJoin();
@@ -444,7 +453,8 @@
     method public void setColor-8_81llA(long p);
     method public void setColorFilter(androidx.compose.ui.graphics.ColorFilter? p);
     method public void setFilterQuality(androidx.compose.ui.graphics.FilterQuality p);
-    method public void setNativePathEffect(android.graphics.PathEffect? p);
+    method @Deprecated public void setNativePathEffect(android.graphics.PathEffect? p);
+    method public void setPathEffect(androidx.compose.ui.graphics.PathEffect? p);
     method public void setShader(android.graphics.Shader? p);
     method public void setStrokeCap(androidx.compose.ui.graphics.StrokeCap p);
     method public void setStrokeJoin(androidx.compose.ui.graphics.StrokeJoin p);
@@ -457,7 +467,8 @@
     property public abstract androidx.compose.ui.graphics.ColorFilter? colorFilter;
     property public abstract androidx.compose.ui.graphics.FilterQuality filterQuality;
     property public abstract boolean isAntiAlias;
-    property public abstract android.graphics.PathEffect? nativePathEffect;
+    property @Deprecated public abstract android.graphics.PathEffect? nativePathEffect;
+    property public abstract androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public abstract android.graphics.Shader? shader;
     property public abstract androidx.compose.ui.graphics.StrokeCap strokeCap;
     property public abstract androidx.compose.ui.graphics.StrokeJoin strokeJoin;
@@ -511,6 +522,17 @@
     method public androidx.compose.ui.graphics.Path combine(androidx.compose.ui.graphics.PathOperation operation, androidx.compose.ui.graphics.Path path1, androidx.compose.ui.graphics.Path path2);
   }
 
+  public interface PathEffect {
+    field public static final androidx.compose.ui.graphics.PathEffect.Companion Companion;
+  }
+
+  public static final class PathEffect.Companion {
+    method public androidx.compose.ui.graphics.PathEffect chainPathEffect(androidx.compose.ui.graphics.PathEffect outer, androidx.compose.ui.graphics.PathEffect inner);
+    method public androidx.compose.ui.graphics.PathEffect cornerPathEffect(float radius);
+    method public androidx.compose.ui.graphics.PathEffect dashPathEffect(float[] intervals, optional float phase);
+    method public androidx.compose.ui.graphics.PathEffect stampedPathEffect(androidx.compose.ui.graphics.Path shape, float advance, float phase, androidx.compose.ui.graphics.StampedPathEffectStyle style);
+  }
+
   public enum PathFillType {
     enum_constant public static final androidx.compose.ui.graphics.PathFillType EvenOdd;
     enum_constant public static final androidx.compose.ui.graphics.PathFillType NonZero;
@@ -612,6 +634,12 @@
     property public final long value;
   }
 
+  public enum StampedPathEffectStyle {
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Morph;
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Rotate;
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Translate;
+  }
+
   public enum StrokeCap {
     enum_constant public static final androidx.compose.ui.graphics.StrokeCap Butt;
     enum_constant public static final androidx.compose.ui.graphics.StrokeCap Round;
@@ -860,14 +888,14 @@
     method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-1-s4MmQ(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-QXZmVdc(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-UXw4dv4(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-IdEHoqk(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath-tilSWAQ(androidx.compose.ui.graphics.Path path, long color, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints-8s8raUw(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints-Aqy9O-k(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-IdEHoqk(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRoundRect-fNghmuc(long color, long topLeft, long size, long cornerRadius, androidx.compose.ui.graphics.drawscope.DrawStyle style, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
@@ -906,14 +934,14 @@
     method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-1-s4MmQ(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-QXZmVdc(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-UXw4dv4(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-IdEHoqk(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath-tilSWAQ(androidx.compose.ui.graphics.Path path, long color, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints-8s8raUw(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints-Aqy9O-k(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-IdEHoqk(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRoundRect-fNghmuc(long color, optional long topLeft, optional long size, optional long cornerRadius, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
@@ -982,23 +1010,23 @@
   }
 
   public final class Stroke extends androidx.compose.ui.graphics.drawscope.DrawStyle {
-    ctor public Stroke(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, android.graphics.PathEffect? pathEffect);
+    ctor public Stroke(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, androidx.compose.ui.graphics.PathEffect? pathEffect);
     ctor public Stroke();
     method public float component1();
     method public float component2();
     method public androidx.compose.ui.graphics.StrokeCap component3();
     method public androidx.compose.ui.graphics.StrokeJoin component4();
-    method public android.graphics.PathEffect? component5();
-    method public androidx.compose.ui.graphics.drawscope.Stroke copy(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, android.graphics.PathEffect? pathEffect);
+    method public androidx.compose.ui.graphics.PathEffect? component5();
+    method public androidx.compose.ui.graphics.drawscope.Stroke copy(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, androidx.compose.ui.graphics.PathEffect? pathEffect);
     method public androidx.compose.ui.graphics.StrokeCap getCap();
     method public androidx.compose.ui.graphics.StrokeJoin getJoin();
     method public float getMiter();
-    method public android.graphics.PathEffect? getPathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public float getWidth();
     property public final androidx.compose.ui.graphics.StrokeCap cap;
     property public final androidx.compose.ui.graphics.StrokeJoin join;
     property public final float miter;
-    property public final android.graphics.PathEffect? pathEffect;
+    property public final androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public final float width;
     field public static final androidx.compose.ui.graphics.drawscope.Stroke.Companion Companion;
     field public static final float DefaultMiter = 4.0f;
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index f6b1064..acc2da8 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -61,6 +61,7 @@
     method public androidx.compose.ui.graphics.ColorFilter? getColorFilter();
     method public androidx.compose.ui.graphics.FilterQuality getFilterQuality();
     method public android.graphics.PathEffect? getNativePathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public android.graphics.Shader? getShader();
     method public androidx.compose.ui.graphics.StrokeCap getStrokeCap();
     method public androidx.compose.ui.graphics.StrokeJoin getStrokeJoin();
@@ -75,6 +76,7 @@
     method public void setColorFilter(androidx.compose.ui.graphics.ColorFilter? value);
     method public void setFilterQuality(androidx.compose.ui.graphics.FilterQuality value);
     method public void setNativePathEffect(android.graphics.PathEffect? value);
+    method public void setPathEffect(androidx.compose.ui.graphics.PathEffect? value);
     method public void setShader(android.graphics.Shader? value);
     method public void setStrokeCap(androidx.compose.ui.graphics.StrokeCap value);
     method public void setStrokeJoin(androidx.compose.ui.graphics.StrokeJoin value);
@@ -88,6 +90,7 @@
     property public androidx.compose.ui.graphics.FilterQuality filterQuality;
     property public boolean isAntiAlias;
     property public android.graphics.PathEffect? nativePathEffect;
+    property public androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public android.graphics.Shader? shader;
     property public androidx.compose.ui.graphics.StrokeCap strokeCap;
     property public androidx.compose.ui.graphics.StrokeJoin strokeJoin;
@@ -134,6 +137,11 @@
     property public boolean isEmpty;
   }
 
+  public final class AndroidPathEffectKt {
+    method public static android.graphics.PathEffect asAndroidPathEffect(androidx.compose.ui.graphics.PathEffect);
+    method public static androidx.compose.ui.graphics.PathEffect toComposePathEffect(android.graphics.PathEffect);
+  }
+
   public final class AndroidPathKt {
     method public static androidx.compose.ui.graphics.Path Path();
     method public static inline android.graphics.Path asAndroidPath(androidx.compose.ui.graphics.Path);
@@ -462,7 +470,8 @@
     method public long getColor-0d7_KjU();
     method public androidx.compose.ui.graphics.ColorFilter? getColorFilter();
     method public androidx.compose.ui.graphics.FilterQuality getFilterQuality();
-    method public android.graphics.PathEffect? getNativePathEffect();
+    method @Deprecated public android.graphics.PathEffect? getNativePathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public android.graphics.Shader? getShader();
     method public androidx.compose.ui.graphics.StrokeCap getStrokeCap();
     method public androidx.compose.ui.graphics.StrokeJoin getStrokeJoin();
@@ -476,7 +485,8 @@
     method public void setColor-8_81llA(long p);
     method public void setColorFilter(androidx.compose.ui.graphics.ColorFilter? p);
     method public void setFilterQuality(androidx.compose.ui.graphics.FilterQuality p);
-    method public void setNativePathEffect(android.graphics.PathEffect? p);
+    method @Deprecated public void setNativePathEffect(android.graphics.PathEffect? p);
+    method public void setPathEffect(androidx.compose.ui.graphics.PathEffect? p);
     method public void setShader(android.graphics.Shader? p);
     method public void setStrokeCap(androidx.compose.ui.graphics.StrokeCap p);
     method public void setStrokeJoin(androidx.compose.ui.graphics.StrokeJoin p);
@@ -489,7 +499,8 @@
     property public abstract androidx.compose.ui.graphics.ColorFilter? colorFilter;
     property public abstract androidx.compose.ui.graphics.FilterQuality filterQuality;
     property public abstract boolean isAntiAlias;
-    property public abstract android.graphics.PathEffect? nativePathEffect;
+    property @Deprecated public abstract android.graphics.PathEffect? nativePathEffect;
+    property public abstract androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public abstract android.graphics.Shader? shader;
     property public abstract androidx.compose.ui.graphics.StrokeCap strokeCap;
     property public abstract androidx.compose.ui.graphics.StrokeJoin strokeJoin;
@@ -543,6 +554,17 @@
     method public androidx.compose.ui.graphics.Path combine(androidx.compose.ui.graphics.PathOperation operation, androidx.compose.ui.graphics.Path path1, androidx.compose.ui.graphics.Path path2);
   }
 
+  public interface PathEffect {
+    field public static final androidx.compose.ui.graphics.PathEffect.Companion Companion;
+  }
+
+  public static final class PathEffect.Companion {
+    method public androidx.compose.ui.graphics.PathEffect chainPathEffect(androidx.compose.ui.graphics.PathEffect outer, androidx.compose.ui.graphics.PathEffect inner);
+    method public androidx.compose.ui.graphics.PathEffect cornerPathEffect(float radius);
+    method public androidx.compose.ui.graphics.PathEffect dashPathEffect(float[] intervals, optional float phase);
+    method public androidx.compose.ui.graphics.PathEffect stampedPathEffect(androidx.compose.ui.graphics.Path shape, float advance, float phase, androidx.compose.ui.graphics.StampedPathEffectStyle style);
+  }
+
   public enum PathFillType {
     enum_constant public static final androidx.compose.ui.graphics.PathFillType EvenOdd;
     enum_constant public static final androidx.compose.ui.graphics.PathFillType NonZero;
@@ -644,6 +666,12 @@
     property public final long value;
   }
 
+  public enum StampedPathEffectStyle {
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Morph;
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Rotate;
+    enum_constant public static final androidx.compose.ui.graphics.StampedPathEffectStyle Translate;
+  }
+
   public enum StrokeCap {
     enum_constant public static final androidx.compose.ui.graphics.StrokeCap Butt;
     enum_constant public static final androidx.compose.ui.graphics.StrokeCap Round;
@@ -892,14 +920,14 @@
     method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-1-s4MmQ(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-QXZmVdc(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-UXw4dv4(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-IdEHoqk(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath-tilSWAQ(androidx.compose.ui.graphics.Path path, long color, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints-8s8raUw(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints-Aqy9O-k(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-IdEHoqk(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRoundRect-fNghmuc(long color, long topLeft, long size, long cornerRadius, androidx.compose.ui.graphics.drawscope.DrawStyle style, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
@@ -962,14 +990,14 @@
     method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-1-s4MmQ(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-QXZmVdc(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawLine-UXw4dv4(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawOval-IdEHoqk(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawPath-tilSWAQ(androidx.compose.ui.graphics.Path path, long color, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
-    method public void drawPoints-8s8raUw(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+    method public void drawPoints-Aqy9O-k(java.util.List<androidx.compose.ui.geometry.Offset> points, androidx.compose.ui.graphics.PointMode pointMode, long color, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRect-IdEHoqk(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
     method public void drawRoundRect-fNghmuc(long color, optional long topLeft, optional long size, optional long cornerRadius, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
@@ -1038,23 +1066,23 @@
   }
 
   public final class Stroke extends androidx.compose.ui.graphics.drawscope.DrawStyle {
-    ctor public Stroke(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, android.graphics.PathEffect? pathEffect);
+    ctor public Stroke(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, androidx.compose.ui.graphics.PathEffect? pathEffect);
     ctor public Stroke();
     method public float component1();
     method public float component2();
     method public androidx.compose.ui.graphics.StrokeCap component3();
     method public androidx.compose.ui.graphics.StrokeJoin component4();
-    method public android.graphics.PathEffect? component5();
-    method public androidx.compose.ui.graphics.drawscope.Stroke copy(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, android.graphics.PathEffect? pathEffect);
+    method public androidx.compose.ui.graphics.PathEffect? component5();
+    method public androidx.compose.ui.graphics.drawscope.Stroke copy(float width, float miter, androidx.compose.ui.graphics.StrokeCap cap, androidx.compose.ui.graphics.StrokeJoin join, androidx.compose.ui.graphics.PathEffect? pathEffect);
     method public androidx.compose.ui.graphics.StrokeCap getCap();
     method public androidx.compose.ui.graphics.StrokeJoin getJoin();
     method public float getMiter();
-    method public android.graphics.PathEffect? getPathEffect();
+    method public androidx.compose.ui.graphics.PathEffect? getPathEffect();
     method public float getWidth();
     property public final androidx.compose.ui.graphics.StrokeCap cap;
     property public final androidx.compose.ui.graphics.StrokeJoin join;
     property public final float miter;
-    property public final android.graphics.PathEffect? pathEffect;
+    property public final androidx.compose.ui.graphics.PathEffect? pathEffect;
     property public final float width;
     field public static final androidx.compose.ui.graphics.drawscope.Stroke.Companion Companion;
     field public static final float DefaultMiter = 4.0f;
diff --git a/compose/ui/ui-graphics/samples/src/main/java/androidx/compose/ui/graphics/samples/PathEffectSample.kt b/compose/ui/ui-graphics/samples/src/main/java/androidx/compose/ui/graphics/samples/PathEffectSample.kt
new file mode 100644
index 0000000..09c5dcc
--- /dev/null
+++ b/compose/ui/ui-graphics/samples/src/main/java/androidx/compose/ui/graphics/samples/PathEffectSample.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.graphics.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+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.graphics.Path
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.StampedPathEffectStyle
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.unit.dp
+
+@Sampled
+@Composable
+fun StampedPathEffectSample() {
+    val size = 20f
+    val square = Path().apply {
+        lineTo(size, 0f)
+        lineTo(size, size)
+        lineTo(0f, size)
+        close()
+    }
+    Column(modifier = Modifier.fillMaxHeight().wrapContentSize(Alignment.Center)) {
+        val canvasModifier = Modifier.size(80.dp).align(Alignment.CenterHorizontally)
+
+        // StampedPathEffectStyle.Morph will modify the lines of the square to be curved to fit
+        // the curvature of the circle itself. Each stamped square will be rendered as an arc
+        // that is fully contained by the bounds of the circle itself
+        Canvas(modifier = canvasModifier) {
+            drawCircle(color = Color.Blue)
+            drawCircle(
+                color = Color.Red,
+                style = Stroke(
+                    pathEffect = PathEffect.stampedPathEffect(
+                        shape = square,
+                        style = StampedPathEffectStyle.Morph,
+                        phase = 0f,
+                        advance = 30f
+                    )
+                )
+            )
+        }
+
+        Spacer(modifier = Modifier.size(10.dp))
+
+        // StampedPathEffectStyle.Rotate will draw the square repeatedly around the circle
+        // such that each stamped square is centered on the circumference of the circle and is
+        // rotated along the curvature of the circle itself
+        Canvas(modifier = canvasModifier) {
+            drawCircle(color = Color.Blue)
+            drawCircle(
+                color = Color.Red,
+                style = Stroke(
+                    pathEffect = PathEffect.stampedPathEffect(
+                        shape = square,
+                        style = StampedPathEffectStyle.Rotate,
+                        phase = 0f,
+                        advance = 30f
+                    )
+                )
+            )
+        }
+
+        Spacer(modifier = Modifier.size(10.dp))
+
+        // StampedPathEffectStyle.Translate will draw the square repeatedly around the circle
+        // with the top left of each stamped square on the circumference of the circle
+        Canvas(modifier = canvasModifier) {
+            drawCircle(color = Color.Blue)
+            drawCircle(
+                color = Color.Red,
+                style = Stroke(
+                    pathEffect = PathEffect.stampedPathEffect(
+                        shape = square,
+                        style = StampedPathEffectStyle.Translate,
+                        phase = 0f,
+                        advance = 30f
+                    )
+                )
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
index ecec048..c5bb9d6 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
@@ -17,8 +17,8 @@
 package androidx.compose.ui.graphics
 
 import android.content.Context
+import android.graphics.Bitmap
 import android.graphics.Canvas
-import android.graphics.Color
 import android.os.Build
 import android.util.AttributeSet
 import android.view.Gravity
@@ -61,14 +61,14 @@
         activityTestRule.runOnUiThread {
             val group = EnableDisableZViewGroup(drawLatch, activity)
             groupView = group
-            group.setBackgroundColor(Color.WHITE)
+            group.setBackgroundColor(android.graphics.Color.WHITE)
             group.layoutParams = ViewGroup.LayoutParams(12, 12)
             val child = View(activity)
             val childLayoutParams = FrameLayout.LayoutParams(10, 10)
             childLayoutParams.gravity = Gravity.TOP or Gravity.LEFT
             child.layoutParams = childLayoutParams
             child.elevation = 4f
-            child.setBackgroundColor(Color.WHITE)
+            child.setBackgroundColor(android.graphics.Color.WHITE)
             group.addView(child)
             activity.setContentView(group)
         }
@@ -78,9 +78,9 @@
         // the drawn content can get onto the screen before we capture the bitmap.
         activityTestRule.runOnUiThread { }
         val bitmap = groupView!!.captureToImage().asAndroidBitmap()
-        assertEquals(Color.WHITE, bitmap.getPixel(0, 0))
-        assertEquals(Color.WHITE, bitmap.getPixel(9, 9))
-        assertNotEquals(Color.WHITE, bitmap.getPixel(10, 10))
+        assertEquals(android.graphics.Color.WHITE, bitmap.getPixel(0, 0))
+        assertEquals(android.graphics.Color.WHITE, bitmap.getPixel(9, 9))
+        assertNotEquals(android.graphics.Color.WHITE, bitmap.getPixel(10, 10))
     }
 
     @Test
@@ -266,6 +266,215 @@
         assertEquals(bg, pixelMap[75, 76])
     }
 
+    @Test
+    fun testCornerPathEffect() {
+        val width = 80
+        val height = 80
+        val radius = 20f
+        val imageBitmap = ImageBitmap(width, height)
+        imageBitmap.asAndroidBitmap().eraseColor(android.graphics.Color.WHITE)
+        val canvas = Canvas(imageBitmap)
+        canvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            Paint().apply {
+                color = Color.Blue
+                pathEffect = PathEffect.cornerPathEffect(radius)
+            }
+        )
+
+        val androidBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        androidBitmap.eraseColor(android.graphics.Color.WHITE)
+        val androidCanvas = android.graphics.Canvas(androidBitmap)
+        androidCanvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            android.graphics.Paint().apply {
+                isAntiAlias = true
+                color = android.graphics.Color.BLUE
+                pathEffect = android.graphics.CornerPathEffect(radius)
+            }
+        )
+
+        val composePixels = imageBitmap.toPixelMap()
+        for (i in 0 until 80) {
+            for (j in 0 until 80) {
+                assertEquals(
+                    "invalid color at i: " + i + ", " + j,
+                    composePixels[i, j].toArgb(),
+                    androidBitmap.getPixel(i, j)
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testDashPathEffect() {
+        val width = 80
+        val height = 80
+        val imageBitmap = ImageBitmap(width, height)
+        imageBitmap.asAndroidBitmap().eraseColor(android.graphics.Color.WHITE)
+        val canvas = Canvas(imageBitmap)
+        canvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            Paint().apply {
+                color = Color.Blue
+                pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 5f), 8f)
+            }
+        )
+
+        val androidBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        androidBitmap.eraseColor(android.graphics.Color.WHITE)
+        val androidCanvas = android.graphics.Canvas(androidBitmap)
+        androidCanvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            android.graphics.Paint().apply {
+                isAntiAlias = true
+                color = android.graphics.Color.BLUE
+                pathEffect = android.graphics.DashPathEffect(floatArrayOf(10f, 5f), 8f)
+            }
+        )
+
+        val composePixels = imageBitmap.toPixelMap()
+        for (i in 0 until 80) {
+            for (j in 0 until 80) {
+                assertEquals(
+                    "invalid color at i: " + i + ", " + j,
+                    composePixels[i, j].toArgb(),
+                    androidBitmap.getPixel(i, j)
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testChainPathEffect() {
+        val width = 80
+        val height = 80
+        val imageBitmap = ImageBitmap(width, height)
+        imageBitmap.asAndroidBitmap().eraseColor(android.graphics.Color.WHITE)
+        val canvas = Canvas(imageBitmap)
+        canvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            Paint().apply {
+                color = Color.Blue
+                pathEffect =
+                    PathEffect.chainPathEffect(
+                        PathEffect.dashPathEffect(floatArrayOf(10f, 5f), 8f),
+                        PathEffect.cornerPathEffect(20f)
+                    )
+            }
+        )
+
+        val androidBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        androidBitmap.eraseColor(android.graphics.Color.WHITE)
+        val androidCanvas = android.graphics.Canvas(androidBitmap)
+        androidCanvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            android.graphics.Paint().apply {
+                isAntiAlias = true
+                color = android.graphics.Color.BLUE
+                pathEffect =
+                    android.graphics.ComposePathEffect(
+                        android.graphics.DashPathEffect(floatArrayOf(10f, 5f), 8f),
+                        android.graphics.CornerPathEffect(20f)
+                    )
+            }
+        )
+
+        val composePixels = imageBitmap.toPixelMap()
+        for (i in 0 until 80) {
+            for (j in 0 until 80) {
+                assertEquals(
+                    "invalid color at i: " + i + ", " + j,
+                    composePixels[i, j].toArgb(),
+                    androidBitmap.getPixel(i, j)
+                )
+            }
+        }
+    }
+
+    @Test
+    fun testPathDashPathEffect() {
+        val width = 80
+        val height = 80
+        val imageBitmap = ImageBitmap(width, height)
+        imageBitmap.asAndroidBitmap().eraseColor(android.graphics.Color.WHITE)
+        val canvas = Canvas(imageBitmap)
+        canvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            Paint().apply {
+                color = Color.Blue
+                pathEffect =
+                    PathEffect.stampedPathEffect(
+                        Path().apply {
+                            lineTo(0f, 5f)
+                            lineTo(5f, 5f)
+                            close()
+                        },
+                        5f,
+                        2f,
+                        StampedPathEffectStyle.Rotate
+                    )
+            }
+        )
+
+        val androidBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        androidBitmap.eraseColor(android.graphics.Color.WHITE)
+        val androidCanvas = android.graphics.Canvas(androidBitmap)
+        androidCanvas.drawRect(
+            0f,
+            0f,
+            width.toFloat(),
+            height.toFloat(),
+            android.graphics.Paint().apply {
+                isAntiAlias = true
+                color = android.graphics.Color.BLUE
+                pathEffect =
+                    android.graphics.PathDashPathEffect(
+                        android.graphics.Path().apply {
+                            lineTo(0f, 5f)
+                            lineTo(5f, 5f)
+                            close()
+                        },
+                        5f,
+                        2f,
+                        android.graphics.PathDashPathEffect.Style.ROTATE
+                    )
+            }
+        )
+
+        val composePixels = imageBitmap.toPixelMap()
+        for (i in 0 until 80) {
+            for (j in 0 until 80) {
+                assertEquals(
+                    "invalid color at i: " + i + ", " + j,
+                    composePixels[i, j].toArgb(),
+                    androidBitmap.getPixel(i, j)
+                )
+            }
+        }
+    }
+
     class EnableDisableZViewGroup @JvmOverloads constructor(
         val drawLatch: CountDownLatch,
         context: Context,
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPaint.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPaint.kt
index 2eca0e3..30b33a1 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPaint.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPaint.kt
@@ -108,7 +108,17 @@
             internalPaint.setNativeColorFilter(value)
         }
 
-    override var nativePathEffect: NativePathEffect? = null
+    override var nativePathEffect: NativePathEffect?
+        get() = pathEffect?.asAndroidPathEffect()
+        set(value) {
+            pathEffect = if (value == null) {
+                null
+            } else {
+                AndroidPathEffect(value)
+            }
+        }
+
+    override var pathEffect: PathEffect? = null
         set(value) {
             internalPaint.setNativePathEffect(value)
             field = value
@@ -235,6 +245,6 @@
     this.shader = value
 }
 
-internal fun NativePaint.setNativePathEffect(value: NativePathEffect?) {
-    this.pathEffect = value
+internal fun NativePaint.setNativePathEffect(value: PathEffect?) {
+    this.pathEffect = (value as AndroidPathEffect).nativePathEffect
 }
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPathEffect.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPathEffect.kt
new file mode 100644
index 0000000..4755271
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPathEffect.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.graphics
+
+import android.graphics.PathDashPathEffect
+
+/**
+ * Obtain a reference to the Android PathEffect type
+ */
+internal class AndroidPathEffect(val nativePathEffect: android.graphics.PathEffect) : PathEffect
+
+fun PathEffect.asAndroidPathEffect(): android.graphics.PathEffect =
+    (this as AndroidPathEffect).nativePathEffect
+
+fun android.graphics.PathEffect.toComposePathEffect(): PathEffect = AndroidPathEffect(this)
+
+internal actual fun actualCornerPathEffect(radius: Float): PathEffect =
+    AndroidPathEffect(android.graphics.CornerPathEffect(radius))
+
+internal actual fun actualDashPathEffect(intervals: FloatArray, phase: Float): PathEffect =
+    AndroidPathEffect(android.graphics.DashPathEffect(intervals, phase))
+
+internal actual fun actualChainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect =
+    AndroidPathEffect(
+        android.graphics.ComposePathEffect(
+            (outer as AndroidPathEffect).nativePathEffect,
+            (inner as AndroidPathEffect).nativePathEffect
+        )
+    )
+
+internal actual fun actualStampedPathEffect(
+    shape: Path,
+    advance: Float,
+    phase: Float,
+    style: StampedPathEffectStyle
+): PathEffect =
+    AndroidPathEffect(
+        PathDashPathEffect(
+            shape.asAndroidPath(),
+            advance,
+            phase,
+            style.toAndroidPathDashPathEffectStyle()
+        )
+    )
+
+internal fun StampedPathEffectStyle.toAndroidPathDashPathEffectStyle() =
+    when (this) {
+        StampedPathEffectStyle.Morph -> PathDashPathEffect.Style.MORPH
+        StampedPathEffectStyle.Rotate -> PathDashPathEffect.Style.ROTATE
+        StampedPathEffectStyle.Translate -> PathDashPathEffect.Style.TRANSLATE
+    }
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Paint.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Paint.kt
index bdbd687..32a3905 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Paint.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Paint.kt
@@ -28,111 +28,126 @@
 interface Paint {
     fun asFrameworkPaint(): NativePaint
 
+    /**
+     * Configures the alpha value between 0f to 1f representing fully transparent to fully
+     * opaque for the color drawn with this Paint
+     */
     var alpha: Float
 
-    // Whether to apply anti-aliasing to lines and images drawn on the
-    // canvas.
-    //
-    // Defaults to true.
+    /**
+     * Whether to apply anti-aliasing to lines and images drawn on the
+     * canvas.
+     * Defaults to true.
+     */
     var isAntiAlias: Boolean
 
-    // The color to use when stroking or filling a shape.
-    //
-    // Defaults to opaque black.
-    //
-    // See also:
-    //
-    //  * [style], which controls whether to stroke or fill (or both).
-    //  * [colorFilter], which overrides [color].
-    //  * [shader], which overrides [color] with more elaborate effects.
-    //
-    // This color is not used when compositing. To colorize a layer, use
-    // [colorFilter].
+    /**
+     * The color to use when stroking or filling a shape.
+     * Defaults to opaque black.
+     * See also:
+     * [style], which controls whether to stroke or fill (or both).
+     * [colorFilter], which overrides [color].
+     * [shader], which overrides [color] with more elaborate effects.
+     * This color is not used when compositing. To colorize a layer, use [colorFilter].
+     */
     var color: Color
 
-    // A blend mode to apply when a shape is drawn or a layer is composited.
-    //
-    // The source colors are from the shape being drawn (e.g. from
-    // [Canvas.drawPath]) or layer being composited (the graphics that were drawn
-    // between the [Canvas.saveLayer] and [Canvas.restore] calls), after applying
-    // the [colorFilter], if any.
-    //
-    // The destination colors are from the background onto which the shape or
-    // layer is being composited.
-    //
-    // Defaults to [BlendMode.srcOver].
-    //
-    // See also:
-    //
-    //  * [Canvas.saveLayer], which uses its [Paint]'s [blendMode] to composite
-    //    the layer when [restore] is called.
-    //  * [BlendMode], which discusses the user of [saveLayer] with [blendMode].
+    /**
+     * A blend mode to apply when a shape is drawn or a layer is composited.
+     * The source colors are from the shape being drawn (e.g. from
+     * [Canvas.drawPath]) or layer being composited (the graphics that were drawn
+     * between the [Canvas.saveLayer] and [Canvas.restore] calls), after applying
+     * the [colorFilter], if any.
+     * The destination colors are from the background onto which the shape or
+     * layer is being composited.
+     * Defaults to [BlendMode.SrcOver].
+     * See also:
+     * [Canvas.saveLayer], which uses its [Paint]'s [blendMode] to composite
+     * the layer when [Canvas.restore] is called.
+     * [BlendMode], which discusses the user of [Canvas.saveLayer] with [blendMode].
+     */
     var blendMode: BlendMode
 
-    // Whether to paint inside shapes, the edges of shapes, or both.
-    //
-    // Defaults to [PaintingStyle.fill].
+    /**
+     * Whether to paint inside shapes, the edges of shapes, or both.
+     * Defaults to [PaintingStyle.Fill].
+     */
     var style: PaintingStyle
 
-    // How wide to make edges drawn when [style] is set to
-    // [PaintingStyle.stroke]. The width is given in logical pixels measured in
-    // the direction orthogonal to the direction of the path.
-    //
-    // Defaults to 0.0, which correspond to a hairline width.
+    /**
+     * How wide to make edges drawn when [style] is set to
+     * [PaintingStyle.Stroke]. The width is given in logical pixels measured in
+     * the direction orthogonal to the direction of the path.
+     * Defaults to 0.0, which correspond to a hairline width.
+     */
     var strokeWidth: Float
 
-    // The kind of finish to place on the end of lines drawn when
-    // [style] is set to [PaintingStyle.stroke].
-    //
-    // Defaults to [StrokeCap.butt], i.e. no caps.
+    /**
+     * The kind of finish to place on the end of lines drawn when
+     * [style] is set to [PaintingStyle.Stroke].
+     * Defaults to [StrokeCap.Butt], i.e. no caps.
+     */
     var strokeCap: StrokeCap
 
-    // The kind of finish to place on the joins between segments.
-    //
-    // This applies to paths drawn when [style] is set to [PaintingStyle.stroke],
-    // It does not apply to points drawn as lines with [Canvas.drawPoints].
-    //
-    // Defaults to [StrokeJoin.miter], i.e. sharp corners. See also
-    // [strokeMiterLimit] to control when miters are replaced by bevels.
+    /**
+     * The kind of finish to place on the joins between segments.
+     * This applies to paths drawn when [style] is set to [PaintingStyle.Stroke],
+     * It does not apply to points drawn as lines with [Canvas.drawPoints].
+     * Defaults to [StrokeJoin.Miter], i.e. sharp corners. See also
+     * [strokeMiterLimit] to control when miters are replaced by bevels.
+     */
     var strokeJoin: StrokeJoin
 
-    // The limit for miters to be drawn on segments when the join is set to
-    // [StrokeJoin.miter] and the [style] is set to [PaintingStyle.stroke]. If
-    // this limit is exceeded, then a [StrokeJoin.bevel] join will be drawn
-    // instead. This may cause some 'popping' of the corners of a path if the
-    // angle between line segments is animated.
-    //
-    // This limit is expressed as a limit on the length of the miter.
-    //
-    // Defaults to 4.0.  Using zero as a limit will cause a [StrokeJoin.bevel]
-    // join to be used all the time.
+    /**
+     * The limit for miters to be drawn on segments when the join is set to
+     * [StrokeJoin.Miter] and the [style] is set to [PaintingStyle.Stroke]. If
+     * this limit is exceeded, then a [StrokeJoin.Bevel] join will be drawn
+     * instead. This may cause some 'popping' of the corners of a path if the
+     * angle between line segments is animated.
+     * This limit is expressed as a limit on the length of the miter.
+     * Defaults to 4.0.  Using zero as a limit will cause a [StrokeJoin.Bevel]
+     * join to be used all the time.
+     */
     var strokeMiterLimit: Float
 
-    // Controls the performance vs quality trade-off to use when applying
-    // when drawing images, as with [Canvas.drawImageRect]
-    //
-    // Defaults to [FilterQuality.none].
+    /**
+     * Controls the performance vs quality trade-off to use when applying
+     * when drawing images, as with [Canvas.drawImageRect]
+     * Defaults to [FilterQuality.None].
+     */
     var filterQuality: FilterQuality
 
-    // The shader to use when stroking or filling a shape.
-    //
-    // When this is null, the [color] is used instead.
-    //
-    // See also:
-    //
-    //  * [Gradient], a shader that paints a color gradient.
-    //  * [ImageShader], a shader that tiles an [Image].
-    //  * [colorFilter], which overrides [shader].
-    //  * [color], which is used if [shader] and [colorFilter] are null.
+    /**
+     * The shader to use when stroking or filling a shape.
+     *
+     * When this is null, the [color] is used instead.
+     *
+     * See also:
+     * [LinearGradientShader], [RadialGradientShader], or [SweepGradientShader] shaders that
+     * paint a color gradient.
+     * [ImageShader], a shader that tiles an [ImageBitmap].
+     * [colorFilter], which overrides [shader].
+     * [color], which is used if [shader] and [colorFilter] are null.
+     */
     var shader: Shader?
 
-    // A color filter to apply when a shape is drawn or when a layer is
-    // composited.
-    //
-    // See [ColorFilter] for details.
-    //
-    // When a shape is being drawn, [colorFilter] overrides [color] and [shader].
+    /**
+     *  A color filter to apply when a shape is drawn or when a layer is
+     *  composited.
+     *  See [ColorFilter] for details.
+     *  When a shape is being drawn, [colorFilter] overrides [color] and [shader].
+     */
     var colorFilter: ColorFilter?
 
+    @Suppress("DEPRECATION")
+    @Deprecated(
+        "Use pathEffect instead",
+        ReplaceWith("pathEffect", "androidx.compose.ui.graphics.Paint")
+    )
     var nativePathEffect: NativePathEffect?
+
+    /**
+     * Specifies the [PathEffect] applied to the geometry of the shape that is drawn
+     */
+    var pathEffect: PathEffect?
 }
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 91e53db..ccf90c0 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
@@ -22,6 +22,10 @@
 
 expect fun Path(): Path
 
+@Deprecated(
+    "Use PathEffect instead",
+    ReplaceWith("PathEffect", "androidx.compose.ui.graphics.PathEffect")
+)
 expect class NativePathEffect
 
 /* expect class */ interface Path {
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathEffect.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathEffect.kt
new file mode 100644
index 0000000..fb12a5d
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathEffect.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.graphics
+
+/**
+ * Effect applied to the geometry of a drawing primitive. For example, this can be used
+ * to draw shapes as a dashed or shaped pattern, or apply a treatment around line segment
+ * intersections.
+ */
+interface PathEffect {
+    companion object {
+
+        /**
+         * Replaces sharp angles between line segments into rounded angles of the specified radius
+         *
+         * @param radius Rounded corner radius to apply for each angle of the drawn shape
+         */
+        fun cornerPathEffect(radius: Float): PathEffect = actualCornerPathEffect(radius)
+
+        /**
+         * Draws a shape as a series of dashes with the given intervals and offset into the specified
+         * interval array. The intervals must contain an even number of entries (>=2). The even indices
+         * specify "on" intervals and the odd indices represent "off" intervals. The phase parameter
+         * is the pixel offset into the intervals array (mod the sum of all of the intervals).
+         *
+         * For example: if `intervals[] = {10, 20}`, and phase = 25, this will set up a dashed
+         * path like so: 5 pixels off 10 pixels on 20 pixels off 10 pixels on 20 pixels off
+         *
+         * The phase parameter is
+         * an offset into the intervals array. The intervals array
+         * controls the length of the dashes. This is only applied for stroked shapes
+         * (ex. [PaintingStyle.Stroke] and is ignored for filled in shapes (ex. [PaintingStyle.Fill]
+         *
+         * @param intervals Array of "on" and "off" distances for the dashed line segments
+         * @param phase Pixel offset into the intervals array
+         */
+        fun dashPathEffect(intervals: FloatArray, phase: Float = 0f): PathEffect =
+            actualDashPathEffect(intervals, phase)
+
+        /**
+         * Create a PathEffect that applies the inner effect to the path, and then applies the outer
+         * effect to the result of the inner effect. (e.g. outer(inner(path)).
+         */
+        fun chainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect =
+            actualChainPathEffect(outer, inner)
+
+        /**
+         * Dash the drawn path by stamping it with the specified shape represented as a [Path].
+         * This is only applied to stroke shapes and will be ignored with filled shapes.
+         * The stroke width used with this [PathEffect] is ignored as well.
+         *
+         * @param shape Path to stamp along
+         * @param advance Spacing between each stamped shape
+         * @param phase Amount to offset before the first shape is stamped
+         * @param style How to transform the shape at each position as it is stamped
+         */
+        fun stampedPathEffect(
+            shape: Path,
+            advance: Float,
+            phase: Float,
+            style: StampedPathEffectStyle
+        ): PathEffect = actualStampedPathEffect(shape, advance, phase, style)
+    }
+}
+
+internal expect fun actualCornerPathEffect(radius: Float): PathEffect
+
+internal expect fun actualDashPathEffect(intervals: FloatArray, phase: Float): PathEffect
+
+internal expect fun actualChainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect
+
+internal expect fun actualStampedPathEffect(
+    shape: Path,
+    advance: Float,
+    phase: Float,
+    style: StampedPathEffectStyle
+): PathEffect
+
+/**
+ * Strategy for transforming each point of the shape along the drawn path
+ *
+ * @sample androidx.compose.ui.graphics.samples.StampedPathEffectSample
+ */
+enum class StampedPathEffectStyle {
+
+    /**
+     * Translate the path shape into the specified location aligning the top left of the path with
+     * the drawn geometry. This does not modify the path itself.
+     *
+     * For example, a circle drawn with a square path and [Translate] will draw the square path
+     * repeatedly with the top left corner of each stamped square along the curvature of the circle.
+     */
+    Translate,
+
+    /**
+     * Rotates the path shape its center along the curvature of the drawn geometry. This does not
+     * modify the path itself.
+     *
+     * For example, a circle drawn with a square path and [Rotate] will draw the square path
+     * repeatedly with the center of each stamped square along the curvature of the circle as well
+     * as each square being rotated along the circumference.
+     */
+    Rotate,
+
+    /**
+     * Modifies the points within the path such that they fit within the drawn geometry. This will
+     * turn straight lines into curves.
+     *
+     * For example, a circle drawn with a square path and [Morph] will modify the straight lines
+     * of the square paths to be curves such that each stamped square is rendered as an arc around
+     * the curvature of the circle.
+     */
+    Morph
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
index a73b4a4..ef1f005 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
@@ -27,10 +27,10 @@
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.Matrix
-import androidx.compose.ui.graphics.NativePathEffect
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.PaintingStyle
 import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathEffect
 import androidx.compose.ui.graphics.PointMode
 import androidx.compose.ui.graphics.StrokeCap
 import androidx.compose.ui.graphics.StrokeJoin
@@ -102,7 +102,7 @@
         end: Offset,
         strokeWidth: Float,
         cap: StrokeCap,
-        pathEffect: NativePathEffect?,
+        pathEffect: PathEffect?,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -131,7 +131,7 @@
         end: Offset,
         strokeWidth: Float,
         cap: StrokeCap,
-        pathEffect: NativePathEffect?,
+        pathEffect: PathEffect?,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -432,7 +432,7 @@
         color: Color,
         strokeWidth: Float,
         cap: StrokeCap,
-        pathEffect: NativePathEffect?,
+        pathEffect: PathEffect?,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -461,7 +461,7 @@
         brush: Brush,
         strokeWidth: Float,
         cap: StrokeCap,
-        pathEffect: NativePathEffect?,
+        pathEffect: PathEffect?,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -554,15 +554,11 @@
             is Stroke ->
                 obtainStrokePaint()
                     .apply {
-                        with(drawStyle) {
-                            if (strokeWidth != width) strokeWidth = width
-                            if (strokeCap != cap) strokeCap = cap
-                            if (strokeMiterLimit != miter) strokeMiterLimit = miter
-                            if (strokeJoin != join) strokeJoin = join
-
-                            // TODO b/154550525 add PathEffect to Paint if necessary
-                            nativePathEffect = pathEffect
-                        }
+                        if (strokeWidth != drawStyle.width) strokeWidth = drawStyle.width
+                        if (strokeCap != drawStyle.cap) strokeCap = drawStyle.cap
+                        if (strokeMiterLimit != drawStyle.miter) strokeMiterLimit = drawStyle.miter
+                        if (strokeJoin != drawStyle.join) strokeJoin = drawStyle.join
+                        if (pathEffect != drawStyle.pathEffect) pathEffect = drawStyle.pathEffect
                     }
         }
 
@@ -612,7 +608,7 @@
         miter: Float,
         cap: StrokeCap,
         join: StrokeJoin,
-        pathEffect: NativePathEffect?,
+        pathEffect: PathEffect?,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -629,7 +625,7 @@
             if (this.strokeMiterLimit != miter) this.strokeMiterLimit = miter
             if (this.strokeCap != cap) this.strokeCap = cap
             if (this.strokeJoin != join) this.strokeJoin = join
-            this.nativePathEffect = pathEffect
+            if (this.pathEffect != pathEffect) this.pathEffect = pathEffect
         }
 
     private fun configureStrokePaint(
@@ -638,7 +634,7 @@
         miter: Float,
         cap: StrokeCap,
         join: StrokeJoin,
-        pathEffect: NativePathEffect?,
+        pathEffect: PathEffect?,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -654,7 +650,7 @@
         if (this.strokeMiterLimit != miter) this.strokeMiterLimit = miter
         if (this.strokeCap != cap) this.strokeCap = cap
         if (this.strokeJoin != join) this.strokeJoin = join
-        this.nativePathEffect = pathEffect
+        if (this.pathEffect != pathEffect) this.pathEffect = pathEffect
     }
 
     /**
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
index 16f8b4f..26eeacd 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
@@ -26,9 +26,9 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.NativePathEffect
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathEffect
 import androidx.compose.ui.graphics.PointMode
 import androidx.compose.ui.graphics.StrokeCap
 import androidx.compose.ui.graphics.StrokeJoin
@@ -327,7 +327,7 @@
         end: Offset,
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = Stroke.DefaultCap,
-        pathEffect: NativePathEffect? = null,
+        pathEffect: PathEffect? = null,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -354,7 +354,7 @@
         end: Offset,
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = Stroke.DefaultCap,
-        pathEffect: NativePathEffect? = null,
+        pathEffect: PathEffect? = null,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -745,7 +745,7 @@
         color: Color,
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = StrokeCap.Butt,
-        pathEffect: NativePathEffect? = null,
+        pathEffect: PathEffect? = null,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -773,7 +773,7 @@
         brush: Brush,
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = StrokeCap.Butt,
-        pathEffect: NativePathEffect? = null,
+        pathEffect: PathEffect? = null,
         @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -837,7 +837,7 @@
     /**
      * Effect to apply to the stroke, null indicates a solid stroke line is to be drawn
      */
-    val pathEffect: NativePathEffect? = null
+    val pathEffect: PathEffect? = null
 ) : DrawStyle() {
     companion object {
 
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.kt
index a22418d..38cc267 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.kt
@@ -114,9 +114,19 @@
             field = value
         }
 
-    override var nativePathEffect: NativePathEffect? = null
+    override var nativePathEffect: NativePathEffect?
+        get() = pathEffect?.asDesktopPathEffect()
         set(value) {
-            skija.pathEffect = value
+            pathEffect = if (value == null) {
+                null
+            } else {
+                DesktopPathEffect(value)
+            }
+        }
+
+    override var pathEffect: PathEffect? = null
+        set(value) {
+            skija.pathEffect = (value as DesktopPathEffect).asDesktopPathEffect()
             field = value
         }
 
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathEffect.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathEffect.kt
new file mode 100644
index 0000000..a443873
--- /dev/null
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathEffect.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.graphics
+
+import org.jetbrains.skija.PathEffect as SkijaPathEffect
+
+internal class DesktopPathEffect(val nativePathEffect: SkijaPathEffect) : PathEffect
+
+/**
+ * Obtain a reference to the desktop PathEffect type
+ */
+fun PathEffect.asDesktopPathEffect(): SkijaPathEffect =
+    (this as DesktopPathEffect).nativePathEffect
+
+internal actual fun actualCornerPathEffect(radius: Float): PathEffect =
+    DesktopPathEffect(SkijaPathEffect.makeCorner(radius))
+
+internal actual fun actualDashPathEffect(
+    intervals: FloatArray,
+    phase: Float
+): PathEffect = DesktopPathEffect(SkijaPathEffect.makeDash(intervals, phase))
+
+internal actual fun actualChainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect =
+    DesktopPathEffect(outer.asDesktopPathEffect().makeCompose(inner.asDesktopPathEffect()))
+
+internal actual fun actualStampedPathEffect(
+    shape: Path,
+    advance: Float,
+    phase: Float,
+    style: StampedPathEffectStyle
+): PathEffect =
+    DesktopPathEffect(
+        SkijaPathEffect.makePath1D(
+            shape.asDesktopPath(),
+            advance,
+            phase,
+            style.toSkijaStampedPathEffectStyle()
+        )
+    )
+
+internal fun StampedPathEffectStyle.toSkijaStampedPathEffectStyle(): SkijaPathEffect.Style =
+    when (this) {
+        StampedPathEffectStyle.Morph -> SkijaPathEffect.Style.MORPH
+        StampedPathEffectStyle.Rotate -> SkijaPathEffect.Style.ROTATE
+        StampedPathEffectStyle.Translate -> SkijaPathEffect.Style.TRANSLATE
+    }
\ No newline at end of file
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 a220afa..8f63900 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
@@ -30,6 +30,7 @@
 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.PsiClass
 import com.intellij.psi.PsiType
 import com.intellij.psi.util.InheritanceUtil
 import org.jetbrains.kotlin.psi.KtCallableDeclaration
@@ -41,6 +42,8 @@
 import org.jetbrains.kotlin.psi.KtUserType
 import org.jetbrains.kotlin.psi.psiUtil.containingClass
 import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.tryResolve
 import java.util.EnumSet
 
 /**
@@ -207,15 +210,26 @@
                 .build()
         )
     } else {
-        val receiverType = (receiverTypeReference.typeElement as KtUserType).referencedName
-        // A receiver that isn't Modifier
-        if (receiverType != ModifierShortName) {
+        val receiverType = (receiverTypeReference.typeElement as KtUserType)
+        val receiverShortName = receiverType.referencedName
+        // Try to resolve the class definition of the receiver
+        val receiverFqn = (
+            receiverType.referenceExpression.toUElement()?.tryResolve().toUElement() as? PsiClass
+            )?.qualifiedName
+        val hasModifierReceiver = if (receiverFqn != null) {
+            // If we could resolve the class, match fqn
+            receiverFqn == ModifierFqn
+        } else {
+            // Otherwise just try and match the short names
+            receiverShortName == ModifierShortName
+        }
+        if (!hasModifierReceiver) {
             report(
                 LintFix.create()
                     .replace()
                     .name("Change receiver to Modifier")
                     .range(context.getLocation(source))
-                    .text(receiverType)
+                    .text(receiverShortName)
                     .with(ModifierShortName)
                     .autoFix()
                     .build()
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
index 06a57be..89e8f8f 100644
--- 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
@@ -26,7 +26,6 @@
 val UMethod.isComposable get() = annotations.any { it.qualifiedName == ComposableFqn }
 
 const val ComposableFqn = "androidx.compose.runtime.Composable"
-val ComposableShortName = ComposableFqn.split(".").last()
 
 const val ModifierFqn = "androidx.compose.ui.Modifier"
 val ModifierShortName = ModifierFqn.split(".").last()
\ No newline at end of file
diff --git a/compose/ui/ui-test-font/build.gradle b/compose/ui/ui-test-font/build.gradle
index 16d1675..995fd29 100644
--- a/compose/ui/ui-test-font/build.gradle
+++ b/compose/ui/ui-test-font/build.gradle
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+
+import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
 import androidx.build.LibraryVersions
 import androidx.build.Publish
@@ -25,6 +27,19 @@
     id("AndroidXUiPlugin")
 }
 
+AndroidXUiPlugin.applyAndConfigureKotlinPlugin(project)
+
+if(AndroidXUiPlugin.isMultiplatformEnabled(project)) {
+    kotlin {
+        android()
+        jvm("desktop")
+
+        sourceSets {
+            desktopMain.dependsOn jvmMain
+        }
+    }
+}
+
 androidx {
     name = "Compose Test Font resources"
     publish = Publish.NONE
diff --git a/compose/ui/ui-test-font/src/main/AndroidManifest.xml b/compose/ui/ui-test-font/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from compose/ui/ui-test-font/src/main/AndroidManifest.xml
rename to compose/ui/ui-test-font/src/androidMain/AndroidManifest.xml
diff --git a/compose/ui/ui-test-font/src/main/res/font/invalid_font.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/invalid_font.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/invalid_font.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/invalid_font.ttf
diff --git a/compose/ui/ui-test-font/src/main/res/font/kern_font.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/kern_font.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/kern_font.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/kern_font.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/sample_font.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/sample_font.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/sample_font.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/sample_font.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/sample_font2.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/sample_font2.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/sample_font2.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/sample_font2.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_100_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_100_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_100_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_100_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_100_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_100_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_100_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_100_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_200_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_200_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_200_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_200_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_200_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_200_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_200_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_200_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_300_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_300_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_300_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_300_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_300_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_300_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_300_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_300_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_400_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_400_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_400_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_400_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_400_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_400_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_400_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_400_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_500_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_500_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_500_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_500_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_500_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_500_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_500_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_500_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_600_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_600_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_600_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_600_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_600_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_600_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_600_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_600_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_700_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_700_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_700_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_700_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_700_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_700_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_700_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_700_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_800_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_800_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_800_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_800_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_800_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_800_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_800_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_800_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_900_italic.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_900_italic.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_900_italic.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_900_italic.ttf
Binary files differ
diff --git a/compose/ui/ui-test-font/src/main/res/font/test_900_regular.ttf b/compose/ui/ui-test-font/src/commonMain/resources/font/test_900_regular.ttf
similarity index 100%
rename from compose/ui/ui-test-font/src/main/res/font/test_900_regular.ttf
rename to compose/ui/ui-test-font/src/commonMain/resources/font/test_900_regular.ttf
Binary files differ
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index 3c79cab..da98a1d 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -123,7 +123,6 @@
 android {
     tasks.withType(KotlinCompile).configureEach {
         kotlinOptions {
-            freeCompilerArgs += ["-XXLanguage:-NewInference"]
             useIR = true
         }
     }
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/AndroidOwnerRegistryTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/AndroidOwnerRegistryTest.kt
index 2c038c3..f07b0a2 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/AndroidOwnerRegistryTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/AndroidOwnerRegistryTest.kt
@@ -20,7 +20,7 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.activity.ComponentActivity
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.test.junit4.android.AndroidOwnerRegistry
 import androidx.test.ext.junit.rules.ActivityScenarioRule
@@ -44,8 +44,8 @@
 
     private val onRegistrationChangedListener =
         object : AndroidOwnerRegistry.OnRegistrationChangedListener {
-            val recordedChanges = mutableListOf<Pair<AndroidOwner, Boolean>>()
-            override fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean) {
+            val recordedChanges = mutableListOf<Pair<ViewRootForTest, Boolean>>()
+            override fun onRegistrationChanged(owner: ViewRootForTest, registered: Boolean) {
                 recordedChanges.add(Pair(owner, registered))
             }
         }
@@ -67,7 +67,7 @@
         activityRule.scenario.onActivity { activity ->
             // set the composable content and find an owner
             activity.setContent { }
-            val owner = activity.findOwner()
+            val owner = activity.findRootForTest()
 
             // Then it is registered
             assertThat(androidOwnerRegistry.getUnfilteredOwners()).isEqualTo(setOf(owner))
@@ -84,7 +84,7 @@
         activityRule.scenario.onActivity { activity ->
             // set the composable content and find an owner
             activity.setContent { }
-            val owner = activity.findOwner()
+            val owner = activity.findRootForTest()
 
             // And remove it from the hierarchy
             activity.setContentView(View(activity))
@@ -107,7 +107,7 @@
         activityRule.scenario.onActivity { activity ->
             // set the composable content and find an owner
             activity.setContent { }
-            val owner = activity.findOwner()
+            val owner = activity.findRootForTest()
 
             // When we tear down the registry
             androidOwnerRegistry.tearDownRegistry()
@@ -126,16 +126,16 @@
     }
 }
 
-private fun Activity.findOwner(): AndroidOwner {
+private fun Activity.findRootForTest(): ViewRootForTest {
     val viewGroup = findViewById<ViewGroup>(android.R.id.content)
-    return requireNotNull(viewGroup.findOwner())
+    return requireNotNull(viewGroup.findRootForTest())
 }
 
-private fun View.findOwner(): AndroidOwner? {
-    if (this is AndroidOwner) return this
+private fun View.findRootForTest(): ViewRootForTest? {
+    if (this is ViewRootForTest) return this
     if (this is ViewGroup) {
         for (i in 0 until childCount) {
-            val owner = getChildAt(i).findOwner()
+            val owner = getChildAt(i).findRootForTest()
             if (owner != null) {
                 return owner
             }
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt
index 2e199ea..1ac7476 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt
@@ -18,10 +18,8 @@
 
 import androidx.activity.ComponentActivity
 import androidx.compose.testutils.expectError
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.test.onNodeWithTag
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -51,7 +49,7 @@
 
     @Before
     fun addMockResumedOwner() {
-        androidOwnerRegistry.registerOwner(mockResumedAndroidOwner())
+        androidOwnerRegistry.registerOwner(mockResumedRootForTest())
     }
 
     @Test
@@ -119,21 +117,9 @@
         }
     }
 
-    private fun mockResumedAndroidOwner(): AndroidOwner {
-        val lifecycle = mock<Lifecycle>()
-        doReturn(Lifecycle.State.RESUMED).whenever(lifecycle).currentState
-
-        val lifecycleOwner = mock<LifecycleOwner>()
-        doReturn(lifecycle).whenever(lifecycleOwner).lifecycle
-
-        val viewTreeOwners = AndroidOwner.ViewTreeOwners(
-            lifecycleOwner = lifecycleOwner,
-            viewModelStoreOwner = mock(),
-            savedStateRegistryOwner = mock()
-        )
-        val owner = mock<AndroidOwner>()
-        doReturn(viewTreeOwners).whenever(owner).viewTreeOwners
-
+    private fun mockResumedRootForTest(): ViewRootForTest {
+        val owner = mock<ViewRootForTest>()
+        doReturn(true).whenever(owner).isLifecycleInResumedState
         return owner
     }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidTestOwner.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidTestOwner.kt
index 404b507..ded27d1 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidTestOwner.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidTestOwner.kt
@@ -17,9 +17,8 @@
 package androidx.compose.ui.test.junit4
 
 import android.annotation.SuppressLint
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.Owner
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.test.InternalTestingApi
 import androidx.compose.ui.test.TestOwner
@@ -34,8 +33,7 @@
 
     @SuppressLint("DocumentExceptions")
     override fun sendTextInputCommand(node: SemanticsNode, command: List<EditOperation>) {
-        @OptIn(ExperimentalLayoutNodeApi::class)
-        val owner = node.componentNode.owner as AndroidOwner
+        val owner = node.owner as ViewRootForTest
 
         @Suppress("DEPRECATION")
         runOnUiThread {
@@ -48,8 +46,7 @@
 
     @SuppressLint("DocumentExceptions")
     override fun sendImeAction(node: SemanticsNode, actionSpecified: ImeAction) {
-        @OptIn(ExperimentalLayoutNodeApi::class)
-        val owner = node.componentNode.owner as AndroidOwner
+        val owner = node.owner as ViewRootForTest
 
         @Suppress("DEPRECATION")
         runOnUiThread {
@@ -69,7 +66,7 @@
         return composeIdlingResource.getOwners()
     }
 
-    private fun AndroidOwner.getTextInputServiceOrDie(): TextInputServiceForTests {
+    private fun ViewRootForTest.getTextInputServiceOrDie(): TextInputServiceForTests {
         return textInputService as? TextInputServiceForTests
             ?: throw IllegalStateException(
                 "Text input service wrapper not set up! Did you use ComposeTestRule?"
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/AndroidOwnerRegistry.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/AndroidOwnerRegistry.kt
index 0138a73..0de743a 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/AndroidOwnerRegistry.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/AndroidOwnerRegistry.kt
@@ -18,9 +18,8 @@
 
 import android.view.View
 import androidx.annotation.VisibleForTesting
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.test.ExperimentalTesting
-import androidx.lifecycle.Lifecycle
 import kotlinx.coroutines.suspendCancellableCoroutine
 import org.junit.runners.model.Statement
 import java.util.Collections
@@ -32,24 +31,25 @@
 import kotlin.time.ExperimentalTime
 
 /**
- * Registry where all [AndroidOwner]s should be registered while they are attached to the window.
- * This registry is used by the testing library to query the owners's state.
+ * Registry where all views implementing [ViewRootForTest] should be registered while they
+ * are attached to the window. This registry is used by the testing library to query the owners's
+ * state.
  */
 internal class AndroidOwnerRegistry {
-    private val owners = Collections.newSetFromMap(WeakHashMap<AndroidOwner, Boolean>())
+    private val owners = Collections.newSetFromMap(WeakHashMap<ViewRootForTest, Boolean>())
     private val registryListeners = mutableSetOf<OnRegistrationChangedListener>()
 
     /**
-     * Returns if the registry is setup to receive registrations from [AndroidOwner]s
+     * Returns if the registry is setup to receive registrations from [ViewRootForTest]s
      */
     val isSetUp: Boolean
-        get() = AndroidOwner.onAndroidOwnerCreatedCallback == ::onAndroidOwnerCreated
+        get() = ViewRootForTest.onViewCreatedCallback == ::onComposeViewCreated
 
     /**
-     * Sets up this registry to be notified of any [AndroidOwner] created
+     * Sets up this registry to be notified of any [ViewRootForTest] created
      */
     private fun setupRegistry() {
-        AndroidOwner.onAndroidOwnerCreatedCallback = ::onAndroidOwnerCreated
+        ViewRootForTest.onViewCreatedCallback = ::onComposeViewCreated
     }
 
     /**
@@ -57,7 +57,7 @@
      */
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     internal fun tearDownRegistry() {
-        AndroidOwner.onAndroidOwnerCreatedCallback = null
+        ViewRootForTest.onViewCreatedCallback = null
         synchronized(owners) {
             getUnfilteredOwners().forEach {
                 unregisterOwner(it)
@@ -67,33 +67,32 @@
         }
     }
 
-    private fun onAndroidOwnerCreated(owner: AndroidOwner) {
+    private fun onComposeViewCreated(owner: ViewRootForTest) {
         owner.view.addOnAttachStateChangeListener(OwnerAttachedListener(owner))
     }
 
     /**
-     * Returns a copy of the set of all registered [AndroidOwner]s, including ones that are
+     * Returns a copy of the set of all registered [ViewRootForTest]s, including ones that are
      * normally not relevant (like those whose lifecycle state is not RESUMED).
      */
-    fun getUnfilteredOwners(): Set<AndroidOwner> {
+    fun getUnfilteredOwners(): Set<ViewRootForTest> {
         return synchronized(owners) { owners.toSet() }
     }
 
     /**
-     * Returns a copy of the set of all registered [AndroidOwner]s that can be interacted with.
+     * Returns a copy of the set of all registered [ViewRootForTest]s that can be interacted with.
      * This method is almost always preferred over [getUnfilteredOwners].
      */
-    fun getOwners(): Set<AndroidOwner> {
+    fun getOwners(): Set<ViewRootForTest> {
         return synchronized(owners) {
             owners.filterTo(mutableSetOf()) {
-                it.viewTreeOwners?.lifecycleOwner
-                    ?.lifecycle?.currentState == Lifecycle.State.RESUMED
+                it.isLifecycleInResumedState
             }
         }
     }
 
     /**
-     * Adds the given [listener], to be notified when an [AndroidOwner] registers or unregisters.
+     * Adds the given [listener], to be notified when an [ViewRootForTest] registers or unregisters.
      */
     fun addOnRegistrationChangedListener(listener: OnRegistrationChangedListener) {
         synchronized(registryListeners) { registryListeners.add(listener) }
@@ -106,7 +105,7 @@
         synchronized(registryListeners) { registryListeners.remove(listener) }
     }
 
-    private fun dispatchOnRegistrationChanged(owner: AndroidOwner, isRegistered: Boolean) {
+    private fun dispatchOnRegistrationChanged(owner: ViewRootForTest, isRegistered: Boolean) {
         synchronized(registryListeners) { registryListeners.toList() }.forEach {
             it.onRegistrationChanged(owner, isRegistered)
         }
@@ -115,7 +114,7 @@
     /**
      * Registers the [owner] in this registry. Must be called from [View.onAttachedToWindow].
      */
-    internal fun registerOwner(owner: AndroidOwner) {
+    internal fun registerOwner(owner: ViewRootForTest) {
         synchronized(owners) {
             if (owners.add(owner)) {
                 dispatchOnRegistrationChanged(owner, true)
@@ -126,7 +125,7 @@
     /**
      * Unregisters the [owner] from this registry. Must be called from [View.onDetachedFromWindow].
      */
-    internal fun unregisterOwner(owner: AndroidOwner) {
+    internal fun unregisterOwner(owner: ViewRootForTest) {
         synchronized(owners) {
             if (owners.remove(owner)) {
                 dispatchOnRegistrationChanged(owner, false)
@@ -148,15 +147,15 @@
     }
 
     /**
-     * Interface to be implemented by components that want to be notified when an [AndroidOwner]
+     * Interface to be implemented by components that want to be notified when an [ViewRootForTest]
      * registers or unregisters at this registry.
      */
     interface OnRegistrationChangedListener {
-        fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean)
+        fun onRegistrationChanged(owner: ViewRootForTest, registered: Boolean)
     }
 
     private inner class OwnerAttachedListener(
-        private val owner: AndroidOwner
+        private val owner: ViewRootForTest
     ) : View.OnAttachStateChangeListener {
 
         // Note: owner.view === view, because the owner _is_ the view,
@@ -187,7 +186,7 @@
     if (!hasAndroidOwners) {
         val latch = CountDownLatch(1)
         val listener = object : AndroidOwnerRegistry.OnRegistrationChangedListener {
-            override fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean) {
+            override fun onRegistrationChanged(owner: ViewRootForTest, registered: Boolean) {
                 if (hasAndroidOwners) {
                     latch.countDown()
                 }
@@ -220,9 +219,12 @@
                 }
             }
 
-            // Usually we resume if an AndroidOwner is registered while the listener is added
+            // Usually we resume if an ComposeViewTestMarker is registered while the listener is added
             val listener = object : AndroidOwnerRegistry.OnRegistrationChangedListener {
-                override fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean) {
+                override fun onRegistrationChanged(
+                    owner: ViewRootForTest,
+                    registered: Boolean
+                ) {
                     if (hasAndroidOwners) {
                         resume(this)
                     }
@@ -234,7 +236,7 @@
                 removeOnRegistrationChangedListener(listener)
             }
 
-            // Sometimes the AndroidOwner was registered before we added
+            // Sometimes the ComposeViewTestMarker was registered before we added
             // the listener, in which case we missed our signal
             if (hasAndroidOwners) {
                 resume(listener)
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 7a19f17..3e55c2a 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -128,7 +128,6 @@
 android {
     tasks.withType(KotlinCompile).configureEach {
         kotlinOptions {
-            freeCompilerArgs += ["-XXLanguage:-NewInference"]
             useIR = true
         }
     }
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidAssertions.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidAssertions.kt
index 0a59655..47c42c1 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidAssertions.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidAssertions.kt
@@ -19,28 +19,25 @@
 import androidx.test.espresso.matcher.ViewMatchers
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.node.findClosestParentNode
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.layout.LayoutInfo
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.semantics.SemanticsNode
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal actual fun SemanticsNodeInteraction.checkIsDisplayed(): Boolean {
     // hierarchy check - check layout nodes are visible
     val errorMessageOnFail = "Failed to perform isDisplayed check."
     val node = fetchSemanticsNode(errorMessageOnFail)
 
-    fun isNotPlaced(node: LayoutNode): Boolean {
+    fun isNotPlaced(node: LayoutInfo): Boolean {
         return !node.isPlaced
     }
 
-    val layoutNode = node.componentNode
-    if (isNotPlaced(layoutNode) || layoutNode.findClosestParentNode(::isNotPlaced) != null) {
+    val layoutInfo = node.layoutInfo
+    if (isNotPlaced(layoutInfo) || layoutInfo.findClosestParentNode(::isNotPlaced) != null) {
         return false
     }
 
-    (layoutNode.owner as? AndroidOwner)?.let {
+    (node.owner as? ViewRootForTest)?.let {
         if (!ViewMatchers.isDisplayed().matches(it.view)) {
             return false
         }
@@ -55,9 +52,8 @@
     return (globalRect.width > 0f && globalRect.height > 0f)
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal actual fun SemanticsNode.clippedNodeBoundsInWindow(): Rect {
-    val composeView = (componentNode.owner as AndroidOwner).view
+    val composeView = (owner as ViewRootForTest).view
     val rootLocationInWindow = intArrayOf(0, 0).let {
         composeView.getLocationInWindow(it)
         Offset(it[0].toFloat(), it[1].toFloat())
@@ -65,9 +61,8 @@
     return boundsInRoot.translate(rootLocationInWindow)
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal actual fun SemanticsNode.isInScreenBounds(): Boolean {
-    val composeView = (componentNode.owner as AndroidOwner).view
+    val composeView = (owner as ViewRootForTest).view
 
     // Window relative bounds of our node
     val nodeBoundsInWindow = clippedNodeBoundsInWindow()
@@ -85,4 +80,24 @@
         nodeBoundsInWindow.left >= globalRootRect.left &&
         nodeBoundsInWindow.right <= globalRootRect.right &&
         nodeBoundsInWindow.bottom <= globalRootRect.bottom
-}
\ No newline at end of file
+}
+
+/**
+ * Executes [selector] on every parent of this [LayoutInfo] and returns the closest
+ * [LayoutInfo] to return `true` from [selector] or null if [selector] returns false
+ * for all ancestors.
+ */
+private fun LayoutInfo.findClosestParentNode(
+    selector: (LayoutInfo) -> Boolean
+): LayoutInfo? {
+    var currentParent = this.parentInfo
+    while (currentParent != null) {
+        if (selector(currentParent)) {
+            return currentParent
+        } else {
+            currentParent = currentParent.parentInfo
+        }
+    }
+
+    return null
+}
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidImageHelpers.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidImageHelpers.kt
index 2e02517..e9de46c 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidImageHelpers.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidImageHelpers.kt
@@ -21,8 +21,7 @@
 import android.view.Window
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.semantics.SemanticsProperties
 import androidx.compose.ui.test.android.captureRegionToImage
 import androidx.compose.ui.window.DialogWindowProvider
@@ -54,8 +53,7 @@
         )
     }
 
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    val view = (node.componentNode.owner as AndroidOwner).view
+    val view = (node.owner as ViewRootForTest).view
 
     // If we are in dialog use its window to capture the bitmap
     val dialogParentNodeMaybe = node.findClosestParentNode(includeSelf = true) {
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt
index f94df4a..6dfc89b 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.kt
@@ -28,15 +28,15 @@
 import androidx.compose.runtime.dispatch.AndroidUiDispatcher
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.node.Owner
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import kotlin.math.max
 
 internal actual fun createInputDispatcher(testContext: TestContext, owner: Owner): InputDispatcher {
-    require(owner is AndroidOwner) {
-        "InputDispatcher currently only supports dispatching to AndroidOwner, not to " +
+    require(owner is ViewRootForTest) {
+        "InputDispatcher currently only supports dispatching to ViewRootForTest, not to " +
             owner::class.java.simpleName
     }
     val view = owner.view
@@ -45,7 +45,7 @@
 
 internal class AndroidInputDispatcher(
     testContext: TestContext,
-    owner: AndroidOwner?,
+    owner: Owner?,
     private val sendEvent: (MotionEvent) -> Unit
 ) : InputDispatcher(testContext, owner) {
 
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
index b4ae8f5..c680bb8 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
@@ -20,7 +20,6 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.boundsInParent
 import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.semantics.AccessibilityAction
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsPropertyKey
@@ -65,10 +64,8 @@
     // Figure out the (clipped) bounds of the viewPort in its direct parent's content area, in
     // root coordinates. We only want the clipping from the direct parent on the scrollable, not
     // from any other ancestors.
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    val viewPortInParent = scrollableNode.componentNode.coordinates.boundsInParent
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    val parentInRoot = scrollableNode.componentNode.coordinates.parentCoordinates
+    val viewPortInParent = scrollableNode.layoutInfo.coordinates.boundsInParent
+    val parentInRoot = scrollableNode.layoutInfo.coordinates.parentCoordinates
         ?.positionInRoot ?: Offset.Zero
 
     val viewPort = viewPortInParent.translate(parentInRoot)
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
index e3ab0ad..c745d64 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
@@ -21,7 +21,6 @@
 import androidx.compose.ui.gesture.DoubleTapTimeout
 import androidx.compose.ui.gesture.LongPressTimeout
 import androidx.compose.ui.layout.globalBounds
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.unit.Duration
 import androidx.compose.ui.unit.IntSize
@@ -107,8 +106,7 @@
         }
 
     // Convenience property
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    private val owner get() = semanticsNode.componentNode.owner
+    private val owner get() = semanticsNode.owner
 
     // TODO(b/133217292): Better error: explain which gesture couldn't be performed
     private var _inputDispatcher: InputDispatcher? =
@@ -287,8 +285,7 @@
  * @param position A position in local coordinates
  */
 private fun GestureScope.localToGlobal(position: Offset): Offset {
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    return position + semanticsNode.componentNode.coordinates.globalBounds.topLeft
+    return position + semanticsNode.layoutInfo.coordinates.globalBounds.topLeft
 }
 
 /**
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInputHelpers.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInputHelpers.kt
index a3a63f0..8b6ae97 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInputHelpers.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/KeyInputHelpers.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.ui.input.key.ExperimentalKeyInput
 import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 
 /**
  * Send the specified [KeyEvent] to the focused component.
@@ -28,8 +27,7 @@
 @OptIn(ExperimentalKeyInput::class)
 fun SemanticsNodeInteraction.performKeyPress(keyEvent: KeyEvent): Boolean {
     val semanticsNode = fetchSemanticsNode("Failed to send key Event (${keyEvent.key})")
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    val owner = semanticsNode.componentNode.owner
+    val owner = semanticsNode.owner
     requireNotNull(owner) { "Failed to find owner" }
     @OptIn(InternalTestingApi::class)
     return testContext.testOwner.runOnUiThread { owner.sendKeyEvent(keyEvent) }
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 eb42c37..dc16fb4 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
@@ -244,4 +244,10 @@
         append(indent)
         append("MergeDescendants = 'true'")
     }
+
+    if (config.isClearingSemantics) {
+        appendLine()
+        append(indent)
+        append("ReplaceSemantics = 'true'")
+    }
 }
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt
index edbacd7..74a2c6d 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/SemanticsNodeInteraction.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.test
 
 import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.unit.Density
 
@@ -249,8 +248,7 @@
     operation: Density.(SemanticsNode) -> R
 ): R {
     val node = fetchSemanticsNode("Failed to retrieve density for the node.")
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    val density = node.componentNode.owner!!.density
+    val density = node.owner!!.density
     return operation.invoke(density, node)
 }
 
@@ -258,8 +256,7 @@
     assertion: Density.(Rect) -> Unit
 ): SemanticsNodeInteraction {
     val node = fetchSemanticsNode("Failed to retrieve bounds of the node.")
-    @OptIn(ExperimentalLayoutNodeApi::class)
-    val density = node.componentNode.owner!!.density
+    val density = node.owner!!.density
 
     assertion.invoke(density, node.unclippedBoundsInRoot)
     return this
diff --git a/compose/ui/ui-tooling/api/current.txt b/compose/ui/ui-tooling/api/current.txt
index 23de9c4..ca0c914 100644
--- a/compose/ui/ui-tooling/api/current.txt
+++ b/compose/ui/ui-tooling/api/current.txt
@@ -12,7 +12,7 @@
     method public final java.util.Collection<java.lang.Object> getData();
     method public final Object? getKey();
     method public final androidx.compose.ui.tooling.SourceLocation? getLocation();
-    method public java.util.List<androidx.compose.ui.node.ModifierInfo> getModifierInfo();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
     method public final String? getName();
     method public java.util.List<androidx.compose.ui.tooling.ParameterInformation> getParameters();
     property public final androidx.compose.ui.unit.IntBounds box;
@@ -20,7 +20,7 @@
     property public final java.util.Collection<java.lang.Object> data;
     property public final Object? key;
     property public final androidx.compose.ui.tooling.SourceLocation? location;
-    property public java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo;
+    property public java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo;
     property public final String? name;
     property public java.util.List<androidx.compose.ui.tooling.ParameterInformation> parameters;
   }
@@ -41,9 +41,9 @@
   }
 
   public final class NodeGroup extends androidx.compose.ui.tooling.Group {
-    ctor public NodeGroup(Object? key, Object node, androidx.compose.ui.unit.IntBounds box, java.util.Collection<?> data, java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo, java.util.Collection<? extends androidx.compose.ui.tooling.Group> children);
+    ctor public NodeGroup(Object? key, Object node, androidx.compose.ui.unit.IntBounds box, java.util.Collection<?> data, java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo, java.util.Collection<? extends androidx.compose.ui.tooling.Group> children);
     method public Object getNode();
-    property public java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo;
+    property public java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo;
     property public final Object node;
   }
 
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
index 23de9c4..ca0c914 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
@@ -12,7 +12,7 @@
     method public final java.util.Collection<java.lang.Object> getData();
     method public final Object? getKey();
     method public final androidx.compose.ui.tooling.SourceLocation? getLocation();
-    method public java.util.List<androidx.compose.ui.node.ModifierInfo> getModifierInfo();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
     method public final String? getName();
     method public java.util.List<androidx.compose.ui.tooling.ParameterInformation> getParameters();
     property public final androidx.compose.ui.unit.IntBounds box;
@@ -20,7 +20,7 @@
     property public final java.util.Collection<java.lang.Object> data;
     property public final Object? key;
     property public final androidx.compose.ui.tooling.SourceLocation? location;
-    property public java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo;
+    property public java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo;
     property public final String? name;
     property public java.util.List<androidx.compose.ui.tooling.ParameterInformation> parameters;
   }
@@ -41,9 +41,9 @@
   }
 
   public final class NodeGroup extends androidx.compose.ui.tooling.Group {
-    ctor public NodeGroup(Object? key, Object node, androidx.compose.ui.unit.IntBounds box, java.util.Collection<?> data, java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo, java.util.Collection<? extends androidx.compose.ui.tooling.Group> children);
+    ctor public NodeGroup(Object? key, Object node, androidx.compose.ui.unit.IntBounds box, java.util.Collection<?> data, java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo, java.util.Collection<? extends androidx.compose.ui.tooling.Group> children);
     method public Object getNode();
-    property public java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo;
+    property public java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo;
     property public final Object node;
   }
 
diff --git a/compose/ui/ui-tooling/api/restricted_current.txt b/compose/ui/ui-tooling/api/restricted_current.txt
index 23de9c4..ca0c914 100644
--- a/compose/ui/ui-tooling/api/restricted_current.txt
+++ b/compose/ui/ui-tooling/api/restricted_current.txt
@@ -12,7 +12,7 @@
     method public final java.util.Collection<java.lang.Object> getData();
     method public final Object? getKey();
     method public final androidx.compose.ui.tooling.SourceLocation? getLocation();
-    method public java.util.List<androidx.compose.ui.node.ModifierInfo> getModifierInfo();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
     method public final String? getName();
     method public java.util.List<androidx.compose.ui.tooling.ParameterInformation> getParameters();
     property public final androidx.compose.ui.unit.IntBounds box;
@@ -20,7 +20,7 @@
     property public final java.util.Collection<java.lang.Object> data;
     property public final Object? key;
     property public final androidx.compose.ui.tooling.SourceLocation? location;
-    property public java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo;
+    property public java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo;
     property public final String? name;
     property public java.util.List<androidx.compose.ui.tooling.ParameterInformation> parameters;
   }
@@ -41,9 +41,9 @@
   }
 
   public final class NodeGroup extends androidx.compose.ui.tooling.Group {
-    ctor public NodeGroup(Object? key, Object node, androidx.compose.ui.unit.IntBounds box, java.util.Collection<?> data, java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo, java.util.Collection<? extends androidx.compose.ui.tooling.Group> children);
+    ctor public NodeGroup(Object? key, Object node, androidx.compose.ui.unit.IntBounds box, java.util.Collection<?> data, java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo, java.util.Collection<? extends androidx.compose.ui.tooling.Group> children);
     method public Object getNode();
-    property public java.util.List<androidx.compose.ui.node.ModifierInfo> modifierInfo;
+    property public java.util.List<androidx.compose.ui.layout.ModifierInfo> modifierInfo;
     property public final Object node;
   }
 
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/ToolingTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/ToolingTest.kt
index bf3b298..d3d094f 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/ToolingTest.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/ToolingTest.kt
@@ -26,7 +26,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.R
 import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.platform.setContent
 import org.junit.Before
 import org.junit.Rule
@@ -81,9 +81,9 @@
             WeakHashMap<SlotTable, Boolean>()
         )
         activityTestRule.onUiThread {
-            AndroidOwner.onAndroidOwnerCreatedCallback = {
+            ViewRootForTest.onViewCreatedCallback = {
                 it.view.setTag(R.id.inspection_slot_table_set, map)
-                AndroidOwner.onAndroidOwnerCreatedCallback = null
+                ViewRootForTest.onViewCreatedCallback = null
             }
             activity.setContent {
                 Box(
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
index 73d6452..4ecf4eb 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.material.Button
 import androidx.compose.material.ModalDrawerLayout
 import androidx.compose.material.Surface
@@ -33,6 +34,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.node.OwnedLayer
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.tooling.Group
 import androidx.compose.ui.tooling.Inspectable
 import androidx.compose.ui.tooling.R
@@ -461,6 +464,30 @@
         assertThat(node).isNotNull()
     }
 
+    @Test // regression test b/174855322
+    fun testBasicText() {
+        val slotTableRecord = SlotTableRecord.create()
+
+        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
+        show {
+            Column {
+                BasicText(
+                    text = "Some text",
+                    style = TextStyle(textDecoration = TextDecoration.Underline)
+                )
+            }
+        }
+
+        val builder = LayoutInspectorTree()
+        val node = builder.convert(view)
+            .flatMap { flatten(it) }
+            .firstOrNull { it.name == "BasicText" }
+
+        assertThat(node).isNotNull()
+
+        assertThat(node?.parameters).isNotEmpty()
+    }
+
     @SdkSuppress(minSdkVersion = 29) // Render id is not returned for api < 29:  b/171519437
     @Test
     @Ignore("b/174152464")
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
index 8f590a8..83a5e5d 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
@@ -49,7 +49,6 @@
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextStyle
@@ -81,7 +80,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@ExperimentalLayoutNodeApi
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 class ParameterFactoryTest {
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/Inspectable.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/Inspectable.kt
index d8145bc..1f1b9d2 100644
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/Inspectable.kt
+++ b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/Inspectable.kt
@@ -59,6 +59,7 @@
     content: @Composable () -> Unit
 ) {
     currentComposer.collectKeySourceInformation()
+    currentComposer.collectParameterInformation()
     val store = (slotTableRecord as SlotTableRecordImpl).store
     store.add(currentComposer.slotTable)
     Providers(
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/SlotTree.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/SlotTree.kt
index bb3361d..265bcfc 100644
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/SlotTree.kt
+++ b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/SlotTree.kt
@@ -23,9 +23,8 @@
 import androidx.compose.runtime.SlotTable
 import androidx.compose.runtime.keySourceInfoOf
 import androidx.compose.ui.layout.globalPosition
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.node.ModifierInfo
+import androidx.compose.ui.layout.LayoutInfo
+import androidx.compose.ui.layout.ModifierInfo
 import androidx.compose.ui.unit.IntBounds
 import java.lang.reflect.Field
 import kotlin.math.max
@@ -433,7 +432,7 @@
 /**
  * Iterate the slot table and extract a group tree that corresponds to the content of the table.
  */
-@OptIn(ExperimentalLayoutNodeApi::class, InternalComposeApi::class)
+@OptIn(InternalComposeApi::class)
 private fun SlotReader.getGroup(parentContext: SourceInformationContext?): Group {
     val key = convertKey(groupKey)
     val groupData = groupAux
@@ -456,7 +455,7 @@
         children.add(getGroup(context))
     }
 
-    val modifierInfo = if (node is LayoutNode) {
+    val modifierInfo = if (node is LayoutInfo) {
         node.getModifierInfo()
     } else {
         emptyList()
@@ -464,7 +463,7 @@
 
     // Calculate bounding box
     val box = when (node) {
-        is LayoutNode -> boundsOfLayoutNode(node)
+        is LayoutInfo -> boundsOfLayoutNode(node)
         else ->
             if (children.isEmpty()) emptyBox else
                 children.map { g -> g.box }.reduce { acc, box -> box.union(acc) }
@@ -492,9 +491,8 @@
         )
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
-private fun boundsOfLayoutNode(node: LayoutNode): IntBounds {
-    if (node.owner == null) {
+private fun boundsOfLayoutNode(node: LayoutInfo): IntBounds {
+    if (!node.isAttached) {
         return IntBounds(
             left = 0,
             top = 0,
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
index f42c9d5..c6add12 100644
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
+++ b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
@@ -16,8 +16,7 @@
 
 package androidx.compose.ui.tooling.inspector
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.layout.LayoutInfo
 
 /**
  * Node representing a Composable for the Layout Inspector.
@@ -105,10 +104,9 @@
 /**
  * Mutable version of [InspectorNode].
  */
-@ExperimentalLayoutNodeApi
 internal class MutableInspectorNode {
     var id = 0L
-    var layoutNodes = mutableListOf<LayoutNode>()
+    var layoutNodes = mutableListOf<LayoutInfo>()
     var name = ""
     var fileName = ""
     var packageHash = -1
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
index edad65e..b831653 100644
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
@@ -19,8 +19,7 @@
 import android.view.View
 import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.SlotTable
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.layout.LayoutInfo
 import androidx.compose.ui.node.OwnedLayer
 import androidx.compose.ui.tooling.Group
 import androidx.compose.ui.tooling.NodeGroup
@@ -58,14 +57,13 @@
 /**
  * Generator of a tree for the Layout Inspector.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 class LayoutInspectorTree {
     private val inlineClassConverter = InlineClassConverter()
     private val parameterFactory = ParameterFactory(inlineClassConverter)
     private val cache = ArrayDeque<MutableInspectorNode>()
     private var generatedId = -1L
-    /** Map from [LayoutNode] to the nearest [InspectorNode] that contains it */
-    private val claimedNodes = IdentityHashMap<LayoutNode, InspectorNode>()
+    /** Map from [LayoutInfo] to the nearest [InspectorNode] that contains it */
+    private val claimedNodes = IdentityHashMap<LayoutInfo, InspectorNode>()
     /** Map from parent tree to child trees that are about to be stitched together */
     private val treeMap = IdentityHashMap<MutableInspectorNode, MutableList<MutableInspectorNode>>()
     /** Map from owner node to child trees that are about to be stitched to this owner */
@@ -123,20 +121,20 @@
     }
 
     /**
-     * Stitch separate trees together using the [LayoutNode]s found in the [SlotTable]s.
+     * Stitch separate trees together using the [LayoutInfo]s found in the [SlotTable]s.
      *
      * Some constructs in Compose (e.g. ModalDrawerLayout) will result is multiple [SlotTable]s.
      * This code will attempt to stitch the resulting [InspectorNode] trees together by looking
-     * at the parent of each [LayoutNode].
+     * at the parent of each [LayoutInfo].
      * If this algorithm is successful the result of this function will be a list with a single
      * tree.
      */
     private fun stitchTreesByLayoutNode(trees: List<MutableInspectorNode>): List<InspectorNode> {
-        val layoutToTreeMap = IdentityHashMap<LayoutNode, MutableInspectorNode>()
+        val layoutToTreeMap = IdentityHashMap<LayoutInfo, MutableInspectorNode>()
         trees.forEach { tree -> tree.layoutNodes.forEach { layoutToTreeMap[it] = tree } }
         trees.forEach { tree ->
             val layout = tree.layoutNodes.lastOrNull()
-            val parentLayout = generateSequence(layout) { it.parent }.firstOrNull {
+            val parentLayout = generateSequence(layout) { it.parentInfo }.firstOrNull {
                 val otherTree = layoutToTreeMap[it]
                 otherTree != null && otherTree != tree
             }
@@ -264,7 +262,7 @@
     private fun parse(group: Group): MutableInspectorNode {
         val node = newNode()
         node.id = getRenderNode(group)
-        ((group as? NodeGroup)?.node as? LayoutNode)?.let { node.layoutNodes.add(it) }
+        ((group as? NodeGroup)?.node as? LayoutInfo)?.let { node.layoutNodes.add(it) }
         if (!parseCallLocation(group, node) && group.name.isNullOrEmpty()) {
             return markUnwanted(node)
         }
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
index 18f3d85..ddbd8f4 100644
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
+++ b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.font.FontListFontFamily
@@ -70,7 +69,6 @@
  *
  * Each parameter value is converted to a user readable value.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class ParameterFactory(private val inlineClassConverter: InlineClassConverter) {
     /**
      * A map from known values to a user readable string representation.
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt
index 7e6d2b6..e25b4ae 100644
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt
+++ b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt
@@ -40,9 +40,9 @@
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.AmbientAnimationClock
 import androidx.compose.ui.platform.AmbientFontLoader
-import androidx.compose.ui.platform.AndroidOwner
 import androidx.compose.ui.platform.AnimationClockAmbient
 import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.tooling.Group
 import androidx.compose.ui.tooling.Inspectable
 import androidx.compose.ui.tooling.SlotTableRecord
@@ -461,7 +461,8 @@
                         // (an AndroidOwner) when setting the clock time to make sure the Compose
                         // Preview will animate when the states are read inside the draw scope.
                         val composeView = getChildAt(0) as ComposeView
-                        (composeView.getChildAt(0) as? AndroidOwner)?.invalidateDescendants()
+                        (composeView.getChildAt(0) as? ViewRootForTest)
+                            ?.invalidateDescendants()
                     }
                     Providers(AmbientAnimationClock provides clock) {
                         composable()
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 551075c..ccac4f1 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -410,9 +410,6 @@
     method public static androidx.compose.ui.Modifier dragSlopExceededGestureFilter(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.gesture.Direction,java.lang.Boolean>? canDrag, optional androidx.compose.ui.gesture.scrollorientationlocking.Orientation? orientation);
   }
 
-  @kotlin.RequiresOptIn(message="This pointer input API is experimental and is likely to change before becoming " + "stable.") public @interface ExperimentalPointerInput {
-  }
-
   public final class GestureUtilsKt {
     method public static boolean anyPointersInBounds-5eFHUEc(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange>, long bounds);
   }
@@ -493,11 +490,11 @@
 
 package androidx.compose.ui.gesture.customevents {
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public final class DelayUpEvent implements androidx.compose.ui.input.pointer.CustomEvent {
+  public final class DelayUpEvent implements androidx.compose.ui.input.pointer.CustomEvent {
     ctor public DelayUpEvent(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
     method public androidx.compose.ui.gesture.customevents.DelayUpMessage component1();
     method public java.util.Set<androidx.compose.ui.input.pointer.PointerId> component2();
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public androidx.compose.ui.gesture.customevents.DelayUpEvent copy(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
+    method public androidx.compose.ui.gesture.customevents.DelayUpEvent copy(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
     method public androidx.compose.ui.gesture.customevents.DelayUpMessage getMessage();
     method public java.util.Set<androidx.compose.ui.input.pointer.PointerId> getPointers();
     method public void setMessage(androidx.compose.ui.gesture.customevents.DelayUpMessage p);
@@ -505,7 +502,7 @@
     property public final java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public enum DelayUpMessage {
+  public enum DelayUpMessage {
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayUp;
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayedUpConsumed;
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayedUpNotConsumed;
@@ -517,6 +514,37 @@
 
 }
 
+package androidx.compose.ui.gesture.nestedscroll {
+
+  public interface NestedScrollConnection {
+    method public default void onPostFling-Pv53iXo(long consumed, long available, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Velocity,kotlin.Unit> onFinished);
+    method public default long onPostScroll-l-UAZDg(long consumed, long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+    method public default long onPreFling-TH1AsA0(long available);
+    method public default long onPreScroll-vG6bCaM(long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+  }
+
+  public final class NestedScrollDelegatingWrapperKt {
+  }
+
+  public final class NestedScrollDispatcher {
+    ctor public NestedScrollDispatcher();
+    method public void dispatchPostFling-uYzo7IE(long consumed, long available);
+    method public long dispatchPostScroll-l-UAZDg(long consumed, long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+    method public long dispatchPreFling-TH1AsA0(long available);
+    method public long dispatchPreScroll-vG6bCaM(long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+  }
+
+  public final class NestedScrollModifierKt {
+    method public static androidx.compose.ui.Modifier nestedScroll(androidx.compose.ui.Modifier, androidx.compose.ui.gesture.nestedscroll.NestedScrollConnection connection, optional androidx.compose.ui.gesture.nestedscroll.NestedScrollDispatcher? dispatcher);
+  }
+
+  public enum NestedScrollSource {
+    enum_constant public static final androidx.compose.ui.gesture.nestedscroll.NestedScrollSource Drag;
+    enum_constant public static final androidx.compose.ui.gesture.nestedscroll.NestedScrollSource Fling;
+  }
+
+}
+
 package androidx.compose.ui.gesture.scrollorientationlocking {
 
   public enum Orientation {
@@ -524,7 +552,7 @@
     enum_constant public static final androidx.compose.ui.gesture.scrollorientationlocking.Orientation Vertical;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public final class ScrollOrientationLocker {
+  public final class ScrollOrientationLocker {
     ctor public ScrollOrientationLocker(androidx.compose.ui.input.pointer.CustomEventDispatcher customEventDispatcher);
     method public void attemptToLockPointers(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation);
     method public java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> getPointersFor(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation);
@@ -678,7 +706,8 @@
 
   public final class VectorApplier extends androidx.compose.runtime.AbstractApplier<androidx.compose.ui.graphics.vector.VNode> {
     ctor public VectorApplier(androidx.compose.ui.graphics.vector.VNode root);
-    method public void insert(int index, androidx.compose.ui.graphics.vector.VNode instance);
+    method public void insertBottomUp(int index, androidx.compose.ui.graphics.vector.VNode instance);
+    method public void insertTopDown(int index, androidx.compose.ui.graphics.vector.VNode instance);
     method public void move(int from, int to, int count);
     method protected void onClear();
     method public void remove(int index, int count);
@@ -1472,13 +1501,13 @@
     method public void retainHitPaths(java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointerIds);
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput @kotlin.coroutines.RestrictsSuspension public interface HandlePointerInputScope extends androidx.compose.ui.unit.Density {
+  @kotlin.coroutines.RestrictsSuspension public interface HandlePointerInputScope extends androidx.compose.ui.unit.Density {
     method public suspend Object? awaitCustomEvent(optional androidx.compose.ui.input.pointer.PointerEventPass pass, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.CustomEvent> p);
     method public suspend Object? awaitPointerEvent(optional androidx.compose.ui.input.pointer.PointerEventPass pass, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerEvent> p);
-    method public java.util.List<androidx.compose.ui.input.pointer.PointerInputData> getCurrentPointers();
+    method public androidx.compose.ui.input.pointer.PointerEvent getCurrentEvent();
     method public long getSize-YbymL2g();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
-    property public abstract java.util.List<androidx.compose.ui.input.pointer.PointerInputData> currentPointers;
+    property public abstract androidx.compose.ui.input.pointer.PointerEvent currentEvent;
     property public abstract long size;
     property public abstract androidx.compose.ui.platform.ViewConfiguration viewConfiguration;
   }
@@ -1600,7 +1629,7 @@
     property public abstract androidx.compose.ui.input.pointer.PointerInputFilter pointerInputFilter;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public interface PointerInputScope extends androidx.compose.ui.unit.Density {
+  public interface PointerInputScope extends androidx.compose.ui.unit.Density {
     method public androidx.compose.ui.input.pointer.CustomEventDispatcher getCustomEventDispatcher();
     method public long getSize-YbymL2g();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
@@ -1626,7 +1655,7 @@
   }
 
   public final class SuspendingPointerInputFilterKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static androidx.compose.ui.Modifier pointerInput(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static androidx.compose.ui.Modifier pointerInput(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
   }
 
 }
@@ -1739,14 +1768,30 @@
     property public abstract Object layoutId;
   }
 
+  public interface LayoutInfo {
+    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
+    method public int getHeight();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
+    method public androidx.compose.ui.layout.LayoutInfo? getParentInfo();
+    method public int getWidth();
+    method public boolean isAttached();
+    method public boolean isPlaced();
+    property public abstract androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public abstract int height;
+    property public abstract boolean isAttached;
+    property public abstract boolean isPlaced;
+    property public abstract androidx.compose.ui.layout.LayoutInfo? parentInfo;
+    property public abstract int width;
+  }
+
   public final class LayoutKt {
     method @androidx.compose.runtime.Composable public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @androidx.compose.runtime.Composable public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.node.ExperimentalLayoutNodeApi public static inline void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocks, optional androidx.compose.ui.Modifier modifier);
-    method public static androidx.compose.ui.node.LayoutNode.MeasureBlocks MeasuringIntrinsicsMeasureBlocks(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
+    method @androidx.compose.runtime.Composable public static inline void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, androidx.compose.ui.node.MeasureBlocks measureBlocks, optional androidx.compose.ui.Modifier modifier);
+    method public static androidx.compose.ui.node.MeasureBlocks MeasuringIntrinsicsMeasureBlocks(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @Deprecated @androidx.compose.runtime.Composable public static void MultiMeasureLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @androidx.compose.runtime.Composable public static void WithConstraints(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.WithConstraintsScope,kotlin.Unit> content);
-    method @androidx.compose.ui.node.ExperimentalLayoutNodeApi public static androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocksOf(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
+    method public static androidx.compose.ui.node.MeasureBlocks measureBlocksOf(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
   }
 
   public interface LayoutModifier extends androidx.compose.ui.Modifier.Element {
@@ -1794,6 +1839,16 @@
     method public static inline String! toString-impl(androidx.compose.ui.layout.Placeable! p);
   }
 
+  public final class ModifierInfo {
+    ctor public ModifierInfo(androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.LayoutCoordinates coordinates, Object? extra);
+    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
+    method public Object? getExtra();
+    method public androidx.compose.ui.Modifier getModifier();
+    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public final Object? extra;
+    property public final androidx.compose.ui.Modifier modifier;
+  }
+
   public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
     method public void onGloballyPositioned(androidx.compose.ui.layout.LayoutCoordinates coordinates);
   }
@@ -1920,38 +1975,23 @@
 
 package androidx.compose.ui.node {
 
-  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level, message="This is an experimental API for Compose UI LayoutNode and is likely to change " + "before becoming stable.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalLayoutNodeApi {
-  }
-
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface InternalCoreApi {
   }
 
-  @androidx.compose.ui.node.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.compose.ui.layout.Measurable androidx.compose.ui.node.OwnerScope androidx.compose.ui.layout.Remeasurement {
+  public final class LayoutNode implements androidx.compose.ui.layout.LayoutInfo androidx.compose.ui.layout.Measurable androidx.compose.ui.node.OwnerScope androidx.compose.ui.layout.Remeasurement {
     ctor public LayoutNode();
-    method public void attach(androidx.compose.ui.node.Owner owner);
-    method public void detach();
-    method public void draw(androidx.compose.ui.graphics.Canvas canvas);
     method public void forceRemeasure();
-    method public Integer? getAlignmentLine(androidx.compose.ui.layout.AlignmentLine line);
-    method @Deprecated public boolean getCanMultiMeasure();
     method public java.util.List<androidx.compose.ui.node.LayoutNode> getChildren();
     method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
-    method public androidx.compose.ui.unit.Density getDensity();
-    method public int getDepth();
     method public int getHeight();
-    method public androidx.compose.ui.node.LayoutNode.MeasureBlocks getMeasureBlocks();
-    method public androidx.compose.ui.layout.MeasureScope getMeasureScope();
     method public androidx.compose.ui.Modifier getModifier();
-    method public java.util.List<androidx.compose.ui.node.ModifierInfo> getModifierInfo();
-    method public kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? getOnAttach();
-    method public kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? getOnDetach();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
     method public androidx.compose.ui.node.Owner? getOwner();
     method public androidx.compose.ui.node.LayoutNode? getParent();
     method public Object? getParentData();
+    method public androidx.compose.ui.layout.LayoutInfo? getParentInfo();
     method public int getWidth();
-    method public void hitTest-fhABUH0(long pointerPositionRelativeToScreen, java.util.List<androidx.compose.ui.input.pointer.PointerInputFilter> hitPointerInputFilters);
-    method public void ignoreModelReads(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method public void insertAt(int index, androidx.compose.ui.node.LayoutNode instance);
+    method public boolean isAttached();
     method public boolean isPlaced();
     method public boolean isValid();
     method public int maxIntrinsicHeight(int width);
@@ -1959,39 +1999,25 @@
     method public androidx.compose.ui.layout.Placeable measure-BRTryo0(long constraints);
     method public int minIntrinsicHeight(int width);
     method public int minIntrinsicWidth(int height);
-    method public void move(int from, int to, int count);
-    method public void place(int x, int y);
-    method public void removeAll();
-    method public void removeAt(int index, int count);
-    method public void requestRelayout();
-    method public void requestRemeasure();
-    method @Deprecated public void setCanMultiMeasure(boolean p);
-    method public void setDensity(androidx.compose.ui.unit.Density p);
-    method public void setDepth(int p);
-    method public void setMeasureBlocks(androidx.compose.ui.node.LayoutNode.MeasureBlocks value);
     method public void setModifier(androidx.compose.ui.Modifier value);
-    method public void setOnAttach(kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.Owner,kotlin.Unit>? p);
-    method public void setOnDetach(kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.Owner,kotlin.Unit>? p);
-    property @Deprecated public final boolean canMultiMeasure;
     property public final java.util.List<androidx.compose.ui.node.LayoutNode> children;
-    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
-    property public final androidx.compose.ui.unit.Density density;
-    property public final int depth;
-    property public final int height;
-    property public final boolean isPlaced;
+    property public androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public int height;
+    property public boolean isAttached;
+    property public boolean isPlaced;
     property public boolean isValid;
-    property public final androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocks;
-    property public final androidx.compose.ui.layout.MeasureScope measureScope;
     property public final androidx.compose.ui.Modifier modifier;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? onAttach;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? onDetach;
     property public final androidx.compose.ui.node.Owner? owner;
     property public final androidx.compose.ui.node.LayoutNode? parent;
     property public Object? parentData;
-    property public final int width;
+    property public androidx.compose.ui.layout.LayoutInfo? parentInfo;
+    property public int width;
   }
 
-  public static interface LayoutNode.MeasureBlocks {
+  public final class LayoutNodeKt {
+  }
+
+  public interface MeasureBlocks {
     method public int maxIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
     method public int maxIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
     method public androidx.compose.ui.layout.MeasureResult measure-8A2P9vY(androidx.compose.ui.layout.MeasureScope measureScope, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables, long constraints);
@@ -1999,28 +2025,6 @@
     method public int minIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
   }
 
-  public abstract static class LayoutNode.NoIntrinsicsMeasureBlocks implements androidx.compose.ui.node.LayoutNode.MeasureBlocks {
-    ctor public LayoutNode.NoIntrinsicsMeasureBlocks(String error);
-    method public Void maxIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
-    method public Void maxIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
-    method public Void minIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
-    method public Void minIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
-  }
-
-  public final class LayoutNodeKt {
-    method public static androidx.compose.ui.node.LayoutNode? findClosestParentNode(androidx.compose.ui.node.LayoutNode, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.LayoutNode,java.lang.Boolean> selector);
-  }
-
-  public final class ModifierInfo {
-    ctor public ModifierInfo(androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.LayoutCoordinates coordinates, Object? extra);
-    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
-    method public Object? getExtra();
-    method public androidx.compose.ui.Modifier getModifier();
-    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
-    property public final Object? extra;
-    property public final androidx.compose.ui.Modifier modifier;
-  }
-
   public interface OwnedLayer {
     method public void destroy();
     method public void drawLayer(androidx.compose.ui.graphics.Canvas canvas);
@@ -2044,7 +2048,6 @@
     method public androidx.compose.ui.focus.FocusManager getFocusManager();
     method public androidx.compose.ui.text.font.Font.ResourceLoader getFontLoader();
     method public androidx.compose.ui.hapticfeedback.HapticFeedback getHapticFeedBack();
-    method public boolean getHasPendingMeasureOrLayout();
     method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
     method public long getMeasureIteration();
     method public androidx.compose.ui.node.LayoutNode getRoot();
@@ -2070,7 +2073,6 @@
     property public abstract androidx.compose.ui.focus.FocusManager focusManager;
     property public abstract androidx.compose.ui.text.font.Font.ResourceLoader fontLoader;
     property public abstract androidx.compose.ui.hapticfeedback.HapticFeedback hapticFeedBack;
-    property public abstract boolean hasPendingMeasureOrLayout;
     property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
     property public abstract long measureIteration;
     property public abstract androidx.compose.ui.node.LayoutNode root;
@@ -2106,16 +2108,13 @@
     property public final T? value;
   }
 
-  public final class UiApplier implements androidx.compose.runtime.Applier<java.lang.Object> {
+  public final class UiApplier extends androidx.compose.runtime.AbstractApplier<java.lang.Object> {
     ctor public UiApplier(Object root);
-    method public void clear();
-    method public void down(Object node);
-    method public Object getCurrent();
-    method public void insert(int index, Object instance);
+    method public void insertBottomUp(int index, Object instance);
+    method public void insertTopDown(int index, Object instance);
     method public void move(int from, int to, int count);
+    method protected void onClear();
     method public void remove(int index, int count);
-    method public void up();
-    property public Object current;
   }
 
   public final class ViewInteropKt {
@@ -2186,31 +2185,6 @@
   public final class AndroidComposeViewKt {
   }
 
-  public interface AndroidOwner extends androidx.compose.ui.node.Owner {
-    method @androidx.compose.ui.node.ExperimentalLayoutNodeApi public void addAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view, androidx.compose.ui.node.LayoutNode layoutNode);
-    method public void drawAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view, android.graphics.Canvas canvas);
-    method public kotlin.jvm.functions.Function1<android.content.res.Configuration,kotlin.Unit> getConfigurationChangeObserver();
-    method public android.view.View getView();
-    method public androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners? getViewTreeOwners();
-    method public void invalidateDescendants();
-    method public void removeAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view);
-    method public void setConfigurationChangeObserver(kotlin.jvm.functions.Function1<? super android.content.res.Configuration,kotlin.Unit> p);
-    method public void setOnViewTreeOwnersAvailable(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners,kotlin.Unit> callback);
-    property public abstract kotlin.jvm.functions.Function1<android.content.res.Configuration,kotlin.Unit> configurationChangeObserver;
-    property public abstract android.view.View view;
-    property public abstract androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners? viewTreeOwners;
-  }
-
-  public static final class AndroidOwner.ViewTreeOwners {
-    ctor public AndroidOwner.ViewTreeOwners(androidx.lifecycle.LifecycleOwner lifecycleOwner, androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, androidx.savedstate.SavedStateRegistryOwner savedStateRegistryOwner);
-    method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
-    method public androidx.savedstate.SavedStateRegistryOwner getSavedStateRegistryOwner();
-    method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner();
-    property public final androidx.lifecycle.LifecycleOwner lifecycleOwner;
-    property public final androidx.savedstate.SavedStateRegistryOwner savedStateRegistryOwner;
-    property public final androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner;
-  }
-
   public final class AndroidUriHandler implements androidx.compose.ui.platform.UriHandler {
     ctor public AndroidUriHandler(android.content.Context context);
     method public void openUri(String uri);
@@ -2289,10 +2263,6 @@
   public final class JvmActualsKt {
   }
 
-  public final class SubcompositionKt {
-    method @MainThread public static androidx.compose.runtime.Composition subcomposeInto(androidx.compose.ui.node.LayoutNode container, androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-  }
-
   public final class TestTagKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier testTag(androidx.compose.ui.Modifier, String tag);
   }
@@ -2344,6 +2314,23 @@
     property public abstract float touchSlop;
   }
 
+  @VisibleForTesting public interface ViewRootForTest extends androidx.compose.ui.node.Owner {
+    method public boolean getHasPendingMeasureOrLayout();
+    method public android.view.View getView();
+    method public void invalidateDescendants();
+    method public boolean isLifecycleInResumedState();
+    property public abstract boolean hasPendingMeasureOrLayout;
+    property public abstract boolean isLifecycleInResumedState;
+    property public abstract android.view.View view;
+    field public static final androidx.compose.ui.platform.ViewRootForTest.Companion Companion;
+  }
+
+  public static final class ViewRootForTest.Companion {
+    method public kotlin.jvm.functions.Function1<androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? getOnViewCreatedCallback();
+    method public void setOnViewCreatedCallback(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? p);
+    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? onViewCreatedCallback;
+  }
+
   @androidx.compose.runtime.Stable public interface WindowManager {
     method public boolean isWindowFocused();
     property public abstract boolean isWindowFocused;
@@ -2401,6 +2388,10 @@
     ctor public LoadedResource(T? resource);
   }
 
+  public final class PainterResourcesKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.painter.Painter painterResource(@DrawableRes int id);
+  }
+
   public final class PendingResource<T> extends androidx.compose.ui.res.Resource<T> {
     ctor public PendingResource(T? resource);
   }
@@ -2435,7 +2426,7 @@
 
 package androidx.compose.ui.selection {
 
-  public interface Selectable {
+  @androidx.compose.ui.text.ExperimentalTextApi public interface Selectable {
     method public androidx.compose.ui.geometry.Rect getBoundingBox(int offset);
     method public long getHandlePosition-F1C5BW0(androidx.compose.ui.selection.Selection selection, boolean isStartHandle);
     method public androidx.compose.ui.layout.LayoutCoordinates? getLayoutCoordinates();
@@ -2485,9 +2476,11 @@
   public final class SelectionManagerKt {
   }
 
-  public interface SelectionRegistrar {
-    method public void onPositionChange();
-    method public void onUpdateSelection-rULFVbc(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition, long endPosition);
+  @androidx.compose.ui.text.ExperimentalTextApi public interface SelectionRegistrar {
+    method public void notifyPositionChange();
+    method public void notifySelectionUpdate-rULFVbc(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition, long endPosition);
+    method public void notifySelectionUpdateEnd();
+    method public void notifySelectionUpdateStart-YJiYy8w(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition);
     method public androidx.compose.ui.selection.Selectable subscribe(androidx.compose.ui.selection.Selectable selectable);
     method public void unsubscribe(androidx.compose.ui.selection.Selectable selectable);
   }
@@ -2601,12 +2594,13 @@
     method public operator <T> T! get(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key);
     method public <T> T! getOrElse(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
     method public <T> T? getOrElseNullable(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
-    method public boolean isEmpty();
+    method public boolean isClearingSemantics();
     method public boolean isMergingSemanticsOfDescendants();
     method public java.util.Iterator<java.util.Map.Entry<androidx.compose.ui.semantics.SemanticsPropertyKey<?>,java.lang.Object>> iterator();
     method public <T> void set(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, T? value);
+    method public void setClearingSemantics(boolean p);
     method public void setMergingSemanticsOfDescendants(boolean p);
-    property public final boolean isEmpty;
+    property public final boolean isClearingSemantics;
     property public final boolean isMergingSemanticsOfDescendants;
   }
 
@@ -2622,6 +2616,7 @@
   }
 
   public final class SemanticsModifierKt {
+    method public static androidx.compose.ui.Modifier clearAndSetSemantics(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
     method public static androidx.compose.ui.Modifier semantics(androidx.compose.ui.Modifier, optional boolean mergeDescendants, kotlin.jvm.functions.Function1<? super androidx.compose.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
   }
 
@@ -2629,25 +2624,27 @@
     method public int getAlignmentLinePosition(androidx.compose.ui.layout.AlignmentLine line);
     method public androidx.compose.ui.geometry.Rect getBoundsInRoot();
     method public java.util.List<androidx.compose.ui.semantics.SemanticsNode> getChildren();
-    method public androidx.compose.ui.node.LayoutNode getComponentNode();
     method public androidx.compose.ui.semantics.SemanticsConfiguration getConfig();
     method public androidx.compose.ui.geometry.Rect getGlobalBounds();
     method public long getGlobalPosition-F1C5BW0();
     method public int getId();
+    method public androidx.compose.ui.layout.LayoutInfo getLayoutInfo();
     method public boolean getMergingEnabled();
+    method public androidx.compose.ui.node.Owner? getOwner();
     method public androidx.compose.ui.semantics.SemanticsNode? getParent();
     method public long getPositionInRoot-F1C5BW0();
     method public long getSize-YbymL2g();
     method public boolean isRoot();
     property public final androidx.compose.ui.geometry.Rect boundsInRoot;
     property public final java.util.List<androidx.compose.ui.semantics.SemanticsNode> children;
-    property public final androidx.compose.ui.node.LayoutNode componentNode;
     property public final androidx.compose.ui.semantics.SemanticsConfiguration config;
     property public final androidx.compose.ui.geometry.Rect globalBounds;
     property public final long globalPosition;
     property public final int id;
     property public final boolean isRoot;
+    property public final androidx.compose.ui.layout.LayoutInfo layoutInfo;
     property public final boolean mergingEnabled;
+    property public final androidx.compose.ui.node.Owner? owner;
     property public final androidx.compose.ui.semantics.SemanticsNode? parent;
     property public final long positionInRoot;
     property public final long size;
@@ -2657,7 +2654,6 @@
   }
 
   public final class SemanticsOwner {
-    ctor public SemanticsOwner(androidx.compose.ui.node.LayoutNode rootNode);
     method public androidx.compose.ui.node.LayoutNode getRootNode();
     method public androidx.compose.ui.semantics.SemanticsNode getRootSemanticsNode();
     method public androidx.compose.ui.semantics.SemanticsNode getUnmergedRootSemanticsNode();
@@ -2832,11 +2828,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class AndroidDialogProperties implements androidx.compose.ui.window.DialogProperties {
-    ctor public AndroidDialogProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    ctor public AndroidDialogProperties(boolean dismissOnBackPress, boolean dismissOnClickOutside, androidx.compose.ui.window.SecureFlagPolicy securePolicy);
     ctor public AndroidDialogProperties();
-    method public androidx.compose.ui.window.SecureFlagPolicy component1();
-    method @androidx.compose.runtime.Immutable public androidx.compose.ui.window.AndroidDialogProperties copy(androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    method public boolean component1();
+    method public boolean component2();
+    method public androidx.compose.ui.window.SecureFlagPolicy component3();
+    method @androidx.compose.runtime.Immutable public androidx.compose.ui.window.AndroidDialogProperties copy(boolean dismissOnBackPress, boolean dismissOnClickOutside, androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    method public boolean getDismissOnBackPress();
+    method public boolean getDismissOnClickOutside();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    property public final boolean dismissOnBackPress;
+    property public final boolean dismissOnClickOutside;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 551075c..ccac4f1 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -410,9 +410,6 @@
     method public static androidx.compose.ui.Modifier dragSlopExceededGestureFilter(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.gesture.Direction,java.lang.Boolean>? canDrag, optional androidx.compose.ui.gesture.scrollorientationlocking.Orientation? orientation);
   }
 
-  @kotlin.RequiresOptIn(message="This pointer input API is experimental and is likely to change before becoming " + "stable.") public @interface ExperimentalPointerInput {
-  }
-
   public final class GestureUtilsKt {
     method public static boolean anyPointersInBounds-5eFHUEc(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange>, long bounds);
   }
@@ -493,11 +490,11 @@
 
 package androidx.compose.ui.gesture.customevents {
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public final class DelayUpEvent implements androidx.compose.ui.input.pointer.CustomEvent {
+  public final class DelayUpEvent implements androidx.compose.ui.input.pointer.CustomEvent {
     ctor public DelayUpEvent(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
     method public androidx.compose.ui.gesture.customevents.DelayUpMessage component1();
     method public java.util.Set<androidx.compose.ui.input.pointer.PointerId> component2();
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public androidx.compose.ui.gesture.customevents.DelayUpEvent copy(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
+    method public androidx.compose.ui.gesture.customevents.DelayUpEvent copy(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
     method public androidx.compose.ui.gesture.customevents.DelayUpMessage getMessage();
     method public java.util.Set<androidx.compose.ui.input.pointer.PointerId> getPointers();
     method public void setMessage(androidx.compose.ui.gesture.customevents.DelayUpMessage p);
@@ -505,7 +502,7 @@
     property public final java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public enum DelayUpMessage {
+  public enum DelayUpMessage {
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayUp;
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayedUpConsumed;
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayedUpNotConsumed;
@@ -517,6 +514,37 @@
 
 }
 
+package androidx.compose.ui.gesture.nestedscroll {
+
+  public interface NestedScrollConnection {
+    method public default void onPostFling-Pv53iXo(long consumed, long available, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Velocity,kotlin.Unit> onFinished);
+    method public default long onPostScroll-l-UAZDg(long consumed, long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+    method public default long onPreFling-TH1AsA0(long available);
+    method public default long onPreScroll-vG6bCaM(long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+  }
+
+  public final class NestedScrollDelegatingWrapperKt {
+  }
+
+  public final class NestedScrollDispatcher {
+    ctor public NestedScrollDispatcher();
+    method public void dispatchPostFling-uYzo7IE(long consumed, long available);
+    method public long dispatchPostScroll-l-UAZDg(long consumed, long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+    method public long dispatchPreFling-TH1AsA0(long available);
+    method public long dispatchPreScroll-vG6bCaM(long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+  }
+
+  public final class NestedScrollModifierKt {
+    method public static androidx.compose.ui.Modifier nestedScroll(androidx.compose.ui.Modifier, androidx.compose.ui.gesture.nestedscroll.NestedScrollConnection connection, optional androidx.compose.ui.gesture.nestedscroll.NestedScrollDispatcher? dispatcher);
+  }
+
+  public enum NestedScrollSource {
+    enum_constant public static final androidx.compose.ui.gesture.nestedscroll.NestedScrollSource Drag;
+    enum_constant public static final androidx.compose.ui.gesture.nestedscroll.NestedScrollSource Fling;
+  }
+
+}
+
 package androidx.compose.ui.gesture.scrollorientationlocking {
 
   public enum Orientation {
@@ -524,7 +552,7 @@
     enum_constant public static final androidx.compose.ui.gesture.scrollorientationlocking.Orientation Vertical;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public final class ScrollOrientationLocker {
+  public final class ScrollOrientationLocker {
     ctor public ScrollOrientationLocker(androidx.compose.ui.input.pointer.CustomEventDispatcher customEventDispatcher);
     method public void attemptToLockPointers(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation);
     method public java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> getPointersFor(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation);
@@ -678,7 +706,8 @@
 
   public final class VectorApplier extends androidx.compose.runtime.AbstractApplier<androidx.compose.ui.graphics.vector.VNode> {
     ctor public VectorApplier(androidx.compose.ui.graphics.vector.VNode root);
-    method public void insert(int index, androidx.compose.ui.graphics.vector.VNode instance);
+    method public void insertBottomUp(int index, androidx.compose.ui.graphics.vector.VNode instance);
+    method public void insertTopDown(int index, androidx.compose.ui.graphics.vector.VNode instance);
     method public void move(int from, int to, int count);
     method protected void onClear();
     method public void remove(int index, int count);
@@ -1472,13 +1501,13 @@
     method public void retainHitPaths(java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointerIds);
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput @kotlin.coroutines.RestrictsSuspension public interface HandlePointerInputScope extends androidx.compose.ui.unit.Density {
+  @kotlin.coroutines.RestrictsSuspension public interface HandlePointerInputScope extends androidx.compose.ui.unit.Density {
     method public suspend Object? awaitCustomEvent(optional androidx.compose.ui.input.pointer.PointerEventPass pass, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.CustomEvent> p);
     method public suspend Object? awaitPointerEvent(optional androidx.compose.ui.input.pointer.PointerEventPass pass, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerEvent> p);
-    method public java.util.List<androidx.compose.ui.input.pointer.PointerInputData> getCurrentPointers();
+    method public androidx.compose.ui.input.pointer.PointerEvent getCurrentEvent();
     method public long getSize-YbymL2g();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
-    property public abstract java.util.List<androidx.compose.ui.input.pointer.PointerInputData> currentPointers;
+    property public abstract androidx.compose.ui.input.pointer.PointerEvent currentEvent;
     property public abstract long size;
     property public abstract androidx.compose.ui.platform.ViewConfiguration viewConfiguration;
   }
@@ -1600,7 +1629,7 @@
     property public abstract androidx.compose.ui.input.pointer.PointerInputFilter pointerInputFilter;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public interface PointerInputScope extends androidx.compose.ui.unit.Density {
+  public interface PointerInputScope extends androidx.compose.ui.unit.Density {
     method public androidx.compose.ui.input.pointer.CustomEventDispatcher getCustomEventDispatcher();
     method public long getSize-YbymL2g();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
@@ -1626,7 +1655,7 @@
   }
 
   public final class SuspendingPointerInputFilterKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static androidx.compose.ui.Modifier pointerInput(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static androidx.compose.ui.Modifier pointerInput(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
   }
 
 }
@@ -1739,14 +1768,30 @@
     property public abstract Object layoutId;
   }
 
+  public interface LayoutInfo {
+    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
+    method public int getHeight();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
+    method public androidx.compose.ui.layout.LayoutInfo? getParentInfo();
+    method public int getWidth();
+    method public boolean isAttached();
+    method public boolean isPlaced();
+    property public abstract androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public abstract int height;
+    property public abstract boolean isAttached;
+    property public abstract boolean isPlaced;
+    property public abstract androidx.compose.ui.layout.LayoutInfo? parentInfo;
+    property public abstract int width;
+  }
+
   public final class LayoutKt {
     method @androidx.compose.runtime.Composable public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @androidx.compose.runtime.Composable public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.node.ExperimentalLayoutNodeApi public static inline void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocks, optional androidx.compose.ui.Modifier modifier);
-    method public static androidx.compose.ui.node.LayoutNode.MeasureBlocks MeasuringIntrinsicsMeasureBlocks(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
+    method @androidx.compose.runtime.Composable public static inline void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, androidx.compose.ui.node.MeasureBlocks measureBlocks, optional androidx.compose.ui.Modifier modifier);
+    method public static androidx.compose.ui.node.MeasureBlocks MeasuringIntrinsicsMeasureBlocks(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @Deprecated @androidx.compose.runtime.Composable public static void MultiMeasureLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @androidx.compose.runtime.Composable public static void WithConstraints(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.WithConstraintsScope,kotlin.Unit> content);
-    method @androidx.compose.ui.node.ExperimentalLayoutNodeApi public static androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocksOf(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
+    method public static androidx.compose.ui.node.MeasureBlocks measureBlocksOf(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
   }
 
   public interface LayoutModifier extends androidx.compose.ui.Modifier.Element {
@@ -1794,6 +1839,16 @@
     method public static inline String! toString-impl(androidx.compose.ui.layout.Placeable! p);
   }
 
+  public final class ModifierInfo {
+    ctor public ModifierInfo(androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.LayoutCoordinates coordinates, Object? extra);
+    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
+    method public Object? getExtra();
+    method public androidx.compose.ui.Modifier getModifier();
+    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public final Object? extra;
+    property public final androidx.compose.ui.Modifier modifier;
+  }
+
   public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
     method public void onGloballyPositioned(androidx.compose.ui.layout.LayoutCoordinates coordinates);
   }
@@ -1920,38 +1975,23 @@
 
 package androidx.compose.ui.node {
 
-  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level, message="This is an experimental API for Compose UI LayoutNode and is likely to change " + "before becoming stable.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalLayoutNodeApi {
-  }
-
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface InternalCoreApi {
   }
 
-  @androidx.compose.ui.node.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.compose.ui.layout.Measurable androidx.compose.ui.node.OwnerScope androidx.compose.ui.layout.Remeasurement {
+  public final class LayoutNode implements androidx.compose.ui.layout.LayoutInfo androidx.compose.ui.layout.Measurable androidx.compose.ui.node.OwnerScope androidx.compose.ui.layout.Remeasurement {
     ctor public LayoutNode();
-    method public void attach(androidx.compose.ui.node.Owner owner);
-    method public void detach();
-    method public void draw(androidx.compose.ui.graphics.Canvas canvas);
     method public void forceRemeasure();
-    method public Integer? getAlignmentLine(androidx.compose.ui.layout.AlignmentLine line);
-    method @Deprecated public boolean getCanMultiMeasure();
     method public java.util.List<androidx.compose.ui.node.LayoutNode> getChildren();
     method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
-    method public androidx.compose.ui.unit.Density getDensity();
-    method public int getDepth();
     method public int getHeight();
-    method public androidx.compose.ui.node.LayoutNode.MeasureBlocks getMeasureBlocks();
-    method public androidx.compose.ui.layout.MeasureScope getMeasureScope();
     method public androidx.compose.ui.Modifier getModifier();
-    method public java.util.List<androidx.compose.ui.node.ModifierInfo> getModifierInfo();
-    method public kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? getOnAttach();
-    method public kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? getOnDetach();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
     method public androidx.compose.ui.node.Owner? getOwner();
     method public androidx.compose.ui.node.LayoutNode? getParent();
     method public Object? getParentData();
+    method public androidx.compose.ui.layout.LayoutInfo? getParentInfo();
     method public int getWidth();
-    method public void hitTest-fhABUH0(long pointerPositionRelativeToScreen, java.util.List<androidx.compose.ui.input.pointer.PointerInputFilter> hitPointerInputFilters);
-    method public void ignoreModelReads(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method public void insertAt(int index, androidx.compose.ui.node.LayoutNode instance);
+    method public boolean isAttached();
     method public boolean isPlaced();
     method public boolean isValid();
     method public int maxIntrinsicHeight(int width);
@@ -1959,39 +1999,25 @@
     method public androidx.compose.ui.layout.Placeable measure-BRTryo0(long constraints);
     method public int minIntrinsicHeight(int width);
     method public int minIntrinsicWidth(int height);
-    method public void move(int from, int to, int count);
-    method public void place(int x, int y);
-    method public void removeAll();
-    method public void removeAt(int index, int count);
-    method public void requestRelayout();
-    method public void requestRemeasure();
-    method @Deprecated public void setCanMultiMeasure(boolean p);
-    method public void setDensity(androidx.compose.ui.unit.Density p);
-    method public void setDepth(int p);
-    method public void setMeasureBlocks(androidx.compose.ui.node.LayoutNode.MeasureBlocks value);
     method public void setModifier(androidx.compose.ui.Modifier value);
-    method public void setOnAttach(kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.Owner,kotlin.Unit>? p);
-    method public void setOnDetach(kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.Owner,kotlin.Unit>? p);
-    property @Deprecated public final boolean canMultiMeasure;
     property public final java.util.List<androidx.compose.ui.node.LayoutNode> children;
-    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
-    property public final androidx.compose.ui.unit.Density density;
-    property public final int depth;
-    property public final int height;
-    property public final boolean isPlaced;
+    property public androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public int height;
+    property public boolean isAttached;
+    property public boolean isPlaced;
     property public boolean isValid;
-    property public final androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocks;
-    property public final androidx.compose.ui.layout.MeasureScope measureScope;
     property public final androidx.compose.ui.Modifier modifier;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? onAttach;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? onDetach;
     property public final androidx.compose.ui.node.Owner? owner;
     property public final androidx.compose.ui.node.LayoutNode? parent;
     property public Object? parentData;
-    property public final int width;
+    property public androidx.compose.ui.layout.LayoutInfo? parentInfo;
+    property public int width;
   }
 
-  public static interface LayoutNode.MeasureBlocks {
+  public final class LayoutNodeKt {
+  }
+
+  public interface MeasureBlocks {
     method public int maxIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
     method public int maxIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
     method public androidx.compose.ui.layout.MeasureResult measure-8A2P9vY(androidx.compose.ui.layout.MeasureScope measureScope, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables, long constraints);
@@ -1999,28 +2025,6 @@
     method public int minIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
   }
 
-  public abstract static class LayoutNode.NoIntrinsicsMeasureBlocks implements androidx.compose.ui.node.LayoutNode.MeasureBlocks {
-    ctor public LayoutNode.NoIntrinsicsMeasureBlocks(String error);
-    method public Void maxIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
-    method public Void maxIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
-    method public Void minIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
-    method public Void minIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
-  }
-
-  public final class LayoutNodeKt {
-    method public static androidx.compose.ui.node.LayoutNode? findClosestParentNode(androidx.compose.ui.node.LayoutNode, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.LayoutNode,java.lang.Boolean> selector);
-  }
-
-  public final class ModifierInfo {
-    ctor public ModifierInfo(androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.LayoutCoordinates coordinates, Object? extra);
-    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
-    method public Object? getExtra();
-    method public androidx.compose.ui.Modifier getModifier();
-    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
-    property public final Object? extra;
-    property public final androidx.compose.ui.Modifier modifier;
-  }
-
   public interface OwnedLayer {
     method public void destroy();
     method public void drawLayer(androidx.compose.ui.graphics.Canvas canvas);
@@ -2044,7 +2048,6 @@
     method public androidx.compose.ui.focus.FocusManager getFocusManager();
     method public androidx.compose.ui.text.font.Font.ResourceLoader getFontLoader();
     method public androidx.compose.ui.hapticfeedback.HapticFeedback getHapticFeedBack();
-    method public boolean getHasPendingMeasureOrLayout();
     method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
     method public long getMeasureIteration();
     method public androidx.compose.ui.node.LayoutNode getRoot();
@@ -2070,7 +2073,6 @@
     property public abstract androidx.compose.ui.focus.FocusManager focusManager;
     property public abstract androidx.compose.ui.text.font.Font.ResourceLoader fontLoader;
     property public abstract androidx.compose.ui.hapticfeedback.HapticFeedback hapticFeedBack;
-    property public abstract boolean hasPendingMeasureOrLayout;
     property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
     property public abstract long measureIteration;
     property public abstract androidx.compose.ui.node.LayoutNode root;
@@ -2106,16 +2108,13 @@
     property public final T? value;
   }
 
-  public final class UiApplier implements androidx.compose.runtime.Applier<java.lang.Object> {
+  public final class UiApplier extends androidx.compose.runtime.AbstractApplier<java.lang.Object> {
     ctor public UiApplier(Object root);
-    method public void clear();
-    method public void down(Object node);
-    method public Object getCurrent();
-    method public void insert(int index, Object instance);
+    method public void insertBottomUp(int index, Object instance);
+    method public void insertTopDown(int index, Object instance);
     method public void move(int from, int to, int count);
+    method protected void onClear();
     method public void remove(int index, int count);
-    method public void up();
-    property public Object current;
   }
 
   public final class ViewInteropKt {
@@ -2186,31 +2185,6 @@
   public final class AndroidComposeViewKt {
   }
 
-  public interface AndroidOwner extends androidx.compose.ui.node.Owner {
-    method @androidx.compose.ui.node.ExperimentalLayoutNodeApi public void addAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view, androidx.compose.ui.node.LayoutNode layoutNode);
-    method public void drawAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view, android.graphics.Canvas canvas);
-    method public kotlin.jvm.functions.Function1<android.content.res.Configuration,kotlin.Unit> getConfigurationChangeObserver();
-    method public android.view.View getView();
-    method public androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners? getViewTreeOwners();
-    method public void invalidateDescendants();
-    method public void removeAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view);
-    method public void setConfigurationChangeObserver(kotlin.jvm.functions.Function1<? super android.content.res.Configuration,kotlin.Unit> p);
-    method public void setOnViewTreeOwnersAvailable(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners,kotlin.Unit> callback);
-    property public abstract kotlin.jvm.functions.Function1<android.content.res.Configuration,kotlin.Unit> configurationChangeObserver;
-    property public abstract android.view.View view;
-    property public abstract androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners? viewTreeOwners;
-  }
-
-  public static final class AndroidOwner.ViewTreeOwners {
-    ctor public AndroidOwner.ViewTreeOwners(androidx.lifecycle.LifecycleOwner lifecycleOwner, androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, androidx.savedstate.SavedStateRegistryOwner savedStateRegistryOwner);
-    method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
-    method public androidx.savedstate.SavedStateRegistryOwner getSavedStateRegistryOwner();
-    method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner();
-    property public final androidx.lifecycle.LifecycleOwner lifecycleOwner;
-    property public final androidx.savedstate.SavedStateRegistryOwner savedStateRegistryOwner;
-    property public final androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner;
-  }
-
   public final class AndroidUriHandler implements androidx.compose.ui.platform.UriHandler {
     ctor public AndroidUriHandler(android.content.Context context);
     method public void openUri(String uri);
@@ -2289,10 +2263,6 @@
   public final class JvmActualsKt {
   }
 
-  public final class SubcompositionKt {
-    method @MainThread public static androidx.compose.runtime.Composition subcomposeInto(androidx.compose.ui.node.LayoutNode container, androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-  }
-
   public final class TestTagKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier testTag(androidx.compose.ui.Modifier, String tag);
   }
@@ -2344,6 +2314,23 @@
     property public abstract float touchSlop;
   }
 
+  @VisibleForTesting public interface ViewRootForTest extends androidx.compose.ui.node.Owner {
+    method public boolean getHasPendingMeasureOrLayout();
+    method public android.view.View getView();
+    method public void invalidateDescendants();
+    method public boolean isLifecycleInResumedState();
+    property public abstract boolean hasPendingMeasureOrLayout;
+    property public abstract boolean isLifecycleInResumedState;
+    property public abstract android.view.View view;
+    field public static final androidx.compose.ui.platform.ViewRootForTest.Companion Companion;
+  }
+
+  public static final class ViewRootForTest.Companion {
+    method public kotlin.jvm.functions.Function1<androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? getOnViewCreatedCallback();
+    method public void setOnViewCreatedCallback(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? p);
+    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? onViewCreatedCallback;
+  }
+
   @androidx.compose.runtime.Stable public interface WindowManager {
     method public boolean isWindowFocused();
     property public abstract boolean isWindowFocused;
@@ -2401,6 +2388,10 @@
     ctor public LoadedResource(T? resource);
   }
 
+  public final class PainterResourcesKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.painter.Painter painterResource(@DrawableRes int id);
+  }
+
   public final class PendingResource<T> extends androidx.compose.ui.res.Resource<T> {
     ctor public PendingResource(T? resource);
   }
@@ -2435,7 +2426,7 @@
 
 package androidx.compose.ui.selection {
 
-  public interface Selectable {
+  @androidx.compose.ui.text.ExperimentalTextApi public interface Selectable {
     method public androidx.compose.ui.geometry.Rect getBoundingBox(int offset);
     method public long getHandlePosition-F1C5BW0(androidx.compose.ui.selection.Selection selection, boolean isStartHandle);
     method public androidx.compose.ui.layout.LayoutCoordinates? getLayoutCoordinates();
@@ -2485,9 +2476,11 @@
   public final class SelectionManagerKt {
   }
 
-  public interface SelectionRegistrar {
-    method public void onPositionChange();
-    method public void onUpdateSelection-rULFVbc(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition, long endPosition);
+  @androidx.compose.ui.text.ExperimentalTextApi public interface SelectionRegistrar {
+    method public void notifyPositionChange();
+    method public void notifySelectionUpdate-rULFVbc(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition, long endPosition);
+    method public void notifySelectionUpdateEnd();
+    method public void notifySelectionUpdateStart-YJiYy8w(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition);
     method public androidx.compose.ui.selection.Selectable subscribe(androidx.compose.ui.selection.Selectable selectable);
     method public void unsubscribe(androidx.compose.ui.selection.Selectable selectable);
   }
@@ -2601,12 +2594,13 @@
     method public operator <T> T! get(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key);
     method public <T> T! getOrElse(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
     method public <T> T? getOrElseNullable(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
-    method public boolean isEmpty();
+    method public boolean isClearingSemantics();
     method public boolean isMergingSemanticsOfDescendants();
     method public java.util.Iterator<java.util.Map.Entry<androidx.compose.ui.semantics.SemanticsPropertyKey<?>,java.lang.Object>> iterator();
     method public <T> void set(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, T? value);
+    method public void setClearingSemantics(boolean p);
     method public void setMergingSemanticsOfDescendants(boolean p);
-    property public final boolean isEmpty;
+    property public final boolean isClearingSemantics;
     property public final boolean isMergingSemanticsOfDescendants;
   }
 
@@ -2622,6 +2616,7 @@
   }
 
   public final class SemanticsModifierKt {
+    method public static androidx.compose.ui.Modifier clearAndSetSemantics(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
     method public static androidx.compose.ui.Modifier semantics(androidx.compose.ui.Modifier, optional boolean mergeDescendants, kotlin.jvm.functions.Function1<? super androidx.compose.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
   }
 
@@ -2629,25 +2624,27 @@
     method public int getAlignmentLinePosition(androidx.compose.ui.layout.AlignmentLine line);
     method public androidx.compose.ui.geometry.Rect getBoundsInRoot();
     method public java.util.List<androidx.compose.ui.semantics.SemanticsNode> getChildren();
-    method public androidx.compose.ui.node.LayoutNode getComponentNode();
     method public androidx.compose.ui.semantics.SemanticsConfiguration getConfig();
     method public androidx.compose.ui.geometry.Rect getGlobalBounds();
     method public long getGlobalPosition-F1C5BW0();
     method public int getId();
+    method public androidx.compose.ui.layout.LayoutInfo getLayoutInfo();
     method public boolean getMergingEnabled();
+    method public androidx.compose.ui.node.Owner? getOwner();
     method public androidx.compose.ui.semantics.SemanticsNode? getParent();
     method public long getPositionInRoot-F1C5BW0();
     method public long getSize-YbymL2g();
     method public boolean isRoot();
     property public final androidx.compose.ui.geometry.Rect boundsInRoot;
     property public final java.util.List<androidx.compose.ui.semantics.SemanticsNode> children;
-    property public final androidx.compose.ui.node.LayoutNode componentNode;
     property public final androidx.compose.ui.semantics.SemanticsConfiguration config;
     property public final androidx.compose.ui.geometry.Rect globalBounds;
     property public final long globalPosition;
     property public final int id;
     property public final boolean isRoot;
+    property public final androidx.compose.ui.layout.LayoutInfo layoutInfo;
     property public final boolean mergingEnabled;
+    property public final androidx.compose.ui.node.Owner? owner;
     property public final androidx.compose.ui.semantics.SemanticsNode? parent;
     property public final long positionInRoot;
     property public final long size;
@@ -2657,7 +2654,6 @@
   }
 
   public final class SemanticsOwner {
-    ctor public SemanticsOwner(androidx.compose.ui.node.LayoutNode rootNode);
     method public androidx.compose.ui.node.LayoutNode getRootNode();
     method public androidx.compose.ui.semantics.SemanticsNode getRootSemanticsNode();
     method public androidx.compose.ui.semantics.SemanticsNode getUnmergedRootSemanticsNode();
@@ -2832,11 +2828,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class AndroidDialogProperties implements androidx.compose.ui.window.DialogProperties {
-    ctor public AndroidDialogProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    ctor public AndroidDialogProperties(boolean dismissOnBackPress, boolean dismissOnClickOutside, androidx.compose.ui.window.SecureFlagPolicy securePolicy);
     ctor public AndroidDialogProperties();
-    method public androidx.compose.ui.window.SecureFlagPolicy component1();
-    method @androidx.compose.runtime.Immutable public androidx.compose.ui.window.AndroidDialogProperties copy(androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    method public boolean component1();
+    method public boolean component2();
+    method public androidx.compose.ui.window.SecureFlagPolicy component3();
+    method @androidx.compose.runtime.Immutable public androidx.compose.ui.window.AndroidDialogProperties copy(boolean dismissOnBackPress, boolean dismissOnClickOutside, androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    method public boolean getDismissOnBackPress();
+    method public boolean getDismissOnClickOutside();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    property public final boolean dismissOnBackPress;
+    property public final boolean dismissOnClickOutside;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
 
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index dac9cd5..74fba3b 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -410,9 +410,6 @@
     method public static androidx.compose.ui.Modifier dragSlopExceededGestureFilter(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> onDragSlopExceeded, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.gesture.Direction,java.lang.Boolean>? canDrag, optional androidx.compose.ui.gesture.scrollorientationlocking.Orientation? orientation);
   }
 
-  @kotlin.RequiresOptIn(message="This pointer input API is experimental and is likely to change before becoming " + "stable.") public @interface ExperimentalPointerInput {
-  }
-
   public final class GestureUtilsKt {
     method public static boolean anyPointersInBounds-5eFHUEc(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange>, long bounds);
   }
@@ -493,11 +490,11 @@
 
 package androidx.compose.ui.gesture.customevents {
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public final class DelayUpEvent implements androidx.compose.ui.input.pointer.CustomEvent {
+  public final class DelayUpEvent implements androidx.compose.ui.input.pointer.CustomEvent {
     ctor public DelayUpEvent(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
     method public androidx.compose.ui.gesture.customevents.DelayUpMessage component1();
     method public java.util.Set<androidx.compose.ui.input.pointer.PointerId> component2();
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public androidx.compose.ui.gesture.customevents.DelayUpEvent copy(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
+    method public androidx.compose.ui.gesture.customevents.DelayUpEvent copy(androidx.compose.ui.gesture.customevents.DelayUpMessage message, java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers);
     method public androidx.compose.ui.gesture.customevents.DelayUpMessage getMessage();
     method public java.util.Set<androidx.compose.ui.input.pointer.PointerId> getPointers();
     method public void setMessage(androidx.compose.ui.gesture.customevents.DelayUpMessage p);
@@ -505,7 +502,7 @@
     property public final java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointers;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public enum DelayUpMessage {
+  public enum DelayUpMessage {
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayUp;
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayedUpConsumed;
     enum_constant public static final androidx.compose.ui.gesture.customevents.DelayUpMessage DelayedUpNotConsumed;
@@ -517,6 +514,37 @@
 
 }
 
+package androidx.compose.ui.gesture.nestedscroll {
+
+  public interface NestedScrollConnection {
+    method public default void onPostFling-Pv53iXo(long consumed, long available, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Velocity,kotlin.Unit> onFinished);
+    method public default long onPostScroll-l-UAZDg(long consumed, long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+    method public default long onPreFling-TH1AsA0(long available);
+    method public default long onPreScroll-vG6bCaM(long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+  }
+
+  public final class NestedScrollDelegatingWrapperKt {
+  }
+
+  public final class NestedScrollDispatcher {
+    ctor public NestedScrollDispatcher();
+    method public void dispatchPostFling-uYzo7IE(long consumed, long available);
+    method public long dispatchPostScroll-l-UAZDg(long consumed, long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+    method public long dispatchPreFling-TH1AsA0(long available);
+    method public long dispatchPreScroll-vG6bCaM(long available, androidx.compose.ui.gesture.nestedscroll.NestedScrollSource source);
+  }
+
+  public final class NestedScrollModifierKt {
+    method public static androidx.compose.ui.Modifier nestedScroll(androidx.compose.ui.Modifier, androidx.compose.ui.gesture.nestedscroll.NestedScrollConnection connection, optional androidx.compose.ui.gesture.nestedscroll.NestedScrollDispatcher? dispatcher);
+  }
+
+  public enum NestedScrollSource {
+    enum_constant public static final androidx.compose.ui.gesture.nestedscroll.NestedScrollSource Drag;
+    enum_constant public static final androidx.compose.ui.gesture.nestedscroll.NestedScrollSource Fling;
+  }
+
+}
+
 package androidx.compose.ui.gesture.scrollorientationlocking {
 
   public enum Orientation {
@@ -524,7 +552,7 @@
     enum_constant public static final androidx.compose.ui.gesture.scrollorientationlocking.Orientation Vertical;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public final class ScrollOrientationLocker {
+  public final class ScrollOrientationLocker {
     ctor public ScrollOrientationLocker(androidx.compose.ui.input.pointer.CustomEventDispatcher customEventDispatcher);
     method public void attemptToLockPointers(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation);
     method public java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> getPointersFor(java.util.List<androidx.compose.ui.input.pointer.PointerInputChange> changes, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation);
@@ -678,7 +706,8 @@
 
   public final class VectorApplier extends androidx.compose.runtime.AbstractApplier<androidx.compose.ui.graphics.vector.VNode> {
     ctor public VectorApplier(androidx.compose.ui.graphics.vector.VNode root);
-    method public void insert(int index, androidx.compose.ui.graphics.vector.VNode instance);
+    method public void insertBottomUp(int index, androidx.compose.ui.graphics.vector.VNode instance);
+    method public void insertTopDown(int index, androidx.compose.ui.graphics.vector.VNode instance);
     method public void move(int from, int to, int count);
     method protected void onClear();
     method public void remove(int index, int count);
@@ -1472,13 +1501,13 @@
     method public void retainHitPaths(java.util.Set<androidx.compose.ui.input.pointer.PointerId> pointerIds);
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput @kotlin.coroutines.RestrictsSuspension public interface HandlePointerInputScope extends androidx.compose.ui.unit.Density {
+  @kotlin.coroutines.RestrictsSuspension public interface HandlePointerInputScope extends androidx.compose.ui.unit.Density {
     method public suspend Object? awaitCustomEvent(optional androidx.compose.ui.input.pointer.PointerEventPass pass, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.CustomEvent> p);
     method public suspend Object? awaitPointerEvent(optional androidx.compose.ui.input.pointer.PointerEventPass pass, optional kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerEvent> p);
-    method public java.util.List<androidx.compose.ui.input.pointer.PointerInputData> getCurrentPointers();
+    method public androidx.compose.ui.input.pointer.PointerEvent getCurrentEvent();
     method public long getSize-YbymL2g();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
-    property public abstract java.util.List<androidx.compose.ui.input.pointer.PointerInputData> currentPointers;
+    property public abstract androidx.compose.ui.input.pointer.PointerEvent currentEvent;
     property public abstract long size;
     property public abstract androidx.compose.ui.platform.ViewConfiguration viewConfiguration;
   }
@@ -1600,7 +1629,7 @@
     property public abstract androidx.compose.ui.input.pointer.PointerInputFilter pointerInputFilter;
   }
 
-  @androidx.compose.ui.gesture.ExperimentalPointerInput public interface PointerInputScope extends androidx.compose.ui.unit.Density {
+  public interface PointerInputScope extends androidx.compose.ui.unit.Density {
     method public androidx.compose.ui.input.pointer.CustomEventDispatcher getCustomEventDispatcher();
     method public long getSize-YbymL2g();
     method public androidx.compose.ui.platform.ViewConfiguration getViewConfiguration();
@@ -1626,7 +1655,7 @@
   }
 
   public final class SuspendingPointerInputFilterKt {
-    method @androidx.compose.ui.gesture.ExperimentalPointerInput public static androidx.compose.ui.Modifier pointerInput(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static androidx.compose.ui.Modifier pointerInput(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
   }
 
 }
@@ -1785,15 +1814,31 @@
     property public abstract Object layoutId;
   }
 
+  public interface LayoutInfo {
+    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
+    method public int getHeight();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
+    method public androidx.compose.ui.layout.LayoutInfo? getParentInfo();
+    method public int getWidth();
+    method public boolean isAttached();
+    method public boolean isPlaced();
+    property public abstract androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public abstract int height;
+    property public abstract boolean isAttached;
+    property public abstract boolean isPlaced;
+    property public abstract androidx.compose.ui.layout.LayoutInfo? parentInfo;
+    property public abstract int width;
+  }
+
   public final class LayoutKt {
     method @androidx.compose.runtime.Composable public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @androidx.compose.runtime.Composable public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.node.ExperimentalLayoutNodeApi public static inline void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocks, optional androidx.compose.ui.Modifier modifier);
-    method public static androidx.compose.ui.node.LayoutNode.MeasureBlocks MeasuringIntrinsicsMeasureBlocks(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
+    method @androidx.compose.runtime.Composable public static inline void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> content, androidx.compose.ui.node.MeasureBlocks measureBlocks, optional androidx.compose.ui.Modifier modifier);
+    method public static androidx.compose.ui.node.MeasureBlocks MeasuringIntrinsicsMeasureBlocks(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @Deprecated @androidx.compose.runtime.Composable public static void MultiMeasureLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
     method @androidx.compose.runtime.Composable public static void WithConstraints(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.WithConstraintsScope,kotlin.Unit> content);
-    method @androidx.compose.ui.node.ExperimentalLayoutNodeApi @kotlin.PublishedApi internal static kotlin.jvm.functions.Function1<androidx.compose.runtime.SkippableUpdater<androidx.compose.ui.node.LayoutNode>,kotlin.Unit> materializerOf(androidx.compose.ui.Modifier modifier);
-    method @androidx.compose.ui.node.ExperimentalLayoutNodeApi public static androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocksOf(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
+    method @kotlin.PublishedApi internal static kotlin.jvm.functions.Function1<androidx.compose.runtime.SkippableUpdater<androidx.compose.ui.node.LayoutNode>,kotlin.Unit> materializerOf(androidx.compose.ui.Modifier modifier);
+    method public static androidx.compose.ui.node.MeasureBlocks measureBlocksOf(kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> minIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicWidthMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.IntrinsicMeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable>,? super java.lang.Integer,java.lang.Integer> maxIntrinsicHeightMeasureBlock, kotlin.jvm.functions.Function3<? super androidx.compose.ui.layout.MeasureScope,? super java.util.List<? extends androidx.compose.ui.layout.Measurable>,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measureBlock);
   }
 
   public interface LayoutModifier extends androidx.compose.ui.Modifier.Element {
@@ -1841,6 +1886,16 @@
     method public static inline String! toString-impl(androidx.compose.ui.layout.Placeable! p);
   }
 
+  public final class ModifierInfo {
+    ctor public ModifierInfo(androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.LayoutCoordinates coordinates, Object? extra);
+    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
+    method public Object? getExtra();
+    method public androidx.compose.ui.Modifier getModifier();
+    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public final Object? extra;
+    property public final androidx.compose.ui.Modifier modifier;
+  }
+
   public interface OnGloballyPositionedModifier extends androidx.compose.ui.Modifier.Element {
     method public void onGloballyPositioned(androidx.compose.ui.layout.LayoutCoordinates coordinates);
   }
@@ -1967,9 +2022,6 @@
 
 package androidx.compose.ui.node {
 
-  @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level, message="This is an experimental API for Compose UI LayoutNode and is likely to change " + "before becoming stable.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalLayoutNodeApi {
-  }
-
   @kotlin.RequiresOptIn(message="This API is internal to library.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface InternalCoreApi {
   }
 
@@ -1977,43 +2029,31 @@
     method public kotlin.jvm.functions.Function0<androidx.compose.ui.node.LayoutNode> getConstructor();
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.unit.Density,kotlin.Unit> getSetDensity();
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.unit.LayoutDirection,kotlin.Unit> getSetLayoutDirection();
-    method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.node.LayoutNode.MeasureBlocks,kotlin.Unit> getSetMeasureBlocks();
+    method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.node.MeasureBlocks,kotlin.Unit> getSetMeasureBlocks();
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.Modifier,kotlin.Unit> getSetModifier();
     method public kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.node.Ref<androidx.compose.ui.node.LayoutNode>,kotlin.Unit> getSetRef();
     property public final kotlin.jvm.functions.Function0<androidx.compose.ui.node.LayoutNode> constructor;
     property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.unit.Density,kotlin.Unit> setDensity;
     property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.unit.LayoutDirection,kotlin.Unit> setLayoutDirection;
-    property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.node.LayoutNode.MeasureBlocks,kotlin.Unit> setMeasureBlocks;
+    property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.node.MeasureBlocks,kotlin.Unit> setMeasureBlocks;
     property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.Modifier,kotlin.Unit> setModifier;
     property public final kotlin.jvm.functions.Function2<androidx.compose.ui.node.LayoutNode,androidx.compose.ui.node.Ref<androidx.compose.ui.node.LayoutNode>,kotlin.Unit> setRef;
   }
 
-  @androidx.compose.ui.node.ExperimentalLayoutNodeApi public final class LayoutNode implements androidx.compose.ui.layout.Measurable androidx.compose.ui.node.OwnerScope androidx.compose.ui.layout.Remeasurement {
+  public final class LayoutNode implements androidx.compose.ui.layout.LayoutInfo androidx.compose.ui.layout.Measurable androidx.compose.ui.node.OwnerScope androidx.compose.ui.layout.Remeasurement {
     ctor public LayoutNode();
-    method public void attach(androidx.compose.ui.node.Owner owner);
-    method public void detach();
-    method public void draw(androidx.compose.ui.graphics.Canvas canvas);
     method public void forceRemeasure();
-    method public Integer? getAlignmentLine(androidx.compose.ui.layout.AlignmentLine line);
-    method @Deprecated public boolean getCanMultiMeasure();
     method public java.util.List<androidx.compose.ui.node.LayoutNode> getChildren();
     method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
-    method public androidx.compose.ui.unit.Density getDensity();
-    method public int getDepth();
     method public int getHeight();
-    method public androidx.compose.ui.node.LayoutNode.MeasureBlocks getMeasureBlocks();
-    method public androidx.compose.ui.layout.MeasureScope getMeasureScope();
     method public androidx.compose.ui.Modifier getModifier();
-    method public java.util.List<androidx.compose.ui.node.ModifierInfo> getModifierInfo();
-    method public kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? getOnAttach();
-    method public kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? getOnDetach();
+    method public java.util.List<androidx.compose.ui.layout.ModifierInfo> getModifierInfo();
     method public androidx.compose.ui.node.Owner? getOwner();
     method public androidx.compose.ui.node.LayoutNode? getParent();
     method public Object? getParentData();
+    method public androidx.compose.ui.layout.LayoutInfo? getParentInfo();
     method public int getWidth();
-    method public void hitTest-fhABUH0(long pointerPositionRelativeToScreen, java.util.List<androidx.compose.ui.input.pointer.PointerInputFilter> hitPointerInputFilters);
-    method public void ignoreModelReads(kotlin.jvm.functions.Function0<kotlin.Unit> block);
-    method public void insertAt(int index, androidx.compose.ui.node.LayoutNode instance);
+    method public boolean isAttached();
     method public boolean isPlaced();
     method public boolean isValid();
     method public int maxIntrinsicHeight(int width);
@@ -2021,39 +2061,25 @@
     method public androidx.compose.ui.layout.Placeable measure-BRTryo0(long constraints);
     method public int minIntrinsicHeight(int width);
     method public int minIntrinsicWidth(int height);
-    method public void move(int from, int to, int count);
-    method public void place(int x, int y);
-    method public void removeAll();
-    method public void removeAt(int index, int count);
-    method public void requestRelayout();
-    method public void requestRemeasure();
-    method @Deprecated public void setCanMultiMeasure(boolean p);
-    method public void setDensity(androidx.compose.ui.unit.Density p);
-    method public void setDepth(int p);
-    method public void setMeasureBlocks(androidx.compose.ui.node.LayoutNode.MeasureBlocks value);
     method public void setModifier(androidx.compose.ui.Modifier value);
-    method public void setOnAttach(kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.Owner,kotlin.Unit>? p);
-    method public void setOnDetach(kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.Owner,kotlin.Unit>? p);
-    property @Deprecated public final boolean canMultiMeasure;
     property public final java.util.List<androidx.compose.ui.node.LayoutNode> children;
-    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
-    property public final androidx.compose.ui.unit.Density density;
-    property public final int depth;
-    property public final int height;
-    property public final boolean isPlaced;
+    property public androidx.compose.ui.layout.LayoutCoordinates coordinates;
+    property public int height;
+    property public boolean isAttached;
+    property public boolean isPlaced;
     property public boolean isValid;
-    property public final androidx.compose.ui.node.LayoutNode.MeasureBlocks measureBlocks;
-    property public final androidx.compose.ui.layout.MeasureScope measureScope;
     property public final androidx.compose.ui.Modifier modifier;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? onAttach;
-    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.node.Owner,kotlin.Unit>? onDetach;
     property public final androidx.compose.ui.node.Owner? owner;
     property public final androidx.compose.ui.node.LayoutNode? parent;
     property public Object? parentData;
-    property public final int width;
+    property public androidx.compose.ui.layout.LayoutInfo? parentInfo;
+    property public int width;
   }
 
-  public static interface LayoutNode.MeasureBlocks {
+  public final class LayoutNodeKt {
+  }
+
+  public interface MeasureBlocks {
     method public int maxIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
     method public int maxIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
     method public androidx.compose.ui.layout.MeasureResult measure-8A2P9vY(androidx.compose.ui.layout.MeasureScope measureScope, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables, long constraints);
@@ -2061,28 +2087,6 @@
     method public int minIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
   }
 
-  public abstract static class LayoutNode.NoIntrinsicsMeasureBlocks implements androidx.compose.ui.node.LayoutNode.MeasureBlocks {
-    ctor public LayoutNode.NoIntrinsicsMeasureBlocks(String error);
-    method public Void maxIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
-    method public Void maxIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
-    method public Void minIntrinsicHeight(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int w);
-    method public Void minIntrinsicWidth(androidx.compose.ui.layout.IntrinsicMeasureScope intrinsicMeasureScope, java.util.List<? extends androidx.compose.ui.layout.IntrinsicMeasurable> measurables, int h);
-  }
-
-  public final class LayoutNodeKt {
-    method public static androidx.compose.ui.node.LayoutNode? findClosestParentNode(androidx.compose.ui.node.LayoutNode, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.LayoutNode,java.lang.Boolean> selector);
-  }
-
-  public final class ModifierInfo {
-    ctor public ModifierInfo(androidx.compose.ui.Modifier modifier, androidx.compose.ui.layout.LayoutCoordinates coordinates, Object? extra);
-    method public androidx.compose.ui.layout.LayoutCoordinates getCoordinates();
-    method public Object? getExtra();
-    method public androidx.compose.ui.Modifier getModifier();
-    property public final androidx.compose.ui.layout.LayoutCoordinates coordinates;
-    property public final Object? extra;
-    property public final androidx.compose.ui.Modifier modifier;
-  }
-
   public interface OwnedLayer {
     method public void destroy();
     method public void drawLayer(androidx.compose.ui.graphics.Canvas canvas);
@@ -2106,7 +2110,6 @@
     method public androidx.compose.ui.focus.FocusManager getFocusManager();
     method public androidx.compose.ui.text.font.Font.ResourceLoader getFontLoader();
     method public androidx.compose.ui.hapticfeedback.HapticFeedback getHapticFeedBack();
-    method public boolean getHasPendingMeasureOrLayout();
     method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
     method public long getMeasureIteration();
     method public androidx.compose.ui.node.LayoutNode getRoot();
@@ -2132,7 +2135,6 @@
     property public abstract androidx.compose.ui.focus.FocusManager focusManager;
     property public abstract androidx.compose.ui.text.font.Font.ResourceLoader fontLoader;
     property public abstract androidx.compose.ui.hapticfeedback.HapticFeedback hapticFeedBack;
-    property public abstract boolean hasPendingMeasureOrLayout;
     property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
     property public abstract long measureIteration;
     property public abstract androidx.compose.ui.node.LayoutNode root;
@@ -2168,16 +2170,13 @@
     property public final T? value;
   }
 
-  public final class UiApplier implements androidx.compose.runtime.Applier<java.lang.Object> {
+  public final class UiApplier extends androidx.compose.runtime.AbstractApplier<java.lang.Object> {
     ctor public UiApplier(Object root);
-    method public void clear();
-    method public void down(Object node);
-    method public Object getCurrent();
-    method public void insert(int index, Object instance);
+    method public void insertBottomUp(int index, Object instance);
+    method public void insertTopDown(int index, Object instance);
     method public void move(int from, int to, int count);
+    method protected void onClear();
     method public void remove(int index, int count);
-    method public void up();
-    property public Object current;
   }
 
   public final class ViewInteropKt {
@@ -2248,31 +2247,6 @@
   public final class AndroidComposeViewKt {
   }
 
-  public interface AndroidOwner extends androidx.compose.ui.node.Owner {
-    method @androidx.compose.ui.node.ExperimentalLayoutNodeApi public void addAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view, androidx.compose.ui.node.LayoutNode layoutNode);
-    method public void drawAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view, android.graphics.Canvas canvas);
-    method public kotlin.jvm.functions.Function1<android.content.res.Configuration,kotlin.Unit> getConfigurationChangeObserver();
-    method public android.view.View getView();
-    method public androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners? getViewTreeOwners();
-    method public void invalidateDescendants();
-    method public void removeAndroidView(androidx.compose.ui.viewinterop.AndroidViewHolder view);
-    method public void setConfigurationChangeObserver(kotlin.jvm.functions.Function1<? super android.content.res.Configuration,kotlin.Unit> p);
-    method public void setOnViewTreeOwnersAvailable(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners,kotlin.Unit> callback);
-    property public abstract kotlin.jvm.functions.Function1<android.content.res.Configuration,kotlin.Unit> configurationChangeObserver;
-    property public abstract android.view.View view;
-    property public abstract androidx.compose.ui.platform.AndroidOwner.ViewTreeOwners? viewTreeOwners;
-  }
-
-  public static final class AndroidOwner.ViewTreeOwners {
-    ctor public AndroidOwner.ViewTreeOwners(androidx.lifecycle.LifecycleOwner lifecycleOwner, androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, androidx.savedstate.SavedStateRegistryOwner savedStateRegistryOwner);
-    method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
-    method public androidx.savedstate.SavedStateRegistryOwner getSavedStateRegistryOwner();
-    method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner();
-    property public final androidx.lifecycle.LifecycleOwner lifecycleOwner;
-    property public final androidx.savedstate.SavedStateRegistryOwner savedStateRegistryOwner;
-    property public final androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner;
-  }
-
   public final class AndroidUriHandler implements androidx.compose.ui.platform.UriHandler {
     ctor public AndroidUriHandler(android.content.Context context);
     method public void openUri(String uri);
@@ -2351,10 +2325,6 @@
   public final class JvmActualsKt {
   }
 
-  public final class SubcompositionKt {
-    method @MainThread public static androidx.compose.runtime.Composition subcomposeInto(androidx.compose.ui.node.LayoutNode container, androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-  }
-
   public final class TestTagKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier testTag(androidx.compose.ui.Modifier, String tag);
   }
@@ -2406,6 +2376,23 @@
     property public abstract float touchSlop;
   }
 
+  @VisibleForTesting public interface ViewRootForTest extends androidx.compose.ui.node.Owner {
+    method public boolean getHasPendingMeasureOrLayout();
+    method public android.view.View getView();
+    method public void invalidateDescendants();
+    method public boolean isLifecycleInResumedState();
+    property public abstract boolean hasPendingMeasureOrLayout;
+    property public abstract boolean isLifecycleInResumedState;
+    property public abstract android.view.View view;
+    field public static final androidx.compose.ui.platform.ViewRootForTest.Companion Companion;
+  }
+
+  public static final class ViewRootForTest.Companion {
+    method public kotlin.jvm.functions.Function1<androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? getOnViewCreatedCallback();
+    method public void setOnViewCreatedCallback(kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? p);
+    property public final kotlin.jvm.functions.Function1<androidx.compose.ui.platform.ViewRootForTest,kotlin.Unit>? onViewCreatedCallback;
+  }
+
   @androidx.compose.runtime.Stable public interface WindowManager {
     method public boolean isWindowFocused();
     property public abstract boolean isWindowFocused;
@@ -2463,6 +2450,10 @@
     ctor public LoadedResource(T? resource);
   }
 
+  public final class PainterResourcesKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.painter.Painter painterResource(@DrawableRes int id);
+  }
+
   public final class PendingResource<T> extends androidx.compose.ui.res.Resource<T> {
     ctor public PendingResource(T? resource);
   }
@@ -2497,7 +2488,7 @@
 
 package androidx.compose.ui.selection {
 
-  public interface Selectable {
+  @androidx.compose.ui.text.ExperimentalTextApi public interface Selectable {
     method public androidx.compose.ui.geometry.Rect getBoundingBox(int offset);
     method public long getHandlePosition-F1C5BW0(androidx.compose.ui.selection.Selection selection, boolean isStartHandle);
     method public androidx.compose.ui.layout.LayoutCoordinates? getLayoutCoordinates();
@@ -2547,9 +2538,11 @@
   public final class SelectionManagerKt {
   }
 
-  public interface SelectionRegistrar {
-    method public void onPositionChange();
-    method public void onUpdateSelection-rULFVbc(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition, long endPosition);
+  @androidx.compose.ui.text.ExperimentalTextApi public interface SelectionRegistrar {
+    method public void notifyPositionChange();
+    method public void notifySelectionUpdate-rULFVbc(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition, long endPosition);
+    method public void notifySelectionUpdateEnd();
+    method public void notifySelectionUpdateStart-YJiYy8w(androidx.compose.ui.layout.LayoutCoordinates layoutCoordinates, long startPosition);
     method public androidx.compose.ui.selection.Selectable subscribe(androidx.compose.ui.selection.Selectable selectable);
     method public void unsubscribe(androidx.compose.ui.selection.Selectable selectable);
   }
@@ -2663,12 +2656,13 @@
     method public operator <T> T! get(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key);
     method public <T> T! getOrElse(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
     method public <T> T? getOrElseNullable(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
-    method public boolean isEmpty();
+    method public boolean isClearingSemantics();
     method public boolean isMergingSemanticsOfDescendants();
     method public java.util.Iterator<java.util.Map.Entry<androidx.compose.ui.semantics.SemanticsPropertyKey<?>,java.lang.Object>> iterator();
     method public <T> void set(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, T? value);
+    method public void setClearingSemantics(boolean p);
     method public void setMergingSemanticsOfDescendants(boolean p);
-    property public final boolean isEmpty;
+    property public final boolean isClearingSemantics;
     property public final boolean isMergingSemanticsOfDescendants;
   }
 
@@ -2684,6 +2678,7 @@
   }
 
   public final class SemanticsModifierKt {
+    method public static androidx.compose.ui.Modifier clearAndSetSemantics(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
     method public static androidx.compose.ui.Modifier semantics(androidx.compose.ui.Modifier, optional boolean mergeDescendants, kotlin.jvm.functions.Function1<? super androidx.compose.ui.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
   }
 
@@ -2691,25 +2686,27 @@
     method public int getAlignmentLinePosition(androidx.compose.ui.layout.AlignmentLine line);
     method public androidx.compose.ui.geometry.Rect getBoundsInRoot();
     method public java.util.List<androidx.compose.ui.semantics.SemanticsNode> getChildren();
-    method public androidx.compose.ui.node.LayoutNode getComponentNode();
     method public androidx.compose.ui.semantics.SemanticsConfiguration getConfig();
     method public androidx.compose.ui.geometry.Rect getGlobalBounds();
     method public long getGlobalPosition-F1C5BW0();
     method public int getId();
+    method public androidx.compose.ui.layout.LayoutInfo getLayoutInfo();
     method public boolean getMergingEnabled();
+    method public androidx.compose.ui.node.Owner? getOwner();
     method public androidx.compose.ui.semantics.SemanticsNode? getParent();
     method public long getPositionInRoot-F1C5BW0();
     method public long getSize-YbymL2g();
     method public boolean isRoot();
     property public final androidx.compose.ui.geometry.Rect boundsInRoot;
     property public final java.util.List<androidx.compose.ui.semantics.SemanticsNode> children;
-    property public final androidx.compose.ui.node.LayoutNode componentNode;
     property public final androidx.compose.ui.semantics.SemanticsConfiguration config;
     property public final androidx.compose.ui.geometry.Rect globalBounds;
     property public final long globalPosition;
     property public final int id;
     property public final boolean isRoot;
+    property public final androidx.compose.ui.layout.LayoutInfo layoutInfo;
     property public final boolean mergingEnabled;
+    property public final androidx.compose.ui.node.Owner? owner;
     property public final androidx.compose.ui.semantics.SemanticsNode? parent;
     property public final long positionInRoot;
     property public final long size;
@@ -2719,7 +2716,6 @@
   }
 
   public final class SemanticsOwner {
-    ctor public SemanticsOwner(androidx.compose.ui.node.LayoutNode rootNode);
     method public androidx.compose.ui.node.LayoutNode getRootNode();
     method public androidx.compose.ui.semantics.SemanticsNode getRootSemanticsNode();
     method public androidx.compose.ui.semantics.SemanticsNode getUnmergedRootSemanticsNode();
@@ -2894,11 +2890,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class AndroidDialogProperties implements androidx.compose.ui.window.DialogProperties {
-    ctor public AndroidDialogProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    ctor public AndroidDialogProperties(boolean dismissOnBackPress, boolean dismissOnClickOutside, androidx.compose.ui.window.SecureFlagPolicy securePolicy);
     ctor public AndroidDialogProperties();
-    method public androidx.compose.ui.window.SecureFlagPolicy component1();
-    method @androidx.compose.runtime.Immutable public androidx.compose.ui.window.AndroidDialogProperties copy(androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    method public boolean component1();
+    method public boolean component2();
+    method public androidx.compose.ui.window.SecureFlagPolicy component3();
+    method @androidx.compose.runtime.Immutable public androidx.compose.ui.window.AndroidDialogProperties copy(boolean dismissOnBackPress, boolean dismissOnClickOutside, androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    method public boolean getDismissOnBackPress();
+    method public boolean getDismissOnClickOutside();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    property public final boolean dismissOnBackPress;
+    property public final boolean dismissOnClickOutside;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/build.gradle b/compose/ui/ui/integration-tests/ui-demos/build.gradle
index eb67fb8..9a5536b 100644
--- a/compose/ui/ui/integration-tests/ui-demos/build.gradle
+++ b/compose/ui/ui/integration-tests/ui-demos/build.gradle
@@ -18,6 +18,7 @@
     implementation project(":compose:foundation:foundation")
     implementation project(":compose:foundation:foundation-layout")
     implementation project(":compose:integration-tests:demos:common")
+    implementation project(":compose:ui:ui:ui-samples")
     implementation project(":compose:material:material")
     implementation project(":compose:runtime:runtime")
     implementation project(":compose:runtime:runtime-livedata")
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
index 021cf64..5f74c0c 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
@@ -45,6 +45,7 @@
 import androidx.compose.integration.demos.common.DemoCategory
 import androidx.compose.ui.demos.focus.FocusInDialog
 import androidx.compose.ui.demos.focus.FocusInPopup
+import androidx.compose.ui.samples.NestedScrollSample
 
 private val GestureDemos = DemoCategory(
     "Gestures",
@@ -87,7 +88,8 @@
                 ComposableDemo("Nested Long Press") { NestedLongPressDemo() },
                 ComposableDemo("Pointer Input During Sub Comp") { PointerInputDuringSubComp() }
             )
-        )
+        ),
+        ComposableDemo("New nested scroll") { NestedScrollSample() }
     )
 )
 
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
index cbae372..5500315 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
@@ -22,14 +22,23 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.platform.AmbientContext
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.ContextCompat
+import kotlin.math.roundToInt
 
 @Suppress("SetTextI18n")
 @Sampled
@@ -43,3 +52,20 @@
         view.layoutParams = ViewGroup.LayoutParams(size, size)
     }
 }
+
+@Sampled
+@Composable
+fun AndroidDrawableInDrawScopeSample() {
+    val drawable = ContextCompat.getDrawable(AmbientContext.current, R.drawable.sample_drawable)
+    Box(
+        modifier = Modifier.size(100.dp)
+            .drawBehind {
+                drawIntoCanvas { canvas ->
+                    drawable?.let {
+                        it.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+                        it.draw(canvas.nativeCanvas)
+                    }
+                }
+            }
+    )
+}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt
index 42e5856..9beccc6 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt
@@ -27,7 +27,6 @@
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.layout.measureBlocksOf
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.offset
 
@@ -79,7 +78,6 @@
 
 @Sampled
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class)
 fun LayoutWithMeasureBlocksWithIntrinsicUsage(content: @Composable () -> Unit) {
     val measureBlocks = measureBlocksOf(
         minIntrinsicWidthMeasureBlock = { measurables, h ->
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollSamples.kt
new file mode 100644
index 0000000..6b4dccc
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollSamples.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.gesture.ScrollCallback
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollSource
+import androidx.compose.ui.gesture.nestedscroll.nestedScroll
+import androidx.compose.ui.gesture.scrollGestureFilter
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.minus
+import kotlin.math.roundToInt
+
+@Sampled
+@Composable
+fun NestedScrollSample() {
+    // constructing the box with next that scrolls as long as text within 0 .. 300
+    // to support nested scrolling, we need to scroll ourselves, dispatch nested scroll events
+    // as we scroll, and listen to potential children when we're scrolling.
+    val maxValue = 300f
+    val minValue = 0f
+    // our state that we update as scroll
+    var value by remember { mutableStateOf(maxValue / 2) }
+    // create dispatch to dispatch scroll events up to the nested scroll parents
+    val nestedScrollDispatcher = remember { NestedScrollDispatcher() }
+    // we're going to scroll vertically, so set the orientation to vertical
+    val orientation = Orientation.Vertical
+
+    // callback to listen to scroll events and dispatch nested scroll events
+    val scrollCallback = remember {
+        object : ScrollCallback {
+            override fun onScroll(scrollDistance: Float): Float {
+                // dispatch prescroll with Y axis since we're going vertical scroll
+                val aboveConsumed = nestedScrollDispatcher.dispatchPreScroll(
+                    Offset(x = 0f, y = scrollDistance),
+                    NestedScrollSource.Drag
+                )
+                // adjust what we can consume according to pre-scroll
+                val available = scrollDistance - aboveConsumed.y
+                // let's calculate how much we want to consume and how much is left
+                val newTotal = value + available
+                val newValue = newTotal.coerceIn(minValue, maxValue)
+                val toConsume = newValue - value
+                val leftAfterUs = available - toConsume
+                // consume ourselves what we need and dispatch "scroll" phase of nested scroll
+                value += toConsume
+                nestedScrollDispatcher.dispatchPostScroll(
+                    Offset(x = 0f, y = toConsume),
+                    Offset(x = 0f, y = leftAfterUs),
+                    NestedScrollSource.Drag
+                )
+                // indicate to the old pointer that we handled everything by returning same value
+                return scrollDistance
+            }
+
+            override fun onStop(velocity: Float) {
+                // for simplicity we won't fling ourselves, but we need to respect nested scroll
+                // dispatch pre fling
+                val velocity2d = Velocity(Offset(x = 0f, y = velocity))
+                val consumed = nestedScrollDispatcher.dispatchPreFling(velocity2d)
+                // now, since we don't fling, we consume 0 (Offset.Zero).
+                // Adjust what's left after prefling and dispatch post fling
+                val left = velocity2d - consumed
+                nestedScrollDispatcher.dispatchPostFling(Velocity.Zero, left)
+            }
+        }
+    }
+
+    // we also want to participate in the nested scrolling, not only dispatching. create connection
+    val connection = remember {
+        object : NestedScrollConnection {
+            // let's assume we want to consume children's delta before them if we can
+            // we should do it in pre scroll
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                // calculate how much we can take from child
+                val oldValue = value
+                val newTotal = value + available.y
+                val newValue = newTotal.coerceIn(minValue, maxValue)
+                val toConsume = newValue - oldValue
+                // consume what we want and report back co children can adjust
+                value += toConsume
+                return Offset(x = 0f, y = toConsume)
+            }
+        }
+    }
+
+    // scrollable parent to which we will dispatch our nested scroll events
+    // Since we properly support scrolling above, this parent will scroll even if we scroll inner
+    // box (with White background)
+    LazyColumn(Modifier.background(Color.Red)) {
+        // our box we constructed
+        item {
+            Box(
+                Modifier
+                    .size(width = 300.dp, height = 100.dp)
+                    .background(Color.White)
+                    // add scrolling listening and dispatching
+                    .scrollGestureFilter(orientation = orientation, scrollCallback = scrollCallback)
+                    // connect self connection and dispatcher to the nested scrolling system
+                    .nestedScroll(connection, dispatcher = nestedScrollDispatcher)
+            ) {
+                // hypothetical scrollable child which we will listen in connection above
+                LazyColumnFor(listOf(1, 2, 3, 4, 5)) {
+                    Text(
+                        "Magenta text above will change first when you scroll me",
+                        modifier = Modifier.padding(5.dp)
+                    )
+                }
+                // simply show our value. It will change when we scroll child list above, taking
+                // child's scroll delta until we reach maxValue or minValue
+                Text(
+                    text = value.roundToInt().toString(),
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .background(Color.Magenta)
+                )
+            }
+        }
+        repeat(100) {
+            item {
+                Text(
+                    "Outer scroll items are Yellow on Red parent",
+                    modifier = Modifier.background(Color.Yellow).padding(5.dp)
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt
index ef4205e..dfce282 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/PainterSample.kt
@@ -17,9 +17,11 @@
 package androidx.compose.ui.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.paint
@@ -27,6 +29,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.unit.dp
 
 @Sampled
@@ -53,4 +56,16 @@
                 .background(color = Color.Yellow)
                 .paint(CustomPainter())
     ) { /** intentionally empty **/ }
+}
+
+@Sampled
+@Composable
+fun PainterResourceSample() {
+    // Sample showing how to render a Painter based on a different resource (vector vs png)
+    // Here a Vector asset is used in the portrait orientation, however, a png is used instead
+    // in the landscape orientation based on the res/drawable and res/drawable-land-hdpi folders
+    Image(
+        painterResource(R.drawable.ic_vector_or_png),
+        modifier = Modifier.size(50.dp)
+    )
 }
\ No newline at end of file
diff --git a/compose/ui/ui/samples/src/main/res/drawable-land-hdpi/ic_vector_or_png.png b/compose/ui/ui/samples/src/main/res/drawable-land-hdpi/ic_vector_or_png.png
new file mode 100755
index 0000000..4696f99
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/res/drawable-land-hdpi/ic_vector_or_png.png
Binary files differ
diff --git a/compose/ui/ui/samples/src/main/res/drawable/ic_vector_or_png.xml b/compose/ui/ui/samples/src/main/res/drawable/ic_vector_or_png.xml
new file mode 100644
index 0000000..64452ad
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/res/drawable/ic_vector_or_png.xml
@@ -0,0 +1,36 @@
+<!--
+  ~ 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="200dp" android:viewportHeight="144"
+    android:viewportWidth="144" android:width="200dp"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#F79D80" android:pathData="M69.26,55.73m-53.39,0a53.39,53.39 0,1 1,106.78 0a53.39,53.39 0,1 1,-106.78 0"/>
+    <path android:fillColor="#37474F" android:pathData="M47.66,76.35h2.26v65.31h-2.26z"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M61.47,22.88l7.59,0.63C69.06,23.51 65.07,22.59 61.47,22.88z"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M78.86,44.71c-6.8,-5.5 -22.66,-7.48 -22.99,-17.32c-0.11,-3.17 2.61,-4.26 5.6,-4.5l-11.02,-0.92c-3.05,9.16 1.58,18.08 14.69,24.18c10.96,5.1 6.86,12.67 3.64,16.47c-3.13,-3.32 -7.57,-5.4 -12.49,-5.4c-0.78,0 -1.54,0.06 -2.29,0.16c-6.08,0.37 -39.94,5.23 -35.41,67.19c0,0 56.87,-40.62 56.95,-40.68c4.87,-3.48 9.11,-8.88 11.13,-14.5C89.88,60.43 86.05,50.53 78.86,44.71z"/>
+    <path android:fillColor="#434343" android:pathData="M68.8,62.41c-3.13,-3.32 -7.57,-5.4 -12.49,-5.4c-0.78,0 -1.54,0.06 -2.29,0.16c-6.08,0.37 -39.94,5.23 -35.41,67.19c0,0 1.34,-0.96 3.61,-2.58c-2.04,-56.41 29.85,-60.98 35.73,-61.34c0.75,-0.1 1.51,-0.16 2.29,-0.16C65.16,60.29 66.58,61.23 68.8,62.41"/>
+    <path android:fillColor="#FFD54F" android:pathData="M83.23,22.46l20.76,1.43c0.4,0.03 0.52,-0.52 0.16,-0.67l-19.1,-7.78c-0.2,-0.08 -0.42,0.03 -0.47,0.24l-1.66,6.35C82.86,22.23 83.01,22.44 83.23,22.46z"/>
+    <path android:fillColor="#F9BF2C" android:pathData="M86.66,16.09l-1.62,-0.66c-0.2,-0.08 -0.42,0.03 -0.47,0.24l-1.66,6.36c-0.06,0.21 0.1,0.42 0.31,0.44l10.52,0.72L86.66,16.09z"/>
+    <path android:fillColor="#1B2428" android:pathData="M47.66,103.79l0,3.86l2.24,2.24l0,-7.7z"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M71.76,7.67c-5.16,-0.35 -9.83,0.75 -12.82,2.74l0,0c-7.5,4.2 -9.01,13.44 -9.18,14.36c-0.18,0.93 3.05,2.01 3.05,2.01l4.12,-4.16l4.52,-0.06c2.52,1.27 5.61,2.55 9.09,2.79c8.62,0.59 15.88,-2.89 16.22,-7.77C87.1,12.7 80.38,8.26 71.76,7.67z"/>
+    <path android:fillColor="#881A51" android:pathData="M71.76,7.67c-5.16,-0.35 -9.83,0.75 -12.82,2.74l0,0c-7.5,4.2 -9.01,13.44 -9.18,14.36c-0.18,0.93 3.05,2.01 3.05,2.01l4.12,-4.16l4.67,-0.38c2.52,1.27 7.02,1.03 10.5,1.27c8.62,0.59 14.33,-1.05 14.66,-5.93C87.1,12.7 80.38,8.26 71.76,7.67z"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M71.83,11.32c-5.16,-0.29 -9.83,0.61 -12.82,2.21l0,0c-7.5,3.39 -9.16,10.46 -9.25,11.23c-0.15,1.18 3.05,3.13 3.05,3.13l4.2,-4.48l4.47,-0.54c2.52,1.03 5.59,2.28 9.07,2.47c8.62,0.48 15.08,-2.54 15.41,-6.49C86.29,14.92 80.45,11.8 71.83,11.32z"/>
+    <path android:fillColor="#BDBDBD" android:pathData="M73.53,24.78c-3.48,-0.19 -6.55,-1.44 -9.07,-2.47c0,0 0,0 0,0l-1.08,0.13c-0.68,0.11 -1.33,0.28 -1.91,0.52l0.11,-0.01c0,0 0,0 0,0c2.52,1.03 5.59,2.28 9.07,2.47c3.37,0.19 6.4,-0.16 8.87,-0.89C77.7,24.8 75.68,24.9 73.53,24.78z"/>
+    <path android:fillColor="#EEEEEE" android:pathData="M82.13,48.13c1.75,4.57 2.05,9.63 0.33,14.43c-2.02,5.62 -6.26,11.01 -11.13,14.5c-0.33,0.24 -41.01,29.29 -53.34,38.1c0.03,2.98 0.16,6.12 0.41,9.41c0,0 56.87,-40.62 56.95,-40.68c4.87,-3.48 9.11,-8.88 11.13,-14.5C89.13,62.01 86.99,53.98 82.13,48.13z"/>
+    <path android:fillColor="#BDBDBD" android:pathData="M78.86,44.91c-0.16,-0.13 -0.33,-0.26 -0.5,-0.39c5.68,5.93 8.39,14.63 5.53,22.58c-2.02,5.62 -6.26,11.01 -11.13,14.5c-0.07,0.05 -43.58,31.16 -54.59,39.02c0.06,1.4 0.15,2.8 0.26,4.26c0,0 57.03,-40.74 57.11,-40.79c4.87,-3.48 9.11,-8.88 11.13,-14.5C89.88,60.64 86.05,50.73 78.86,44.91z"/>
+    <path android:fillColor="#BDBDBD" android:pathData="M78.94,44.76c-6.8,-5.5 -22.66,-7.48 -22.99,-17.32c-0.04,-1.21 0.33,-2.11 0.97,-2.78c-1.81,0.6 -3.12,1.8 -3.04,4.02C54.2,38.52 70.07,40.5 76.86,46c7.19,5.82 11.02,15.73 7.81,24.68c-2.02,5.62 -6.26,11.01 -11.13,14.5c-0.07,0.05 -44.69,31.92 -54.89,39.21c0.01,0.08 0.01,0.16 0.02,0.24c0,0 56.87,-40.62 56.95,-40.68c4.87,-3.48 9.11,-8.88 11.13,-14.5C89.96,60.49 86.13,50.59 78.94,44.76z"/>
+    <path android:fillColor="#BDBDBD" android:pathData="M54.5,28.8c0,0 -0.52,0.61 -0.52,0.61c0.01,-0.01 -0.02,-0.11 -0.02,-0.12c-0.02,-0.11 -0.03,-0.23 -0.05,-0.34c-0.04,-0.4 -0.06,-0.79 -0.04,-1.19c0.02,-0.55 0.1,-1.1 0.24,-1.64c0.17,-0.63 0.43,-1.24 0.78,-1.79c0.42,-0.65 0.96,-1.21 1.58,-1.66c0.32,-0.23 0.66,-0.43 1.01,-0.61c0.47,-0.24 0.93,-0.43 1.46,-0.49c0.27,-0.03 0.55,-0.05 0.82,-0.05c0.89,-0.02 1.78,0.07 2.65,0.24c0.37,0.07 0.73,0.16 1.09,0.26c0.19,0.05 0.39,0.11 0.58,0.17c0.1,0.03 0.2,0.07 0.3,0.1c0.04,0.01 0.27,0.13 0.3,0.11c0,0 -1.79,1.07 -1.79,1.07s-5.78,-1.34 -6.91,3.25L54.5,28.8z"/>
+</vector>
diff --git a/car/app/app/src/androidTest/AndroidManifest.xml b/compose/ui/ui/samples/src/main/res/drawable/sample_drawable.xml
similarity index 68%
copy from car/app/app/src/androidTest/AndroidManifest.xml
copy to compose/ui/ui/samples/src/main/res/drawable/sample_drawable.xml
index 3bc2684..68053bc 100644
--- a/car/app/app/src/androidTest/AndroidManifest.xml
+++ b/compose/ui/ui/samples/src/main/res/drawable/sample_drawable.xml
@@ -14,6 +14,11 @@
   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.car.app">
-</manifest>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+  <gradient
+      android:startColor="@android:color/holo_red_dark"
+      android:centerColor="@android:color/holo_orange_dark"
+      android:endColor="@android:color/holo_blue_dark" />
+</shape>
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 6219052..8fb5451 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -38,7 +38,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
 import androidx.compose.ui.platform.testTag
@@ -59,7 +58,6 @@
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.TextFieldValue
-import androidx.core.os.BuildCompat
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -92,7 +90,6 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(
     ExperimentalFoundationApi::class,
-    ExperimentalLayoutNodeApi::class
 )
 class AndroidAccessibilityTest {
     @get:Rule
@@ -176,7 +173,7 @@
         var accessibilityNodeInfo = provider.createAccessibilityNodeInfo(toggleableNode.id)
         assertEquals("android.view.View", accessibilityNodeInfo.className)
         val stateDescription = when {
-            BuildCompat.isAtLeastR() -> {
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
                 accessibilityNodeInfo.stateDescription
             }
             Build.VERSION.SDK_INT >= 19 -> {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index 0b258d2..4b77ed4 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -23,7 +23,6 @@
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.FrameLayout
 import androidx.activity.ComponentActivity
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.InnerPlaceable
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.platform.AmbientClipboardManager
@@ -51,7 +50,6 @@
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextRange
-import androidx.core.os.BuildCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -73,7 +71,6 @@
 import org.mockito.ArgumentMatcher
 import org.mockito.ArgumentMatchers
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AndroidComposeViewAccessibilityDelegateCompatTest {
@@ -114,7 +111,7 @@
         val clickActionLabel = "click"
         val dismissActionLabel = "dismiss"
         val accessibilityValue = "checked"
-        val semanticsModifier = SemanticsModifierCore(1, true) {
+        val semanticsModifier = SemanticsModifierCore(1, true, false) {
             this.accessibilityValue = accessibilityValue
             onClick(clickActionLabel) { true }
             dismiss(dismissActionLabel) { true }
@@ -144,7 +141,7 @@
             )
         )
         val stateDescription = when {
-            BuildCompat.isAtLeastR() -> {
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
                 info.unwrap().stateDescription
             }
             Build.VERSION.SDK_INT >= 19 -> {
@@ -165,7 +162,7 @@
     fun testPopulateAccessibilityNodeInfoProperties_SeekBar() {
         val info = AccessibilityNodeInfoCompat.obtain()
         val setProgressActionLabel = "setProgress"
-        val semanticsModifier = SemanticsModifierCore(1, true) {
+        val semanticsModifier = SemanticsModifierCore(1, true, false) {
             accessibilityValueRange = AccessibilityRangeInfo(0.5f, 0f..1f, 6)
             setProgress(setProgressActionLabel) { true }
         }
@@ -201,7 +198,7 @@
         val setSelectionActionLabel = "setSelection"
         val setTextActionLabel = "setText"
         val text = "hello"
-        val semanticsModifier = SemanticsModifierCore(1, true) {
+        val semanticsModifier = SemanticsModifierCore(1, true, false) {
             this.text = AnnotatedString(text)
             this.textSelectionRange = TextRange(1)
             this.focused = true
@@ -389,7 +386,7 @@
         mergeDescendants: Boolean,
         properties: (SemanticsPropertyReceiver.() -> Unit)
     ): SemanticsNode {
-        val semanticsModifier = SemanticsModifierCore(id, mergeDescendants, properties)
+        val semanticsModifier = SemanticsModifierCore(id, mergeDescendants, false, properties)
         return SemanticsNode(
             SemanticsWrapper(InnerPlaceable(LayoutNode()), semanticsModifier),
             true
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollModifierTest.kt
new file mode 100644
index 0000000..513b0f6
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollModifierTest.kt
@@ -0,0 +1,1062 @@
+/*
+ * 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.gesture.nestedscroll
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.minus
+import androidx.compose.ui.unit.plus
+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 org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class NestedScrollModifierTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val preScrollOffset = Offset(120f, 120f)
+    private val scrollOffset = Offset(125f, 125f)
+    private val scrollLeftOffset = Offset(32f, 32f)
+    private val preFling = Velocity(Offset(120f, 120f))
+    private val postFlingConsumed = Velocity(Offset(151f, 63f))
+    private val postFlingLeft = Velocity(Offset(11f, 13f))
+
+    @Test
+    fun nestedScroll_twoNodes_orderTest() {
+        var counter = 0
+        val childConnection = object : NestedScrollConnection {}
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(counter).isEqualTo(1)
+                counter++
+                return Offset.Zero
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(counter).isEqualTo(3)
+                counter++
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(counter).isEqualTo(5)
+                counter++
+                return Velocity.Zero
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(counter).isEqualTo(7)
+                counter++
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                Box(
+                    Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(counter).isEqualTo(0)
+            counter++
+
+            childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(counter).isEqualTo(2)
+            counter++
+
+            childDispatcher
+                .dispatchPostScroll(scrollOffset, scrollLeftOffset, NestedScrollSource.Drag)
+            assertThat(counter).isEqualTo(4)
+            counter++
+
+            childDispatcher.dispatchPreFling(preFling)
+            assertThat(counter).isEqualTo(6)
+            counter++
+
+            childDispatcher.dispatchPostFling(postFlingConsumed, postFlingLeft)
+            assertThat(counter).isEqualTo(8)
+            counter++
+        }
+    }
+
+    @Test
+    fun nestedScroll_NNodes_orderTest_preScroll() {
+        var counter = 0
+        val childConnection = object : NestedScrollConnection {}
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(counter).isEqualTo(2)
+                counter++
+                return Offset.Zero
+            }
+        }
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(counter).isEqualTo(1)
+                counter++
+                return Offset.Zero
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(counter).isEqualTo(0)
+            counter++
+
+            childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(counter).isEqualTo(3)
+            counter++
+        }
+    }
+
+    @Test
+    fun nestedScroll_NNodes_orderTest_scroll() {
+        var counter = 0
+        val childConnection = object : NestedScrollConnection {}
+        val parentConnection = object : NestedScrollConnection {
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(counter).isEqualTo(1)
+                counter++
+                return Offset.Zero
+            }
+        }
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(counter).isEqualTo(2)
+                counter++
+                return Offset.Zero
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(counter).isEqualTo(0)
+            counter++
+
+            childDispatcher
+                .dispatchPostScroll(scrollOffset, scrollLeftOffset, NestedScrollSource.Drag)
+            assertThat(counter).isEqualTo(3)
+            counter++
+        }
+    }
+
+    @Test
+    fun nestedScroll_NNodes_orderTest_preFling() {
+        var counter = 0
+        val childConnection = object : NestedScrollConnection {}
+        val parentConnection = object : NestedScrollConnection {
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(counter).isEqualTo(2)
+                counter++
+                return Velocity.Zero
+            }
+        }
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(counter).isEqualTo(1)
+                counter++
+                return Velocity.Zero
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(counter).isEqualTo(0)
+            counter++
+
+            childDispatcher.dispatchPreFling(preFling)
+            assertThat(counter).isEqualTo(3)
+            counter++
+        }
+    }
+
+    @Test
+    fun nestedScroll_NNodes_orderTest_fling() {
+        var counter = 0
+        val childConnection = object : NestedScrollConnection {}
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(counter).isEqualTo(1)
+                counter++
+                onFinished.invoke(Velocity.Zero)
+            }
+        }
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(counter).isEqualTo(2)
+                counter++
+                onFinished.invoke(Velocity.Zero)
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(counter).isEqualTo(0)
+            counter++
+
+            childDispatcher.dispatchPostFling(postFlingConsumed, postFlingLeft)
+
+            assertThat(counter).isEqualTo(3)
+            counter++
+        }
+    }
+
+    @Test
+    fun nestedScroll_twoNodes_hierarchyDispatch() {
+        val preScrollReturn = Offset(60f, 30f)
+        val preFlingReturn = Velocity(Offset(154f, 56f))
+        var currentsource = NestedScrollSource.Drag
+
+        val childConnection = object : NestedScrollConnection {}
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(available).isEqualTo(preScrollOffset)
+                assertThat(source).isEqualTo(currentsource)
+                return preScrollReturn
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(consumed).isEqualTo(scrollOffset)
+                assertThat(available).isEqualTo(scrollLeftOffset)
+                assertThat(source).isEqualTo(currentsource)
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(available).isEqualTo(preFling)
+                return preFlingReturn
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(consumed).isEqualTo(postFlingConsumed)
+                assertThat(available).isEqualTo(postFlingLeft)
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                Box(
+                    Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            val preRes = childDispatcher.dispatchPreScroll(preScrollOffset, currentsource)
+            assertThat(preRes).isEqualTo(preScrollReturn)
+
+            childDispatcher.dispatchPostScroll(scrollOffset, scrollLeftOffset, currentsource)
+            // flip to fling to test again below
+            currentsource = NestedScrollSource.Fling
+        }
+
+        rule.runOnIdle {
+            val preRes = childDispatcher.dispatchPreScroll(preScrollOffset, currentsource)
+            assertThat(preRes).isEqualTo(preScrollReturn)
+
+            childDispatcher.dispatchPostScroll(scrollOffset, scrollLeftOffset, currentsource)
+        }
+
+        rule.runOnIdle {
+            val preFlingRes = childDispatcher.dispatchPreFling(preFling)
+            assertThat(preFlingRes).isEqualTo(preFlingReturn)
+            childDispatcher.dispatchPostFling(postFlingConsumed, postFlingLeft)
+        }
+    }
+
+    @Test
+    fun nestedScroll_deltaCalculation_preScroll() {
+        val dispatchedPreScroll = Offset(10f, 10f)
+        val grandParentConsumesPreScroll = Offset(2f, 2f)
+        val parentConsumedPreScroll = Offset(1f, 1f)
+
+        val childConnection = object : NestedScrollConnection {}
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(available).isEqualTo(dispatchedPreScroll)
+                return grandParentConsumesPreScroll
+            }
+        }
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(available)
+                    .isEqualTo(dispatchedPreScroll - grandParentConsumesPreScroll)
+                return parentConsumedPreScroll
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            val preRes =
+                childDispatcher.dispatchPreScroll(dispatchedPreScroll, NestedScrollSource.Drag)
+            assertThat(preRes).isEqualTo(grandParentConsumesPreScroll + parentConsumedPreScroll)
+        }
+    }
+
+    @Test
+    fun nestedScroll_deltaCalculation_scroll() {
+        val dispatchedConsumedScroll = Offset(4f, 4f)
+        val dispatchedScroll = Offset(10f, 10f)
+        val grandParentConsumedScroll = Offset(2f, 2f)
+        val parentConsumedScroll = Offset(1f, 1f)
+
+        val childConnection = object : NestedScrollConnection {}
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(consumed).isEqualTo(parentConsumedScroll + dispatchedConsumedScroll)
+                assertThat(available).isEqualTo(dispatchedScroll - parentConsumedScroll)
+                return grandParentConsumedScroll
+            }
+        }
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(consumed).isEqualTo(dispatchedConsumedScroll)
+                assertThat(available).isEqualTo(dispatchedScroll)
+                return parentConsumedScroll
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            childDispatcher.dispatchPostScroll(
+                dispatchedConsumedScroll,
+                dispatchedScroll,
+                NestedScrollSource.Drag
+            )
+        }
+    }
+
+    @Test
+    fun nestedScroll_deltaCalculation_preFling() {
+        val dispatchedVelocity = Velocity(Offset(10f, 10f))
+        val grandParentConsumesPreFling = Velocity(Offset(2f, 2f))
+        val parentConsumedPreFling = Velocity(Offset(1f, 1f))
+
+        val childConnection = object : NestedScrollConnection {}
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(available).isEqualTo(dispatchedVelocity)
+                return grandParentConsumesPreFling
+            }
+        }
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(available)
+                    .isEqualTo(dispatchedVelocity - grandParentConsumesPreFling)
+                return parentConsumedPreFling
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            val preRes = childDispatcher.dispatchPreFling(dispatchedVelocity)
+            assertThat(preRes).isEqualTo(grandParentConsumesPreFling + parentConsumedPreFling)
+        }
+    }
+
+    @Test
+    fun nestedScroll_deltaCalculation_fling() {
+        val dispatchedConsumedVelocity = Velocity(Offset(4f, 4f))
+        val dispatchedLeftVelocity = Velocity(Offset(10f, 10f))
+        val grandParentConsumedPostFling = Velocity(Offset(2f, 2f))
+        val parentConsumedPostFling = Velocity(Offset(1f, 1f))
+
+        val childConnection = object : NestedScrollConnection {}
+        val grandParentConnection = object : NestedScrollConnection {
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(consumed)
+                    .isEqualTo(parentConsumedPostFling + dispatchedConsumedVelocity)
+                assertThat(available)
+                    .isEqualTo(dispatchedLeftVelocity - parentConsumedPostFling)
+                return onFinished(grandParentConsumedPostFling)
+            }
+        }
+        val parentConnection = object : NestedScrollConnection {
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(consumed).isEqualTo(dispatchedConsumedVelocity)
+                assertThat(available).isEqualTo(dispatchedLeftVelocity)
+                onFinished(parentConsumedPostFling)
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.size(100.dp).nestedScroll(grandParentConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(parentConnection)) {
+                    Box(
+                        Modifier.size(100.dp).nestedScroll(childConnection, childDispatcher)
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            childDispatcher.dispatchPostFling(dispatchedConsumedVelocity, dispatchedLeftVelocity)
+        }
+    }
+
+    @Test
+    fun nestedScroll_twoNodes_flatDispatch() {
+        val preScrollReturn = Offset(60f, 30f)
+        val preFlingReturn = Velocity(Offset(154f, 56f))
+        var currentsource = NestedScrollSource.Drag
+
+        val childConnection = object : NestedScrollConnection {}
+        val parentConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(available).isEqualTo(preScrollOffset)
+                assertThat(source).isEqualTo(currentsource)
+                return preScrollReturn
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(consumed).isEqualTo(scrollOffset)
+                assertThat(available).isEqualTo(scrollLeftOffset)
+                assertThat(source).isEqualTo(currentsource)
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(available).isEqualTo(preFling)
+                return preFlingReturn
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(consumed).isEqualTo(postFlingConsumed)
+                assertThat(available).isEqualTo(postFlingLeft)
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(
+                Modifier
+                    .size(100.dp)
+                    .nestedScroll(parentConnection) // parent
+                    .nestedScroll(childConnection, childDispatcher) // child
+            )
+        }
+
+        rule.runOnIdle {
+            val preRes = childDispatcher.dispatchPreScroll(preScrollOffset, currentsource)
+            assertThat(preRes).isEqualTo(preScrollReturn)
+
+            childDispatcher.dispatchPostScroll(scrollOffset, scrollLeftOffset, currentsource)
+            // flip to fling to test again below
+            currentsource = NestedScrollSource.Fling
+        }
+
+        rule.runOnIdle {
+            val preRes = childDispatcher.dispatchPreScroll(preScrollOffset, currentsource)
+            assertThat(preRes).isEqualTo(preScrollReturn)
+
+            childDispatcher.dispatchPostScroll(scrollOffset, scrollLeftOffset, currentsource)
+        }
+
+        rule.runOnIdle {
+            val preFlingRes = childDispatcher.dispatchPreFling(preFling)
+            assertThat(preFlingRes).isEqualTo(preFlingReturn)
+            childDispatcher.dispatchPostFling(postFlingConsumed, postFlingLeft)
+        }
+    }
+
+    @Test
+    fun nestedScroll_shouldNotCalledSelfConnection() {
+        val childConnection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertWithMessage("self connection shouldn't be called").fail()
+                return Offset.Zero
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertWithMessage("self connection shouldn't be called").fail()
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertWithMessage("self connection shouldn't be called").fail()
+                return Velocity.Zero
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertWithMessage("self connection shouldn't be called").fail()
+            }
+        }
+        val parentConnection = object : NestedScrollConnection {}
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            Box(Modifier.nestedScroll(parentConnection)) {
+                Box(Modifier.nestedScroll(childConnection, childDispatcher))
+            }
+        }
+
+        rule.runOnIdle {
+            childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            childDispatcher
+                .dispatchPostScroll(scrollOffset, scrollLeftOffset, NestedScrollSource.Fling)
+        }
+
+        rule.runOnIdle {
+            childDispatcher.dispatchPreFling(preFling)
+            childDispatcher.dispatchPostFling(postFlingConsumed, postFlingLeft)
+        }
+    }
+
+    @Test
+    fun nestedScroll_hierarchyDispatch_rootParentRemoval() {
+        testRootParentAdditionRemoval { root, child ->
+            Box(Modifier.size(100.dp).then(root)) {
+                Box(child)
+            }
+        }
+    }
+
+    @Test
+    fun nestedScroll_flatDispatch_rootParentRemoval() {
+        testRootParentAdditionRemoval { root, child ->
+            Box(Modifier.then(root).then(child))
+        }
+    }
+
+    @Test
+    fun nestedScroll_flatDispatch_longChain_rootParentRemoval() {
+        testRootParentAdditionRemoval { root, child ->
+            // insert a few random modifiers so it's more realistic example of wrapper re-usage
+            Box(Modifier.size(100.dp).then(root).padding(5.dp).size(50.dp).then(child))
+        }
+    }
+
+    @Test
+    fun nestedScroll_hierarchyDispatch_middleParentRemoval() {
+        testMiddleParentAdditionRemoval { rootMod, middleMod, childMod ->
+            // random boxes to emulate nesting
+            Box(Modifier.size(100.dp).then(rootMod)) {
+                Box {
+                    Box(Modifier.size(100.dp).then(middleMod)) {
+                        Box {
+                            Box(childMod)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun nestedScroll_flatDispatch_middleParentRemoval() {
+        testMiddleParentAdditionRemoval { rootMod, middleMod, childMod ->
+            Box(
+                Modifier
+                    .then(rootMod)
+                    .then(middleMod)
+                    .then(childMod)
+            )
+        }
+    }
+
+    @Test
+    fun nestedScroll_flatDispatch_longChain_middleParentRemoval() {
+        testMiddleParentAdditionRemoval { rootMod, middleMod, childMod ->
+            // insert a few random modifiers so it's more realistic example of wrapper re-usage
+            Box(
+                Modifier
+                    .size(100.dp)
+                    .then(rootMod)
+                    .size(90.dp)
+                    .clipToBounds()
+                    .then(middleMod)
+                    .padding(5.dp)
+                    .then(childMod)
+            )
+        }
+    }
+
+    @Test
+    fun nestedScroll_flatDispatch_runtimeSwapChange_orderTest() {
+        val preScrollReturn = Offset(60f, 30f)
+        val preFlingReturn = Velocity(Offset(154f, 56f))
+        var counter = 0
+
+        val isConnection1Parent = mutableStateOf(true)
+        val childConnection = object : NestedScrollConnection {}
+        val connection1 = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                return preScrollReturn
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                return preFlingReturn
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                onFinished.invoke(Velocity.Zero)
+            }
+        }
+        val connection2 = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                return preScrollReturn
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                return preFlingReturn
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                onFinished.invoke(Velocity.Zero)
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            val nestedScrollParents = if (isConnection1Parent.value) {
+                Modifier.nestedScroll(connection1).nestedScroll(connection2)
+            } else {
+                Modifier.nestedScroll(connection2).nestedScroll(connection1)
+            }
+            Box(
+                Modifier
+                    .size(100.dp)
+                    .then(nestedScrollParents)
+                    .nestedScroll(childConnection, childDispatcher)
+            )
+        }
+
+        repeat(2) {
+            rule.runOnIdle {
+                counter = 1
+
+                childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                childDispatcher.dispatchPostScroll(
+                    scrollOffset,
+                    scrollLeftOffset,
+                    NestedScrollSource.Drag
+                )
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                childDispatcher.dispatchPreFling(preFling)
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                childDispatcher.dispatchPostFling(postFlingConsumed, postFlingLeft)
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                isConnection1Parent.value = !isConnection1Parent.value
+            }
+        }
+    }
+
+    @Test
+    fun nestedScroll_hierarchyDispatch_runtimeSwapChange_orderTest() {
+        val preScrollReturn = Offset(60f, 30f)
+        val preFlingReturn = Velocity(Offset(154f, 56f))
+        var counter = 0
+
+        val isConnection1Parent = mutableStateOf(true)
+        val childConnection = object : NestedScrollConnection {}
+        val connection1 = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                return preScrollReturn
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                return preFlingReturn
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                onFinished.invoke(Velocity.Zero)
+            }
+        }
+        val connection2 = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                return preScrollReturn
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                return Offset.Zero
+            }
+
+            override fun onPreFling(available: Velocity): Velocity {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 2 else 1)
+                counter++
+                return preFlingReturn
+            }
+
+            override fun onPostFling(
+                consumed: Velocity,
+                available: Velocity,
+                onFinished: (Velocity) -> Unit
+            ) {
+                assertThat(counter).isEqualTo(if (isConnection1Parent.value) 1 else 2)
+                counter++
+                onFinished.invoke(Velocity.Zero)
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            val outerBoxConnection = if (isConnection1Parent.value) connection1 else connection2
+            val innerBoxConnection = if (isConnection1Parent.value) connection2 else connection1
+            Box(Modifier.size(100.dp).nestedScroll(outerBoxConnection)) {
+                Box(Modifier.size(100.dp).nestedScroll(innerBoxConnection)) {
+                    Box(Modifier.nestedScroll(childConnection, childDispatcher))
+                }
+            }
+        }
+
+        repeat(2) {
+            rule.runOnIdle {
+                counter = 1
+
+                childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                childDispatcher.dispatchPostScroll(
+                    scrollOffset,
+                    scrollLeftOffset,
+                    NestedScrollSource.Drag
+                )
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                childDispatcher.dispatchPreFling(preFling)
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                childDispatcher.dispatchPostFling(postFlingConsumed, postFlingLeft)
+                assertThat(counter).isEqualTo(3)
+                counter = 1
+
+                isConnection1Parent.value = !isConnection1Parent.value
+            }
+        }
+    }
+
+    // helper functions
+
+    private fun testMiddleParentAdditionRemoval(
+        content: @Composable (root: Modifier, middle: Modifier, child: Modifier) -> Unit
+    ) {
+        val rootParentPreConsumed = Offset(60f, 30f)
+        val parentToRemovePreConsumed = Offset(21f, 44f)
+
+        val emitNewParent = mutableStateOf(true)
+        val childConnection = object : NestedScrollConnection {}
+        val rootParent = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                return rootParentPreConsumed
+            }
+        }
+        val parentToRemove = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                if (!emitNewParent.value) {
+                    assertWithMessage("Shouldn't be called when not emitted").fail()
+                }
+                return parentToRemovePreConsumed
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            val maybeNestedScroll =
+                if (emitNewParent.value) Modifier.nestedScroll(parentToRemove) else Modifier
+            content.invoke(
+                Modifier.nestedScroll(rootParent),
+                maybeNestedScroll,
+                Modifier.nestedScroll(childConnection, childDispatcher)
+            )
+        }
+
+        rule.runOnIdle {
+            val res =
+                childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(res).isEqualTo(rootParentPreConsumed + parentToRemovePreConsumed)
+
+            emitNewParent.value = false
+        }
+
+        rule.runOnIdle {
+            val res =
+                childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(res).isEqualTo(rootParentPreConsumed)
+
+            emitNewParent.value = true
+        }
+
+        rule.runOnIdle {
+            val res =
+                childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(res).isEqualTo(rootParentPreConsumed + parentToRemovePreConsumed)
+        }
+    }
+
+    private fun testRootParentAdditionRemoval(
+        content: @Composable (root: Modifier, child: Modifier) -> Unit
+    ) {
+        val preScrollReturn = Offset(60f, 30f)
+
+        val emitParentNestedScroll = mutableStateOf(true)
+        val childConnection = object : NestedScrollConnection {}
+        val parent = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                return preScrollReturn
+            }
+        }
+        val childDispatcher = NestedScrollDispatcher()
+        rule.setContent {
+            val maybeNestedScroll =
+                if (emitParentNestedScroll.value) Modifier.nestedScroll(parent) else Modifier
+            content.invoke(
+                maybeNestedScroll,
+                Modifier.nestedScroll(childConnection, childDispatcher)
+            )
+        }
+
+        rule.runOnIdle {
+            val res = childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(res).isEqualTo(preScrollReturn)
+
+            emitParentNestedScroll.value = false
+        }
+
+        rule.runOnIdle {
+            val res = childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(res).isEqualTo(Offset.Zero)
+            emitParentNestedScroll.value = true
+        }
+
+        rule.runOnIdle {
+            val res = childDispatcher.dispatchPreScroll(preScrollOffset, NestedScrollSource.Drag)
+            assertThat(res).isEqualTo(preScrollReturn)
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index 70908d0..a70a1e1 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -29,7 +29,6 @@
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNodeWrapper
@@ -84,7 +83,6 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalLayoutNodeApi::class)
 class PointerInputEventProcessorTest {
 
     private lateinit var root: LayoutNode
@@ -3094,7 +3092,6 @@
 abstract class TestOwner : Owner {
     var position: IntOffset? = null
 
-    @ExperimentalLayoutNodeApi
     override val root: LayoutNode
         get() = LayoutNode()
 
@@ -3106,7 +3103,6 @@
 private class PointerInputModifierImpl2(override val pointerInputFilter: PointerInputFilter) :
     PointerInputModifier
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 private fun LayoutNode(x: Int, y: Int, x2: Int, y2: Int, modifier: Modifier = Modifier) =
     LayoutNode().apply {
         this.modifier = modifier
@@ -3130,13 +3126,11 @@
         detach()
     }
 
-@ExperimentalLayoutNodeApi
 private fun mockOwner(
     position: IntOffset = IntOffset.Zero,
     targetRoot: LayoutNode = LayoutNode()
 ): Owner = MockOwner(position, targetRoot)
 
-@ExperimentalLayoutNodeApi
 @OptIn(
     ExperimentalFocus::class,
     InternalCoreApi::class
@@ -3187,8 +3181,6 @@
     override fun onRequestRelayout(layoutNode: LayoutNode) {
     }
 
-    override val hasPendingMeasureOrLayout = false
-
     override fun onAttach(node: LayoutNode) {
     }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
index cc1b366..7ca9b32 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.ValueElement
 import androidx.compose.ui.platform.ViewConfiguration
@@ -31,22 +30,24 @@
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.withTimeout
-import kotlinx.coroutines.yield
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalPointerInput::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class SuspendingPointerInputFilterTest {
     @After
     fun after() {
@@ -55,8 +56,8 @@
     }
 
     @Test
-    fun testAwaitSingleEvent(): Unit = runBlocking {
-        val filter = SuspendingPointerInputFilter(DummyViewConfiguration())
+    fun testAwaitSingleEvent(): Unit = runBlockingTest {
+        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
 
         val result = CompletableDeferred<PointerEvent>()
         launch {
@@ -66,7 +67,6 @@
                 }
             }
         }
-        yield()
 
         val emitter = PointerInputChangeEmitter()
         val expectedChange = emitter.nextChange(Offset(5f, 5f))
@@ -85,10 +85,10 @@
     }
 
     @Test
-    fun testAwaitSeveralEvents(): Unit = runBlocking {
-        val filter = SuspendingPointerInputFilter(DummyViewConfiguration())
+    fun testAwaitSeveralEvents(): Unit = runBlockingTest {
+        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
         val results = Channel<PointerEvent>(Channel.UNLIMITED)
-        val reader = launch {
+        launch {
             with(filter) {
                 handlePointerInput {
                     repeat(3) {
@@ -98,7 +98,6 @@
                 }
             }
         }
-        yield()
 
         val emitter = PointerInputChangeEmitter()
         val expected = listOf(
@@ -118,15 +117,13 @@
         }
 
         assertEquals(expected, received)
-
-        reader.cancel()
     }
 
     @Test
-    fun testSyntheticCancelEvent(): Unit = runBlocking {
-        val filter = SuspendingPointerInputFilter(DummyViewConfiguration())
+    fun testSyntheticCancelEvent(): Unit = runBlockingTest {
+        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
         val results = Channel<PointerEvent>(Channel.UNLIMITED)
-        val reader = launch {
+        launch {
             with(filter) {
                 handlePointerInput {
                     repeat(3) {
@@ -136,7 +133,6 @@
                 }
             }
         }
-        yield()
 
         val bounds = IntSize(50, 50)
         val emitter1 = PointerInputChangeEmitter(0)
@@ -193,8 +189,33 @@
             val actualEvent = received[index]
             PointerEventSubject.assertThat(actualEvent).isStructurallyEqualTo(expectedEvent)
         }
+    }
 
-        reader.cancel()
+    @Test
+    fun testCancelledHandlerBlock() = runBlockingTest {
+        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+        val counter = TestCounter()
+        val handler = launch {
+            with(filter) {
+                try {
+                    handlePointerInput {
+                        try {
+                            counter.expect(1, "about to call awaitPointerEvent")
+                            awaitPointerEvent()
+                            fail("awaitPointerEvent returned; should have thrown for cancel")
+                        } finally {
+                            counter.expect(3, "inner finally block running")
+                        }
+                    }
+                } finally {
+                    counter.expect(4, "outer finally block running; inner finally should have run")
+                }
+            }
+        }
+
+        counter.expect(2, "before cancelling handler; awaitPointerEvent should be suspended")
+        handler.cancel()
+        counter.expect(5, "after cancelling; finally blocks should have run")
     }
 
     @Test
@@ -213,7 +234,6 @@
 
 private fun PointerInputChange.toPointerEvent() = PointerEvent(listOf(this))
 
-@ExperimentalPointerInput
 private val PointerEvent.firstChange get() = changes.first()
 
 private class PointerInputChangeEmitter(id: Int = 0) {
@@ -244,7 +264,7 @@
     }
 }
 
-private class DummyViewConfiguration : ViewConfiguration {
+private class FakeViewConfiguration : ViewConfiguration {
     override val longPressTimeout: Duration
         get() = 500.milliseconds
     override val doubleTapTimeout: Duration
@@ -253,4 +273,16 @@
         get() = 40.milliseconds
     override val touchSlop: Float
         get() = 18f
-}
\ No newline at end of file
+}
+
+private class TestCounter {
+    private var count = 0
+
+    fun expect(checkpoint: Int, message: String = "(no message)") {
+        val expected = count + 1
+        if (checkpoint != expected) {
+            fail("out of order event $checkpoint, expected $expected, $message")
+        }
+        count = expected
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt
index 9b4325a..04da750 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/AlignmentLineTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.layout
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import org.junit.Test
@@ -26,7 +25,6 @@
 @RunWith(AndroidJUnit4::class)
 class AlignmentLineTest {
     @Test
-    @OptIn(ExperimentalLayoutNodeApi::class)
     fun queryingLinesOfUnmeasuredChild() {
         val root = root {
             queryAlignmentLineDuringMeasure()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt
index fb78806..65a92f9 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/Helpers.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.layout
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.MeasureAndLayoutDelegate
 import androidx.compose.ui.node.OwnerSnapshotObserver
@@ -30,7 +29,6 @@
 import kotlin.math.min
 
 @Suppress("UNCHECKED_CAST")
-@ExperimentalLayoutNodeApi
 internal fun createDelegate(
     root: LayoutNode,
     firstMeasureCompleted: Boolean = true
@@ -65,7 +63,6 @@
 
 internal fun defaultRootConstraints() = Constraints(maxWidth = 100, maxHeight = 100)
 
-@ExperimentalLayoutNodeApi
 internal fun assertNotRemeasured(node: LayoutNode, block: (LayoutNode) -> Unit) {
     val measuresCountBefore = node.measuresCount
     block(node)
@@ -73,7 +70,6 @@
     assertMeasuredAndLaidOut(node)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun assertRemeasured(
     node: LayoutNode,
     times: Int = 1,
@@ -89,7 +85,6 @@
     assertMeasuredAndLaidOut(node)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun assertRelaidOut(node: LayoutNode, times: Int = 1, block: (LayoutNode) -> Unit) {
     val layoutsCountBefore = node.layoutsCount
     block(node)
@@ -97,7 +92,6 @@
     assertMeasuredAndLaidOut(node)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun assertNotRelaidOut(node: LayoutNode, block: (LayoutNode) -> Unit) {
     val layoutsCountBefore = node.layoutsCount
     block(node)
@@ -105,17 +99,14 @@
     assertMeasuredAndLaidOut(node)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun assertMeasureRequired(node: LayoutNode) {
     Truth.assertThat(node.layoutState).isEqualTo(LayoutNode.LayoutState.NeedsRemeasure)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun assertMeasuredAndLaidOut(node: LayoutNode) {
     Truth.assertThat(node.layoutState).isEqualTo(LayoutNode.LayoutState.Ready)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun assertLayoutRequired(node: LayoutNode) {
     Truth.assertThat(node.layoutState).isEqualTo(LayoutNode.LayoutState.NeedsRelayout)
 }
@@ -147,12 +138,10 @@
     Truth.assertThat(modifier.layoutsCount).isEqualTo(layoutsCountBefore + 1)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun root(block: LayoutNode.() -> Unit = {}): LayoutNode {
     return node(block)
 }
 
-@ExperimentalLayoutNodeApi
 internal fun node(block: LayoutNode.() -> Unit = {}): LayoutNode {
     return LayoutNode().apply {
         measureBlocks = MeasureInMeasureBlock()
@@ -160,66 +149,51 @@
     }
 }
 
-@ExperimentalLayoutNodeApi
 internal fun LayoutNode.add(child: LayoutNode) = insertAt(children.count(), child)
 
-@ExperimentalLayoutNodeApi
 internal fun LayoutNode.measureInLayoutBlock() {
     measureBlocks = MeasureInLayoutBlock()
 }
 
-@ExperimentalLayoutNodeApi
 internal fun LayoutNode.doNotMeasure() {
     measureBlocks = NoMeasureBlock()
 }
 
-@ExperimentalLayoutNodeApi
 internal fun LayoutNode.queryAlignmentLineDuringMeasure() {
     (measureBlocks as SmartMeasureBlock).queryAlignmentLinesDuringMeasure = true
 }
 
-@ExperimentalLayoutNodeApi
 internal fun LayoutNode.runDuringMeasure(block: () -> Unit) {
     (measureBlocks as SmartMeasureBlock).preMeasureCallback = block
 }
 
-@ExperimentalLayoutNodeApi
 internal fun LayoutNode.runDuringLayout(block: () -> Unit) {
     (measureBlocks as SmartMeasureBlock).preLayoutCallback = block
 }
 
-@ExperimentalLayoutNodeApi
 internal val LayoutNode.first: LayoutNode get() = children.first()
-@ExperimentalLayoutNodeApi
 internal val LayoutNode.second: LayoutNode get() = children[1]
-@ExperimentalLayoutNodeApi
 internal val LayoutNode.measuresCount: Int
     get() = (measureBlocks as SmartMeasureBlock).measuresCount
-@ExperimentalLayoutNodeApi
 internal val LayoutNode.layoutsCount: Int
     get() = (measureBlocks as SmartMeasureBlock).layoutsCount
-@ExperimentalLayoutNodeApi
 internal var LayoutNode.wrapChildren: Boolean
     get() = (measureBlocks as SmartMeasureBlock).wrapChildren
     set(value) {
         (measureBlocks as SmartMeasureBlock).wrapChildren = value
     }
-@ExperimentalLayoutNodeApi
 internal val LayoutNode.measuredWithLayoutDirection: LayoutDirection
     get() = (measureBlocks as SmartMeasureBlock).measuredLayoutDirection!!
-@ExperimentalLayoutNodeApi
 internal var LayoutNode.size: Int?
     get() = (measureBlocks as SmartMeasureBlock).size
     set(value) {
         (measureBlocks as SmartMeasureBlock).size = value
     }
-@ExperimentalLayoutNodeApi
 internal var LayoutNode.childrenDirection: LayoutDirection?
     get() = (measureBlocks as SmartMeasureBlock).childrenLayoutDirection
     set(value) {
         (measureBlocks as SmartMeasureBlock).childrenLayoutDirection = value
     }
-@ExperimentalLayoutNodeApi
 internal var LayoutNode.shouldPlaceChildren: Boolean
     get() = (measureBlocks as SmartMeasureBlock).shouldPlaceChildren
     set(value) {
@@ -228,7 +202,6 @@
 
 internal val TestAlignmentLine = HorizontalAlignmentLine(::min)
 
-@ExperimentalLayoutNodeApi
 internal abstract class SmartMeasureBlock : LayoutNode.NoIntrinsicsMeasureBlocks("") {
     var measuresCount = 0
         protected set
@@ -246,7 +219,6 @@
     var shouldPlaceChildren = true
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class MeasureInMeasureBlock : SmartMeasureBlock() {
     override fun measure(
         measureScope: MeasureScope,
@@ -292,7 +264,6 @@
     }
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class MeasureInLayoutBlock : SmartMeasureBlock() {
 
     override var wrapChildren: Boolean
@@ -342,7 +313,6 @@
     }
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class NoMeasureBlock : SmartMeasureBlock() {
 
     override var queryAlignmentLinesDuringMeasure: Boolean
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureAndLayoutDelegateTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureAndLayoutDelegateTest.kt
index 1319672..9136657 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureAndLayoutDelegateTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureAndLayoutDelegateTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.layout
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode.LayoutState
 import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
 import androidx.compose.ui.unit.Constraints
@@ -29,7 +28,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalLayoutNodeApi::class)
 class MeasureAndLayoutDelegateTest {
 
     private val DifferentSize = 50
@@ -1098,7 +1096,6 @@
         val root = root {
             add(
                 node {
-                    @OptIn(ExperimentalLayoutNodeApi::class)
                     modifier = spyModifier
                 }
             )
@@ -1118,7 +1115,6 @@
         val root = root {
             add(
                 node {
-                    @OptIn(ExperimentalLayoutNodeApi::class)
                     modifier = spyModifier
                 }
             )
@@ -1168,7 +1164,6 @@
                     delegate.requestRelayout(root)
                     root.runDuringLayout {
                         // this means the root.first will be measured before laying out the root
-                        @OptIn(ExperimentalLayoutNodeApi::class)
                         assertThat(root.first.layoutState).isEqualTo(LayoutState.NeedsRelayout)
                     }
                     assertThat(delegate.measureAndLayout()).isFalse()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacedChildTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacedChildTest.kt
index 747973d..e9af374 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacedChildTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacedChildTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.layout
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.unit.Constraints
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,7 +29,6 @@
 class PlacedChildTest {
 
     @Test
-    @OptIn(ExperimentalLayoutNodeApi::class)
     fun remeasureNotPlacedChild() {
         val root = root {
             measureBlocks = UseChildSizeButNotPlace
@@ -59,7 +57,6 @@
     }
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 private val UseChildSizeButNotPlace = object : LayoutNode.NoIntrinsicsMeasureBlocks("") {
     override fun measure(
         measureScope: MeasureScope,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RemeasurementModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RemeasurementModifierTest.kt
index 014540f..6dc67ef 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RemeasurementModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RemeasurementModifierTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.layout
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
@@ -27,7 +26,6 @@
 @RunWith(AndroidJUnit4::class)
 class RemeasurementModifierTest {
     @Test
-    @OptIn(ExperimentalLayoutNodeApi::class)
     fun nodeIsRemeasuredAfterForceRemeasureBlocking() {
         var remeasurementObj: Remeasurement? = null
         val root = root {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidViewCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidViewCompatTest.kt
index 84b5920..04dca9c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidViewCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidViewCompatTest.kt
@@ -59,12 +59,10 @@
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.globalPosition
 import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutEmitHelper
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.node.Ref
-import androidx.compose.ui.node.isAttached
 import androidx.compose.ui.test.TestActivity
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.captureToImage
@@ -573,7 +571,7 @@
     }
 
     @Test
-    @OptIn(ExperimentalLayoutNodeApi::class, ExperimentalComposeApi::class)
+    @OptIn(ExperimentalComposeApi::class)
     fun testComposeInsideView_attachingAndDetaching() {
         var composeContent by mutableStateOf(true)
         var node: LayoutNode? = null
@@ -585,7 +583,7 @@
                             emit<LayoutNode, Applier<Any>>(
                                 ctor = LayoutEmitHelper.constructor,
                                 update = {
-                                    node = this.node
+                                    set(Unit) { node = this }
                                     set(noOpMeasureBlocks, LayoutEmitHelper.setMeasureBlocks)
                                 }
                             )
@@ -620,7 +618,7 @@
         assertNotNull(innerAndroidComposeView)
         assertTrue(innerAndroidComposeView!!.isAttachedToWindow)
         assertNotNull(node)
-        assertTrue(node!!.isAttached())
+        assertTrue(node!!.isAttached)
 
         rule.runOnIdle { composeContent = false }
 
@@ -628,7 +626,7 @@
         rule.runOnIdle {
             assertFalse(innerAndroidComposeView!!.isAttachedToWindow)
             // the node stays attached after the compose view is detached
-            assertTrue(node!!.isAttached())
+            assertTrue(node!!.isAttached)
         }
     }
 
@@ -783,7 +781,6 @@
         }
     }
 
-    @OptIn(ExperimentalLayoutNodeApi::class)
     private val noOpMeasureBlocks = object : LayoutNode.NoIntrinsicsMeasureBlocks("") {
         override fun measure(
             measureScope: MeasureScope,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt
index c08af79..f82dba7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.platform
 
 import androidx.compose.ui.node.DepthSortedSet
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.node.add
@@ -31,7 +30,6 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalLayoutNodeApi::class)
 class DepthSortedSetTest {
 
     @Test
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
index a0d27e1..5182721 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
@@ -176,6 +176,7 @@
         activityScenario.onActivity {
             var composed = false
             it.setContent {
+                check(!composed) { "the content is expected to be composed once" }
                 composed = true
             }
             assertTrue("setContent didn't compose the content synchronously", composed)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
index c7136d0..6dbf1c4 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
@@ -31,12 +31,14 @@
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertCountEquals
 import androidx.compose.ui.test.assertLabelEquals
+import androidx.compose.ui.test.assertTextEquals
 import androidx.compose.ui.test.assertValueEquals
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onAllNodesWithLabel
 import androidx.compose.ui.test.onAllNodesWithText
 import androidx.compose.ui.test.onNodeWithLabel
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.AnnotatedString
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -138,6 +140,34 @@
     }
 
     @Test
+    fun clearAndSetSemantics() {
+        val tag1 = "tag1"
+        val tag2 = "tag2"
+        val label1 = "foo"
+        val label2 = "hidden"
+        val label3 = "baz"
+        rule.setContent {
+            SimpleTestLayout(Modifier.semantics(mergeDescendants = true) {}.testTag(tag1)) {
+                SimpleTestLayout(Modifier.semantics { accessibilityLabel = label1 }) { }
+                SimpleTestLayout(Modifier.clearAndSetSemantics {}) {
+                    SimpleTestLayout(Modifier.semantics { accessibilityLabel = label2 }) { }
+                }
+                SimpleTestLayout(Modifier.clearAndSetSemantics { accessibilityLabel = label3 }) {
+                    SimpleTestLayout(Modifier.semantics { accessibilityLabel = label2 }) { }
+                }
+                SimpleTestLayout(
+                    Modifier.semantics(mergeDescendants = true) {}.testTag(tag2)
+                        .clearAndSetSemantics { text = AnnotatedString(label1) }
+                ) {
+                    SimpleTestLayout(Modifier.semantics { text = AnnotatedString(label2) }) { }
+                }
+            }
+        }
+
+        rule.onNodeWithTag(tag1).assertLabelEquals("$label1, $label3")
+        rule.onNodeWithTag(tag2).assertTextEquals(label1)
+    }
+    @Test
     fun removingMergedSubtree_updatesSemantics() {
         val label = "foo"
         val showSubtree = mutableStateOf(true)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt
index ee36bdc..dcdc42e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogSecureFlagTest.kt
@@ -26,7 +26,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.unit.dp
@@ -74,7 +73,7 @@
     @Test
     fun forcedFlagOnDialogToDisabled() {
         rule.setContent {
-            TestDialog(AndroidDialogProperties(SecureFlagPolicy.SecureOff))
+            TestDialog(AndroidDialogProperties(securePolicy = SecureFlagPolicy.SecureOff))
         }
 
         // This tests that we also override the flag from the Activity
@@ -84,7 +83,7 @@
     @Test
     fun forcedFlagOnDialogToEnabled() {
         rule.setContent {
-            TestDialog(AndroidDialogProperties(SecureFlagPolicy.SecureOn))
+            TestDialog(AndroidDialogProperties(securePolicy = SecureFlagPolicy.SecureOn))
         }
 
         assertThat(isSecureFlagEnabledForDialog()).isTrue()
@@ -93,7 +92,7 @@
     @Test
     fun toggleFlagOnDialog() {
         var properties: AndroidDialogProperties?
-        by mutableStateOf(AndroidDialogProperties(SecureFlagPolicy.SecureOff))
+        by mutableStateOf(AndroidDialogProperties(securePolicy = SecureFlagPolicy.SecureOff))
 
         rule.setContent {
             TestDialog(properties)
@@ -102,11 +101,11 @@
         assertThat(isSecureFlagEnabledForDialog()).isFalse()
 
         // Toggle flag
-        properties = AndroidDialogProperties(SecureFlagPolicy.SecureOn)
+        properties = AndroidDialogProperties(securePolicy = SecureFlagPolicy.SecureOn)
         assertThat(isSecureFlagEnabledForDialog()).isTrue()
 
         // Set to inherit
-        properties = AndroidDialogProperties(SecureFlagPolicy.Inherit)
+        properties = AndroidDialogProperties(securePolicy = SecureFlagPolicy.Inherit)
         assertThat(isSecureFlagEnabledForDialog()).isEqualTo(setSecureFlagOnActivity)
     }
 
@@ -123,10 +122,9 @@
     }
 
     private fun isSecureFlagEnabledForDialog(): Boolean {
-        @OptIn(ExperimentalLayoutNodeApi::class)
         val owner = rule
             .onNode(isDialog())
-            .fetchSemanticsNode("").componentNode.owner as View
+            .fetchSemanticsNode("").owner as View
         return (owner.rootView.layoutParams as WindowManager.LayoutParams).flags and
             WindowManager.LayoutParams.FLAG_SECURE != 0
     }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt
index 5bbf8203..72d4466 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt
@@ -33,7 +33,6 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.UiDevice
 import org.junit.Assert.assertEquals
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -148,6 +147,34 @@
     }
 
     @Test
+    fun dialogTest_isNotDismissed_whenDismissOnClickOutsideIsFalse() {
+        rule.setContent {
+            val showDialog = remember { mutableStateOf(true) }
+
+            if (showDialog.value) {
+                Dialog(
+                    onDismissRequest = {
+                        showDialog.value = false
+                    },
+                    properties = AndroidDialogProperties(dismissOnClickOutside = false)
+                ) {
+                    BasicText(defaultText)
+                }
+            }
+        }
+
+        rule.onNodeWithText(defaultText).assertIsDisplayed()
+
+        // Click outside the dialog to try to dismiss it
+        val outsideX = 0
+        val outsideY = rule.displaySize.height / 2
+        UiDevice.getInstance(getInstrumentation()).click(outsideX, outsideY)
+
+        // The Dialog should still be visible
+        rule.onNodeWithText(defaultText).assertIsDisplayed()
+    }
+
+    @Test
     fun dialogTest_isDismissed_whenSpecified_backButtonPressed() {
         rule.setContent {
             val showDialog = remember { mutableStateOf(true) }
@@ -171,9 +198,6 @@
         rule.onNodeWithText(defaultText).assertDoesNotExist()
     }
 
-    // TODO(pavlis): Espresso loses focus on the dialog after back press. That makes the
-    // subsequent query to fails.
-    @Ignore
     @Test
     fun dialogTest_isNotDismissed_whenNotSpecified_backButtonPressed() {
         rule.setContent {
@@ -196,6 +220,32 @@
     }
 
     @Test
+    fun dialogTest_isNotDismissed_whenDismissOnBackPressIsFalse() {
+        rule.setContent {
+            val showDialog = remember { mutableStateOf(true) }
+
+            if (showDialog.value) {
+                Dialog(
+                    onDismissRequest = {
+                        showDialog.value = false
+                    },
+                    properties = AndroidDialogProperties(dismissOnBackPress = false)
+                ) {
+                    BasicText(defaultText)
+                }
+            }
+        }
+
+        rule.onNodeWithText(defaultText).assertIsDisplayed()
+
+        // Click the back button to try to dismiss the dialog
+        Espresso.pressBack()
+
+        // The Dialog should still be visible
+        rule.onNodeWithText(defaultText).assertIsDisplayed()
+    }
+
+    @Test
     fun dialog_preservesAmbients() {
         val ambient = ambientOf<Float>()
         var value = 0f
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/UiApplier.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/UiApplier.kt
index 144631b..92a0968 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/UiApplier.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/UiApplier.kt
@@ -19,112 +19,73 @@
 
 import android.view.View
 import android.view.ViewGroup
-import androidx.compose.runtime.Applier
+import androidx.compose.runtime.AbstractApplier
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.viewinterop.AndroidViewHolder
 import androidx.compose.ui.viewinterop.InternalInteropApi
 import androidx.compose.ui.viewinterop.ViewBlockHolder
 
-// TODO: evaluate if this class is necessary or not
-private class Stack<T> {
-    private val backing = ArrayList<T>()
-
-    val size: Int get() = backing.size
-
-    fun push(value: T) = backing.add(value)
-    fun pop(): T = backing.removeAt(size - 1)
-    fun peek(): T = backing[size - 1]
-    fun isEmpty() = backing.isEmpty()
-    fun isNotEmpty() = !isEmpty()
-    fun clear() = backing.clear()
-}
-
-@OptIn(ExperimentalLayoutNodeApi::class)
-class UiApplier(
-    private val root: Any
-) : Applier<Any> {
-    private val stack = Stack<Any>()
-    private data class PendingInsert(val index: Int, val instance: Any)
-    // TODO(b/159073250): remove
-    private val pendingInserts = Stack<PendingInsert>()
-
+class UiApplier(root: Any) : AbstractApplier<Any>(root) {
     private fun invalidNode(node: Any): Nothing =
         error("Unsupported node type ${node.javaClass.simpleName}")
 
-    override var current: Any = root
-        private set
-
-    override fun down(node: Any) {
-        stack.push(current)
-        current = node
-    }
-
     override fun up() {
         val instance = current
-        val parent = stack.pop()
-        current = parent
-        // TODO(lmr): We should strongly consider removing this ViewAdapter concept
+        super.up()
+        val parent = current
+        if (parent is ViewGroup && instance is View) {
+            instance.getViewAdapterIfExists()?.didUpdate(instance, parent)
+        }
+    }
+
+    override fun insertTopDown(index: Int, instance: Any) {
+        // Ignored. Insert is performed in [insertBottomUp] to build the tree bottom-up to avoid
+        // duplicate notification when the child nodes enter the tree.
+    }
+
+    override fun insertBottomUp(index: Int, instance: Any) {
         val adapter = when (instance) {
             is View -> instance.getViewAdapterIfExists()
             else -> null
         }
-        if (pendingInserts.isNotEmpty()) {
-            val pendingInsert = pendingInserts.peek()
-            if (pendingInsert.instance == instance) {
-                val index = pendingInsert.index
-                pendingInserts.pop()
-                when (parent) {
-                    is ViewGroup ->
-                        when (instance) {
-                            is View -> {
-                                adapter?.willInsert(instance, parent)
-                                parent.addView(instance, index)
-                                adapter?.didInsert(instance, parent)
-                            }
-                            is LayoutNode -> {
-                                val composeView = AndroidComposeView(parent.context)
-                                parent.addView(composeView, index)
-                                composeView.root.insertAt(0, instance)
-                            }
-                            else -> invalidNode(instance)
-                        }
-                    is LayoutNode ->
-                        when (instance) {
-                            is View -> {
-                                // Wrap the instance in an AndroidViewHolder, unless the instance
-                                // itself is already one.
-                                @OptIn(InternalInteropApi::class)
-                                val androidViewHolder =
-                                    if (instance is AndroidViewHolder) {
-                                        instance
-                                    } else {
-                                        ViewBlockHolder<View>(instance.context).apply {
-                                            view = instance
-                                        }
-                                    }
-
-                                parent.insertAt(index, androidViewHolder.toLayoutNode())
-                            }
-                            is LayoutNode -> parent.insertAt(index, instance)
-                            else -> invalidNode(instance)
-                        }
-                    else -> invalidNode(parent)
+        when (val parent = current) {
+            is ViewGroup ->
+                when (instance) {
+                    is View -> {
+                        adapter?.willInsert(instance, parent)
+                        parent.addView(instance, index)
+                        adapter?.didInsert(instance, parent)
+                    }
+                    is LayoutNode -> {
+                        val composeView = AndroidComposeView(parent.context)
+                        parent.addView(composeView, index)
+                        composeView.root.insertAt(0, instance)
+                    }
+                    else -> invalidNode(instance)
                 }
-                return
-            }
-        }
-        if (parent is ViewGroup)
-            adapter?.didUpdate(instance as View, parent)
-    }
+            is LayoutNode ->
+                when (instance) {
+                    is View -> {
+                        // Wrap the instance in an AndroidViewHolder, unless the instance
+                        // itself is already one.
+                        @OptIn(InternalInteropApi::class)
+                        val androidViewHolder =
+                            if (instance is AndroidViewHolder) {
+                                instance
+                            } else {
+                                ViewBlockHolder<View>(instance.context).apply {
+                                    view = instance
+                                }
+                            }
 
-    override fun insert(index: Int, instance: Any) {
-        pendingInserts.push(
-            PendingInsert(
-                index,
-                instance
-            )
-        )
+                        parent.insertAt(index, androidViewHolder.toLayoutNode())
+                    }
+                    is LayoutNode -> parent.insertAt(index, instance)
+                    else -> invalidNode(instance)
+                }
+            else -> invalidNode(parent)
+        }
     }
 
     override fun remove(index: Int, count: Int) {
@@ -163,13 +124,31 @@
         }
     }
 
-    override fun clear() {
-        stack.clear()
-        current = root
+    override fun onClear() {
         when (root) {
             is ViewGroup -> root.removeAllViews()
             is LayoutNode -> root.removeAll()
             else -> invalidNode(root)
         }
     }
+
+    override fun onEndChanges() {
+        super.onEndChanges()
+        if (root is ViewGroup) {
+            clearInvalidObservations(root)
+        } else if (root is LayoutNode) {
+            (root.owner as? AndroidComposeView)?.clearInvalidObservations()
+        }
+    }
+
+    private fun clearInvalidObservations(viewGroup: ViewGroup) {
+        for (i in 0 until viewGroup.childCount) {
+            val child = viewGroup.getChildAt(i)
+            if (child is AndroidComposeView) {
+                child.clearInvalidObservations()
+            } else if (child is ViewGroup) {
+                clearInvalidObservations(child)
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/ViewInterop.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/ViewInterop.kt
index ab6bb3d..45698e4 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/ViewInterop.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/node/ViewInterop.kt
@@ -29,7 +29,7 @@
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.AndroidOwner
+import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
@@ -95,7 +95,7 @@
  * Builds a [LayoutNode] tree representation for an Android [View].
  * The component nodes will proxy the Compose core calls to the [View].
  */
-@OptIn(ExperimentalLayoutNodeApi::class, InternalInteropApi::class)
+@OptIn(InternalInteropApi::class)
 internal fun AndroidViewHolder.toLayoutNode(): LayoutNode {
     // TODO(soboleva): add layout direction here?
     // TODO(popam): forward pointer input, accessibility, focus
@@ -106,7 +106,7 @@
         .pointerInteropFilter(this)
         .drawBehind {
             drawIntoCanvas { canvas ->
-                (layoutNode.owner as? AndroidOwner)
+                (layoutNode.owner as? AndroidComposeView)
                     ?.drawAndroidView(this@toLayoutNode, canvas.nativeCanvas)
             }
         }.onGloballyPositioned {
@@ -122,11 +122,11 @@
 
     var viewRemovedOnDetach: View? = null
     layoutNode.onAttach = { owner ->
-        (owner as? AndroidOwner)?.addAndroidView(this, layoutNode)
+        (owner as? AndroidComposeView)?.addAndroidView(this, layoutNode)
         if (viewRemovedOnDetach != null) view = viewRemovedOnDetach
     }
     layoutNode.onDetach = { owner ->
-        (owner as? AndroidOwner)?.removeAndroidView(this)
+        (owner as? AndroidComposeView)?.removeAndroidView(this)
         viewRemovedOnDetach = view
         view = null
     }
@@ -168,7 +168,6 @@
     return layoutNode
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 private fun View.layoutAccordingTo(layoutNode: LayoutNode) {
     val position = layoutNode.coordinates.positionInRoot
     val x = position.x.roundToInt()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt
index 9e28215..e236b1ff 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidAmbients.kt
@@ -134,8 +134,8 @@
 
 @Composable
 @OptIn(InternalAnimationApi::class)
-internal fun ProvideAndroidAmbients(owner: AndroidOwner, content: @Composable () -> Unit) {
-    val view = owner.view
+internal fun ProvideAndroidAmbients(owner: AndroidComposeView, content: @Composable () -> Unit) {
+    val view = owner
     val context = view.context
     val scope = rememberCoroutineScope()
     val rootAnimationClock = remember(scope) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt
index 50e46ae..e5aab10 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt
@@ -59,12 +59,12 @@
 import androidx.compose.ui.input.pointer.PointerInputEventProcessor
 import androidx.compose.ui.input.pointer.ProcessResult
 import androidx.compose.ui.layout.RootMeasureBlocks
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNode.UsageByParent
 import androidx.compose.ui.node.MeasureAndLayoutDelegate
 import androidx.compose.ui.node.OwnedLayer
+import androidx.compose.ui.node.Owner
 import androidx.compose.ui.node.OwnerSnapshotObserver
 import androidx.compose.ui.semantics.SemanticsModifierCore
 import androidx.compose.ui.semantics.SemanticsOwner
@@ -81,21 +81,25 @@
 import androidx.compose.ui.viewinterop.InternalInteropApi
 import androidx.core.os.HandlerCompat
 import androidx.core.view.ViewCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelStoreOwner
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.savedstate.SavedStateRegistryOwner
 import androidx.savedstate.ViewTreeSavedStateRegistryOwner
 import java.lang.reflect.Method
 import android.view.KeyEvent as AndroidKeyEvent
 
-@SuppressLint("ViewConstructor")
+@SuppressLint("ViewConstructor", "VisibleForTests")
 @OptIn(
     ExperimentalComposeApi::class,
     ExperimentalFocus::class,
     ExperimentalKeyInput::class,
-    ExperimentalLayoutNodeApi::class
 )
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-internal class AndroidComposeView(context: Context) : ViewGroup(context), AndroidOwner {
+internal class AndroidComposeView(context: Context) :
+    ViewGroup(context), Owner, ViewRootForTest {
 
     override val view: View = this
 
@@ -105,6 +109,7 @@
     private val semanticsModifier = SemanticsModifierCore(
         id = SemanticsModifierCore.generateSemanticsId(),
         mergeDescendants = false,
+        clearAndSetSemantics = false,
         properties = {}
     )
 
@@ -146,10 +151,12 @@
     // TODO(mount): reinstate when coroutines are supported by IR compiler
     // private val ownerScope = CoroutineScope(Dispatchers.Main.immediate + Job())
 
-    // Used for updating the ConfigurationAmbient when configuration changes - consume the
-    // configuration ambient instead of changing this observer if you are writing a component that
-    // adapts to configuration changes.
-    override var configurationChangeObserver: (Configuration) -> Unit = {}
+    /**
+     * Used for updating the ConfigurationAmbient when configuration changes - consume the
+     * configuration ambient instead of changing this observer if you are writing a component
+     * that adapts to configuration changes.
+     */
+    var configurationChangeObserver: (Configuration) -> Unit = {}
 
     private val _autofill = if (autofillSupported()) AndroidAutofill(this, autofillTree) else null
 
@@ -174,13 +181,6 @@
     @OptIn(InternalCoreApi::class)
     override var showLayoutBounds = false
 
-    private val clearInvalidObservations: Runnable = Runnable {
-        if (observationClearRequested) {
-            observationClearRequested = false
-            snapshotObserver.clearInvalidObservations()
-        }
-    }
-
     private var _androidViewsHandler: AndroidViewsHandler? = null
     private val androidViewsHandler: AndroidViewsHandler
         get() {
@@ -229,10 +229,14 @@
     // so that we don't have to continue using try/catch after fails once.
     private var isRenderNodeCompatible = true
 
-    override var viewTreeOwners: AndroidOwner.ViewTreeOwners? = null
+    /**
+     * Current [ViewTreeOwners]. Use [setOnViewTreeOwnersAvailable] if you want to
+     * execute your code when the object will be created.
+     */
+    var viewTreeOwners: ViewTreeOwners? = null
         private set
 
-    private var onViewTreeOwnersAvailable: ((AndroidOwner.ViewTreeOwners) -> Unit)? = null
+    private var onViewTreeOwnersAvailable: ((ViewTreeOwners) -> Unit)? = null
 
     // executed when the layout pass has been finished. as a result of it our view could be moved
     // inside the window (we are interested not only in the event when our parent positioned us
@@ -283,7 +287,7 @@
         isFocusableInTouchMode = true
         clipChildren = false
         ViewCompat.setAccessibilityDelegate(this, accessibilityDelegate)
-        AndroidOwner.onAndroidOwnerCreatedCallback?.invoke(this)
+        ViewRootForTest.onViewCreatedCallback?.invoke(this)
         root.attach(this)
     }
 
@@ -325,27 +329,56 @@
     }
 
     fun requestClearInvalidObservations() {
-        val handler = handler
-        if (!observationClearRequested && handler != null) {
-            observationClearRequested = true
-            handler.postAtFrontOfQueue(clearInvalidObservations)
+        observationClearRequested = true
+    }
+
+    internal fun clearInvalidObservations() {
+        if (observationClearRequested) {
+            snapshotObserver.clearInvalidObservations()
+            observationClearRequested = false
+        }
+        val childAndroidViews = _androidViewsHandler
+        if (childAndroidViews != null) {
+            clearChildInvalidObservations(childAndroidViews)
         }
     }
 
+    private fun clearChildInvalidObservations(viewGroup: ViewGroup) {
+        for (i in 0 until viewGroup.childCount) {
+            val child = viewGroup.getChildAt(i)
+            if (child is AndroidComposeView) {
+                child.clearInvalidObservations()
+            } else if (child is ViewGroup) {
+                clearChildInvalidObservations(child)
+            }
+        }
+    }
+
+    /**
+     * Called to inform the owner that a new Android [View] was [attached][Owner.onAttach]
+     * to the hierarchy.
+     */
     @OptIn(InternalInteropApi::class)
-    override fun addAndroidView(view: AndroidViewHolder, layoutNode: LayoutNode) {
+    fun addAndroidView(view: AndroidViewHolder, layoutNode: LayoutNode) {
         androidViewsHandler.layoutNode[view] = layoutNode
         androidViewsHandler.addView(view)
     }
 
+    /**
+     * Called to inform the owner that an Android [View] was [detached][Owner.onDetach]
+     * from the hierarchy.
+     */
     @OptIn(InternalInteropApi::class)
-    override fun removeAndroidView(view: AndroidViewHolder) {
+    fun removeAndroidView(view: AndroidViewHolder) {
         androidViewsHandler.removeView(view)
         androidViewsHandler.layoutNode.remove(view)
     }
 
+    /**
+     * Called to ask the owner to draw a child Android [View] to [canvas].
+     */
     @OptIn(InternalInteropApi::class)
-    override fun drawAndroidView(view: AndroidViewHolder, canvas: android.graphics.Canvas) {
+    fun drawAndroidView(view: AndroidViewHolder, canvas: android.graphics.Canvas) {
         androidViewsHandler.drawView(view, canvas)
     }
 
@@ -504,7 +537,11 @@
         }
     }
 
-    override fun setOnViewTreeOwnersAvailable(callback: (AndroidOwner.ViewTreeOwners) -> Unit) {
+    /**
+     * The callback to be executed when [viewTreeOwners] is created and not-null anymore.
+     * Note that this callback will be fired inline when it is already available
+     */
+    fun setOnViewTreeOwnersAvailable(callback: (ViewTreeOwners) -> Unit) {
         val viewTreeOwners = viewTreeOwners
         if (viewTreeOwners != null) {
             callback(viewTreeOwners)
@@ -554,7 +591,7 @@
                     "Composed into the View which doesn't propagate" +
                         "ViewTreeSavedStateRegistryOwner!"
                 )
-            val viewTreeOwners = AndroidOwner.ViewTreeOwners(
+            val viewTreeOwners = ViewTreeOwners(
                 lifecycleOwner = lifecycleOwner,
                 viewModelStoreOwner = viewModelStoreOwner,
                 savedStateRegistryOwner = savedStateRegistryOwner
@@ -576,14 +613,6 @@
         }
         viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
         viewTreeObserver.removeOnScrollChangedListener(scrollChangedListener)
-
-        // In case of benchmarks, the handler callbacks will never get executed as benchmarks block
-        // the main thread. However this callback holds references that point to this view which
-        // effectively prevents it from being garbage collected in benchmarks.
-        if (observationClearRequested) {
-            observationClearRequested = false
-            handler.removeCallbacks(clearInvalidObservations)
-        }
     }
 
     override fun onProvideAutofillVirtualStructure(structure: ViewStructure?, flags: Int) {
@@ -646,6 +675,10 @@
         return accessibilityDelegate.dispatchHoverEvent(event)
     }
 
+    override val isLifecycleInResumedState: Boolean
+        get() = viewTreeOwners?.lifecycleOwner
+            ?.lifecycle?.currentState == Lifecycle.State.RESUMED
+
     companion object {
         private var systemPropertiesClass: Class<*>? = null
         private var getBooleanMethod: Method? = null
@@ -666,6 +699,24 @@
             false
         }
     }
+
+    /**
+     * Combines objects populated via ViewTree*Owner
+     */
+    class ViewTreeOwners(
+        /**
+         * The [LifecycleOwner] associated with this owner.
+         */
+        val lifecycleOwner: LifecycleOwner,
+        /**
+         * The [ViewModelStoreOwner] associated with this owner.
+         */
+        val viewModelStoreOwner: ViewModelStoreOwner,
+        /**
+         * The [SavedStateRegistryOwner] associated with this owner.
+         */
+        val savedStateRegistryOwner: SavedStateRegistryOwner
+    )
 }
 
 /**
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidOwner.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidOwner.kt
deleted file mode 100644
index 2cc6d44..0000000
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidOwner.kt
+++ /dev/null
@@ -1,117 +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.platform
-
-import android.content.res.Configuration
-import android.graphics.Canvas
-import android.view.View
-import androidx.annotation.RestrictTo
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
-import androidx.compose.ui.node.LayoutNode
-import androidx.compose.ui.node.Owner
-import androidx.compose.ui.viewinterop.AndroidViewHolder
-import androidx.compose.ui.viewinterop.InternalInteropApi
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.ViewModelStoreOwner
-import androidx.savedstate.SavedStateRegistryOwner
-import org.jetbrains.annotations.TestOnly
-
-/**
- * Interface to be implemented by [Owner]s able to handle Android View specific functionality.
- */
-interface AndroidOwner : Owner {
-
-    /**
-     * The view backing this Owner.
-     */
-    val view: View
-
-    /**
-     * Called to inform the owner that a new Android [View] was [attached][Owner.onAttach]
-     * to the hierarchy.
-     */
-    @OptIn(InternalInteropApi::class)
-    @ExperimentalLayoutNodeApi
-    fun addAndroidView(view: AndroidViewHolder, layoutNode: LayoutNode)
-
-    /**
-     * Called to inform the owner that an Android [View] was [detached][Owner.onDetach]
-     * from the hierarchy.
-     */
-    @OptIn(InternalInteropApi::class)
-    fun removeAndroidView(view: AndroidViewHolder)
-
-    /**
-     * Called to ask the owner to draw a child Android [View] to [canvas].
-     */
-    @OptIn(InternalInteropApi::class)
-    fun drawAndroidView(view: AndroidViewHolder, canvas: Canvas)
-
-    /**
-     * Used for updating the ConfigurationAmbient when configuration changes - consume the
-     * configuration ambient instead of changing this observer if you are writing a component
-     * that adapts to configuration changes.
-     */
-    var configurationChangeObserver: (Configuration) -> Unit
-
-    /**
-     * Current [ViewTreeOwners]. Use [setOnViewTreeOwnersAvailable] if you want to
-     * execute your code when the object will be created.
-     */
-    val viewTreeOwners: ViewTreeOwners?
-
-    /**
-     * The callback to be executed when [viewTreeOwners] is created and not-null anymore.
-     * Note that this callback will be fired inline when it is already available
-     */
-    fun setOnViewTreeOwnersAvailable(callback: (ViewTreeOwners) -> Unit)
-
-    /**
-     * Called to invalidate the Android [View] sub-hierarchy handled by this Owner.
-     */
-    fun invalidateDescendants()
-
-    /** @suppress */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    companion object {
-        /**
-         * Called after an [AndroidOwner] is created. Used by AndroidComposeTestRule to keep
-         * track of all attached [AndroidComposeView]s. Not to be set or used by any other
-         * component.
-         */
-        var onAndroidOwnerCreatedCallback: ((AndroidOwner) -> Unit)? = null
-            @TestOnly
-            set
-    }
-
-    /**
-     * Combines objects populated via ViewTree*Owner
-     */
-    class ViewTreeOwners(
-        /**
-         * The [LifecycleOwner] associated with this owner.
-         */
-        val lifecycleOwner: LifecycleOwner,
-        /**
-         * The [ViewModelStoreOwner] associated with this owner.
-         */
-        val viewModelStoreOwner: ViewModelStoreOwner,
-        /**
-         * The [SavedStateRegistryOwner] associated with this owner.
-         */
-        val savedStateRegistryOwner: SavedStateRegistryOwner
-    )
-}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewsHandler.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewsHandler.kt
index aab323a..34b1cd2 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewsHandler.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewsHandler.kt
@@ -13,8 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:OptIn(ExperimentalLayoutNodeApi::class)
-
 package androidx.compose.ui.platform
 
 import android.annotation.SuppressLint
@@ -24,7 +22,6 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewParent
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNode.LayoutState.NeedsRemeasure
 import androidx.compose.ui.viewinterop.AndroidViewHolder
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewRootForTest.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewRootForTest.kt
new file mode 100644
index 0000000..b5c4e4e
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewRootForTest.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.platform
+
+import android.view.View
+import androidx.compose.ui.node.Owner
+import androidx.compose.ui.util.annotation.VisibleForTesting
+
+/**
+ * The marker interface to be implemented by the [View] backing the composition.
+ * To be used in tests.
+ */
+@VisibleForTesting
+// TODO(b/174747742) Introduce RootForTest and extend it instead of Owner
+interface ViewRootForTest : Owner {
+
+    /**
+     * The view backing this Owner.
+     */
+    val view: View
+
+    /**
+     * Returns true when the associated LifecycleOwner is in the resumed state
+     */
+    val isLifecycleInResumedState: Boolean
+
+    /**
+     * Whether the Owner has pending layout work.
+     */
+    val hasPendingMeasureOrLayout: Boolean
+
+    /**
+     * Called to invalidate the Android [View] sub-hierarchy handled by this [View].
+     */
+    fun invalidateDescendants()
+
+    companion object {
+        /**
+         * Called after an View implementing [ViewRootForTest] is created. Used by
+         * AndroidComposeTestRule to keep track of all attached ComposeViews. Not to be
+         * set or used by any other component.
+         */
+        @VisibleForTesting
+        var onViewCreatedCallback: ((ViewRootForTest) -> Unit)? = null
+    }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
index 562a02c..9caff49 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
@@ -35,7 +35,6 @@
 import androidx.compose.runtime.emptyContent
 import androidx.compose.runtime.tooling.InspectionTables
 import androidx.compose.ui.R
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.UiApplier
 import androidx.lifecycle.Lifecycle
@@ -111,8 +110,8 @@
 // nextFrame() inside recompose() doesn't really start a new frame, but a new subframe
 // instead.
 @MainThread
-@OptIn(ExperimentalComposeApi::class, ExperimentalLayoutNodeApi::class)
-internal actual fun actualSubcomposeInto(
+@OptIn(ExperimentalComposeApi::class)
+internal actual fun subcomposeInto(
     container: LayoutNode,
     parent: CompositionReference,
     composable: @Composable () -> Unit
@@ -140,9 +139,9 @@
     content: @Composable () -> Unit
 ): Composition {
     GlobalSnapshotManager.ensureStarted()
-    val composeView: AndroidOwner = window.decorView
+    val composeView: AndroidComposeView = window.decorView
         .findViewById<ViewGroup>(android.R.id.content)
-        .getChildAt(0) as? AndroidOwner
+        .getChildAt(0) as? AndroidComposeView
         ?: AndroidComposeView(this).also {
             setContentView(it.view, DefaultLayoutParams)
         }
@@ -169,7 +168,7 @@
     GlobalSnapshotManager.ensureStarted()
     val composeView =
         if (childCount > 0) {
-            getChildAt(0) as? AndroidOwner
+            getChildAt(0) as? AndroidComposeView
         } else {
             removeAllViews(); null
         } ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
@@ -178,12 +177,12 @@
 
 @OptIn(InternalComposeApi::class)
 private fun doSetContent(
-    owner: AndroidOwner,
+    owner: AndroidComposeView,
     parent: CompositionReference,
     content: @Composable () -> Unit
 ): Composition {
     if (inspectionWanted(owner)) {
-        owner.view.setTag(
+        owner.setTag(
             R.id.inspection_slot_table_set,
             Collections.newSetFromMap(WeakHashMap<SlotTable, Boolean>())
         )
@@ -213,36 +212,40 @@
 }
 
 private class WrappedComposition(
-    val owner: AndroidOwner,
+    val owner: AndroidComposeView,
     val original: Composition
 ) : Composition, LifecycleEventObserver {
 
     private var disposed = false
     private var addedToLifecycle: Lifecycle? = null
-    private var contentWaitingForCreated: @Composable () -> Unit = emptyContent()
+    private var lastContent: @Composable () -> Unit = emptyContent()
 
     @OptIn(InternalComposeApi::class)
     override fun setContent(content: @Composable () -> Unit) {
         owner.setOnViewTreeOwnersAvailable {
             if (!disposed) {
                 val lifecycle = it.lifecycleOwner.lifecycle
+                lastContent = content
                 if (addedToLifecycle == null) {
-                    lifecycle.addObserver(this)
                     addedToLifecycle = lifecycle
-                }
-                if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
+                    // this will call ON_CREATE synchronously if we already created
+                    lifecycle.addObserver(this)
+                } else if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
                     original.setContent {
                         @Suppress("UNCHECKED_CAST")
                         val inspectionTable =
-                            owner.view.getTag(R.id.inspection_slot_table_set) as?
+                            owner.getTag(R.id.inspection_slot_table_set) as?
                                 MutableSet<SlotTable>
-                        inspectionTable?.add(currentComposer.slotTable)
+                                ?: (owner.parent as? View)?.getTag(R.id.inspection_slot_table_set)
+                                    as? MutableSet<SlotTable>
+                        if (inspectionTable != null) {
+                            inspectionTable.add(currentComposer.slotTable)
+                            currentComposer.collectParameterInformation()
+                        }
                         Providers(InspectionTables provides inspectionTable) {
                             ProvideAndroidAmbients(owner, content)
                         }
                     }
-                } else {
-                    contentWaitingForCreated = content
                 }
             }
         }
@@ -264,8 +267,7 @@
             dispose()
         } else if (event == Lifecycle.Event.ON_CREATE) {
             if (!disposed) {
-                setContent(contentWaitingForCreated)
-                contentWaitingForCreated = emptyContent()
+                setContent(lastContent)
             }
         }
     }
@@ -286,6 +288,6 @@
  *
  * Instead check if the attributeSourceResourceMap is not empty.
  */
-private fun inspectionWanted(owner: AndroidOwner): Boolean =
+private fun inspectionWanted(owner: AndroidComposeView): Boolean =
     Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
-        owner.view.attributeSourceResourceMap.isNotEmpty()
+        owner.attributeSourceResourceMap.isNotEmpty()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt
index c377937..b8ad9f3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt
@@ -30,6 +30,8 @@
  *
  * Note: This API is transient and will be likely removed for encouraging async resource loading.
  *
+ * For loading generic loading of rasterized or vector assets see [painterResource]
+ *
  * @param id the resource identifier
  * @return the decoded image data associated with the resource
  */
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/PainterResources.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/PainterResources.kt
new file mode 100644
index 0000000..d8af23f
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/PainterResources.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.res
+
+import android.content.res.Resources
+import android.util.TypedValue
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.imageFromResource
+import androidx.compose.ui.graphics.painter.ImagePainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.graphics.vector.VectorPainter
+import androidx.compose.ui.graphics.vector.compat.seekToStartTag
+import androidx.compose.ui.platform.AmbientContext
+
+/**
+ * Create a [Painter] from an Android resource id. This can load either an instance of
+ * [ImagePainter] or [VectorPainter] for [ImageBitmap] based assets or vector based assets
+ * respectively. The resources with the given id must point to either fully rasterized
+ * images (ex. PNG or JPG files) or VectorDrawable xml assets. API based xml Drawables
+ * are not supported here.
+ *
+ * Example:
+ * @sample androidx.compose.ui.samples.PainterResourceSample
+ *
+ * Alternative Drawable implementations can be used with compose by calling
+ * [drawIntoCanvas] and drawing with the Android framework canvas provided through [nativeCanvas]
+ *
+ * Example:
+ * @sample androidx.compose.ui.samples.AndroidDrawableInDrawScopeSample
+ *
+ * @param id Resources object to query the image file from
+ *
+ * @return [Painter] used for drawing the loaded resource
+ */
+@Composable
+fun painterResource(@DrawableRes id: Int): Painter {
+    val context = AmbientContext.current
+    val res = context.resources
+    val value = remember { TypedValue() }
+    res.getValue(id, value, true)
+    val path = value.string
+    // Assume .xml suffix implies loading a VectorDrawable resource
+    return if (path?.endsWith(".xml") == true) {
+        val imageVector = remember(path, id) {
+            loadVectorResource(context.theme, res, id)
+        }
+        rememberVectorPainter(imageVector)
+    } else {
+        // Otherwise load the bitmap resource
+        val imageBitmap = remember(path, id) {
+            loadImageBitmapResource(res, id)
+        }
+        ImagePainter(imageBitmap)
+    }
+}
+
+/**
+ * Helper method to validate that the xml resource is a vector drawable then load
+ * the ImageVector. Because this throws exceptions we cannot have this implementation as part of
+ * the composable implementation it is invoked in.
+ */
+private fun loadVectorResource(theme: Resources.Theme, res: Resources, id: Int): ImageVector {
+    @Suppress("ResourceType") val parser = res.getXml(id)
+    if (parser.seekToStartTag().name != "vector") {
+        throw IllegalArgumentException(errorMessage)
+    }
+    return loadVectorResourceInner(theme, res, parser)
+}
+
+/**
+ * Helper method to validate the asset resource is a supported resource type and returns
+ * an ImageBitmap resource. Because this throws exceptions we cannot have this implementation
+ * as part of the composable implementation it is invoked in.
+ */
+private fun loadImageBitmapResource(res: Resources, id: Int): ImageBitmap {
+    try {
+        return imageFromResource(res, id)
+    } catch (throwable: Throwable) {
+        throw IllegalArgumentException(errorMessage)
+    }
+}
+
+private const val errorMessage =
+    "Only VectorDrawables and rasterized asset types are supported ex. PNG, JPG"
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.kt
index 8f59986..3fd252b 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.kt
@@ -16,8 +16,8 @@
 
 package androidx.compose.ui.res
 
-import android.annotation.SuppressLint
 import android.content.res.Resources
+import android.content.res.XmlResourceParser
 import android.util.TypedValue
 import android.util.Xml
 import androidx.annotation.DrawableRes
@@ -39,6 +39,8 @@
  * based off of it's dimensions appropriately
  *
  * Note: This API is transient and will be likely removed for encouraging async resource loading.
+ *
+ * For loading generic loading of rasterized or vector assets see [painterResource]
  */
 @Composable
 fun vectorResource(@DrawableRes id: Int): ImageVector {
@@ -57,6 +59,8 @@
  * [PendingResource]. Once the loading finishes, recompose is scheduled and this function will
  * return deferred vector drawable resource with [LoadedResource] or [FailedResource].
  *
+ * For loading generic loading of rasterized or vector assets see [painterResource]
+ *
  * @param id the resource identifier
  * @param pendingResource an optional resource to be used during loading instead.
  * @param failedResource an optional resource to be used if resource loading failed.
@@ -90,11 +94,23 @@
 internal fun loadVectorResource(
     theme: Resources.Theme? = null,
     res: Resources,
-    resId: Int
+    resId: Int,
+): ImageVector =
+    loadVectorResourceInner(theme, res, res.getXml(resId).apply { seekToStartTag() })
+
+/**
+ * Helper method that parses a vector asset from the given [XmlResourceParser] position.
+ * This method assumes the parser is already been positioned to the start tag
+ */
+@Throws(XmlPullParserException::class)
+@SuppressWarnings("RestrictedApi")
+internal fun loadVectorResourceInner(
+    theme: Resources.Theme? = null,
+    res: Resources,
+    parser: XmlResourceParser
 ): ImageVector {
-    @SuppressLint("ResourceType") val parser = res.getXml(resId)
     val attrs = Xml.asAttributeSet(parser)
-    val builder = parser.seekToStartTag().createVectorImageBuilder(res, theme, attrs)
+    val builder = parser.createVectorImageBuilder(res, theme, attrs)
 
     var nestedGroups = 0
     while (!parser.isAtEnd()) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.kt
index a5d6337..e12e11f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.kt
@@ -55,11 +55,17 @@
 /**
  * Android specific properties to configure a dialog.
  *
- * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the dialog's
- * window.
+ * @property dismissOnClickOutside whether the dialog can be dismissed by pressing the back button.
+ * If true, pressing the back button will call onDismissRequest.
+ * @property dismissOnClickOutside whether the dialog can be dismissed by clicking outside the
+ * dialog's bounds. If true, clicking outside the dialog will call onDismissRequest.
+ * @property securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the
+ * dialog's window.
  */
 @Immutable
 data class AndroidDialogProperties(
+    val dismissOnBackPress: Boolean = true,
+    val dismissOnClickOutside: Boolean = true,
     val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit
 ) : DialogProperties
 
@@ -88,7 +94,7 @@
     val density = AmbientDensity.current
 
     val dialog = remember(view, density) { DialogWrapper(view, density) }
-    dialog.onCloseRequest = onDismissRequest
+    dialog.onDismissRequest = onDismissRequest
     remember(properties) { dialog.setProperties(properties) }
 
     onActive {
@@ -138,10 +144,11 @@
      */
     ContextThemeWrapper(composeView.context, R.style.DialogWindowTheme)
 ) {
-    lateinit var onCloseRequest: () -> Unit
+    lateinit var onDismissRequest: () -> Unit
 
     private val dialogLayout: DialogLayout
     private var composition: Composition? = null
+    private var properties: AndroidDialogProperties = AndroidDialogProperties()
 
     private val maxSupportedElevation = 30.dp
 
@@ -209,15 +216,13 @@
         )
     }
 
-    fun setProperties(properties: DialogProperties?) {
-        if (properties != null && properties is AndroidDialogProperties) {
-            setSecureFlagEnabled(
-                properties.securePolicy
-                    .shouldApplySecureFlag(composeView.isFlagSecureEnabled())
-            )
-        } else {
-            setSecureFlagEnabled(composeView.isFlagSecureEnabled())
+    fun setProperties(newProperties: DialogProperties?) {
+        if (newProperties is AndroidDialogProperties) {
+            properties = newProperties
         }
+        setSecureFlagEnabled(
+            properties.securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled())
+        )
     }
 
     fun disposeComposition() {
@@ -226,8 +231,8 @@
 
     override fun onTouchEvent(event: MotionEvent): Boolean {
         val result = super.onTouchEvent(event)
-        if (result) {
-            onCloseRequest()
+        if (result && properties.dismissOnClickOutside) {
+            onDismissRequest()
         }
 
         return result
@@ -239,7 +244,9 @@
     }
 
     override fun onBackPressed() {
-        onCloseRequest()
+        if (properties.dismissOnBackPress) {
+            onDismissRequest()
+        }
     }
 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt
index 30655f4..d0335e3 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusNodeUtils.kt
@@ -18,14 +18,12 @@
 
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.ModifiedFocusNode
 import androidx.compose.ui.util.fastForEach
 
 internal val FOCUS_TAG = "Compose Focus"
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal fun LayoutNode.focusableChildren2(): List<ModifiedFocusNode> {
     val focusableChildren = mutableListOf<ModifiedFocusNode>()
     // TODO(b/152529395): Write a test for LayoutNode.focusableChildren(). We were calling the wrong
@@ -43,9 +41,6 @@
  *
  * @param queue a mutable list used as a queue for breadth-first search.
  */
-@OptIn(
-    ExperimentalLayoutNodeApi::class
-)
 internal fun LayoutNode.searchChildrenForFocusNode(
     queue: MutableVector<LayoutNode> = mutableVectorOf()
 ): ModifiedFocusNode? {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilter.kt
index 39bb158..5d2b6f4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilter.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture
 
 import androidx.compose.runtime.remember
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilter.kt
index 5c386f1..141b8ec 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilter.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture
 
 import androidx.compose.runtime.remember
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt
index 430326b..2fa2b6b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture
 
 import androidx.compose.runtime.remember
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/TapGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/TapGestureFilter.kt
index 57e5807..e3aba9e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/TapGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/TapGestureFilter.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture
 
 import androidx.compose.runtime.remember
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/customevents/DelayUpEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/customevents/DelayUpEvent.kt
index 10ea0f1..b2787cd 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/customevents/DelayUpEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/customevents/DelayUpEvent.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.gesture.customevents
 
 import androidx.compose.ui.gesture.DoubleTapGestureFilter
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.gesture.TapGestureFilter
 import androidx.compose.ui.input.pointer.CustomEvent
 import androidx.compose.ui.input.pointer.PointerId
@@ -40,7 +39,6 @@
  * @param pointers The pointers whose up events are being requested to be delayed.
  */
 @Suppress("EqualsOrHashCode")
-@ExperimentalPointerInput
 data class DelayUpEvent(var message: DelayUpMessage, val pointers: Set<PointerId>) : CustomEvent {
 
     // Only generating hash code with immutable property.
@@ -52,7 +50,6 @@
 /**
  * The types of messages that can be dispatched.
  */
-@ExperimentalPointerInput
 enum class DelayUpMessage {
     /**
      * Reports that future "up events" should not result in any normally related callbacks at
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollDelegatingWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollDelegatingWrapper.kt
new file mode 100644
index 0000000..6c29a8d
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollDelegatingWrapper.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.gesture.nestedscroll
+
+import androidx.compose.runtime.collection.MutableVector
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.node.DelegatingLayoutNodeWrapper
+import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.node.LayoutNodeWrapper
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.minus
+import androidx.compose.ui.unit.plus
+
+internal class NestedScrollDelegatingWrapper(
+    wrapped: LayoutNodeWrapper,
+    nestedScrollModifier: NestedScrollModifier
+) : DelegatingLayoutNodeWrapper<NestedScrollModifier>(wrapped, nestedScrollModifier) {
+
+    // reference to the parent connection to properly dispatch or provide to children when detached
+    private var parentConnection: NestedScrollConnection? = null
+        set(value) {
+            modifier.dispatcher.parent = value
+            childScrollConnection.parent = value ?: NoOpConnection
+            field = value
+        }
+
+    // save last modifier until the next onModifierChanged() call to understand if we got new
+    // connection or a new dispatcher, therefore we need to update self and our children
+    private var lastModifier: NestedScrollModifier? = null
+
+    override fun onModifierChanged() {
+        super.onModifierChanged()
+        childScrollConnection.self = modifier.connection
+        modifier.dispatcher.parent = parentConnection
+        refreshSelfIfNeeded()
+    }
+
+    override var modifier: NestedScrollModifier
+        get() = super.modifier
+        set(value) {
+            lastModifier = super.modifier
+            super.modifier = value
+        }
+
+    override fun attach() {
+        super.attach()
+        refreshSelfIfNeeded()
+    }
+
+    override fun detach() {
+        super.detach()
+        refreshChildrenWithParentConnection(parentConnection)
+        lastModifier = null
+    }
+
+    override fun findPreviousNestedScrollWrapper() = this
+
+    override fun findNextNestedScrollWrapper() = this
+
+    private val childScrollConnection = ParentWrapperNestedScrollConnection(
+        parent = parentConnection ?: NoOpConnection,
+        self = nestedScrollModifier.connection
+    )
+
+    private fun refreshSelfIfNeeded() {
+        val localLastModifier = lastModifier
+        val modifierChanged = localLastModifier == null ||
+            localLastModifier.connection !== modifier.connection ||
+            localLastModifier.dispatcher !== modifier.dispatcher
+        if (modifierChanged && isAttached) {
+            parentConnection = super.findPreviousNestedScrollWrapper()?.childScrollConnection
+            refreshChildrenWithParentConnection(childScrollConnection)
+            lastModifier = modifier
+        }
+    }
+
+    /**
+     * Supply new parent connection for children. Initially children can do it themselves, but
+     * after runtime nestedscroll graph changes parents need to update their children.
+     *
+     * This is O(n) operation, so call only when parent really changes (connection changes,
+     * detach, attach, etc)
+     */
+    private fun refreshChildrenWithParentConnection(newParent: NestedScrollConnection?) {
+        nestedScrollChildrenResult.clear()
+        val nextNestedScrollWrapper = wrapped.findNextNestedScrollWrapper()
+        if (nextNestedScrollWrapper != null) {
+            nestedScrollChildrenResult.add(nextNestedScrollWrapper)
+        } else {
+            loopChildrenForNestedScroll(layoutNode._children)
+        }
+        nestedScrollChildrenResult.forEach {
+            it.parentConnection = newParent
+        }
+    }
+
+    private fun loopChildrenForNestedScroll(children: MutableVector<LayoutNode>) {
+        children.forEach { child ->
+            val nestedScrollChild =
+                child.outerLayoutNodeWrapper.findNextNestedScrollWrapper()
+            if (nestedScrollChild != null) {
+                nestedScrollChildrenResult.add(nestedScrollChild)
+            } else {
+                loopChildrenForNestedScroll(child._children)
+            }
+        }
+    }
+
+    // do not use directly, this is only for optimization.
+    // Populated and returned by findNestedScrollChildren.
+    private val nestedScrollChildrenResult = MutableVector<NestedScrollDelegatingWrapper>()
+}
+
+/**
+ * Parent-child binding contract. This wrapper guarantees pre-scroll/scroll/pre-fling/fling call
+ * order in the nested scroll chain.
+ */
+private class ParentWrapperNestedScrollConnection(
+    var parent: NestedScrollConnection,
+    var self: NestedScrollConnection
+) : NestedScrollConnection {
+
+    override fun onPreScroll(
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset {
+        val parentPreConsumed = parent.onPreScroll(available, source)
+        val selfPreConsumed = self.onPreScroll(available - parentPreConsumed, source)
+        return parentPreConsumed + selfPreConsumed
+    }
+
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset {
+        val selfConsumed = self.onPostScroll(consumed, available, source)
+        val parentConsumed =
+            parent.onPostScroll(consumed + selfConsumed, available - selfConsumed, source)
+        return selfConsumed + parentConsumed
+    }
+
+    override fun onPreFling(available: Velocity): Velocity {
+        val parentPreConsumed = parent.onPreFling(available)
+        val selfPreConsumed = self.onPreFling(available - parentPreConsumed)
+        return parentPreConsumed + selfPreConsumed
+    }
+
+    override fun onPostFling(
+        consumed: Velocity,
+        available: Velocity,
+        onFinished: (Velocity) -> Unit
+    ) {
+        val selfEnd = { selfConsumed: Velocity ->
+            val parentEnd = { parentConsumed: Velocity ->
+                onFinished.invoke(selfConsumed + parentConsumed)
+            }
+            parent.onPostFling(
+                consumed + selfConsumed,
+                available - selfConsumed,
+                parentEnd
+            )
+        }
+        self.onPostFling(consumed, available, selfEnd)
+    }
+}
+
+/**
+ * No-op parent that consumed nothing. Should be gone by b/174348612
+ */
+private val NoOpConnection: NestedScrollConnection = object : NestedScrollConnection {
+
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset =
+        Offset.Zero
+
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset =
+        Offset.Zero
+
+    override fun onPreFling(available: Velocity): Velocity = Velocity.Zero
+
+    override fun onPostFling(
+        consumed: Velocity,
+        available: Velocity,
+        onFinished: (Velocity) -> Unit
+    ) {
+        onFinished.invoke(Velocity.Zero)
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollModifier.kt
new file mode 100644
index 0000000..fbbb177
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/nestedscroll/NestedScrollModifier.kt
@@ -0,0 +1,296 @@
+/*
+ * 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.gesture.nestedscroll
+
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Velocity
+
+/**
+ * A [Modifier.Element] that represents nested scroll node in the hierarchy
+ */
+internal interface NestedScrollModifier : Modifier.Element {
+
+    /**
+     * Nested scroll events dispatcher to notify nested scrolling system about scroll events.
+     * This is to be used by the nodes that are scrollable themselves to notify
+     * [NestedScrollConnection]s in the tree.
+     *
+     * Note: The [connection] passed to the [NestedScrollModifier] doesn't count as an ancestor
+     * since it's the node itself
+     */
+    val dispatcher: NestedScrollDispatcher
+
+    /**
+     * Nested scroll connection to participate in the nested scroll events chain. Implementing
+     * this connection allows to react on the nested scroll related events and influence
+     * scrolling descendants and ascendants
+     */
+    val connection: NestedScrollConnection
+}
+
+/**
+ * Interface to connect to the nested scroll system.
+ *
+ * Pass this connection to the [nestedScroll] modifier to participate in the nested scroll
+ * hierarchy and to receive nested scroll events when they are dispatched by the scrolling child
+ * (scrolling child - the element that actually receives scrolling events and dispatches them via
+ * [NestedScrollDispatcher]).
+ *
+ * @see NestedScrollDispatcher to learn how to dispatch nested scroll events to become a
+ * scrolling child
+ * @see nestedScroll to attach this connection to the nested scroll system
+ */
+interface NestedScrollConnection {
+
+    /**
+     * Pre scroll event chain. Called by children to allow parents to consume a portion of a drag
+     * event beforehand
+     *
+     * @param available the delta available to consume for pre scroll
+     * @param source the source of the scroll event
+     *
+     * @see NestedScrollSource
+     *
+     * @return the amount this connection consumed
+     */
+    fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = Offset.Zero
+
+    /**
+     * Post scroll event pass. This pass occurs when the dispatching (scrolling) descendant made
+     * their consumption and notifies ancestors with what's left for them to consume.
+     *
+     * @param consumed the amount that was consumed by all nested scroll nodes below the hierarchy
+     * @param available the amount of delta available for this connection to consume
+     * @param source source of the scroll
+     *
+     * @see NestedScrollSource
+     *
+     * @return the amount that was consumed by this connection
+     */
+    fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset = Offset.Zero
+
+    /**
+     * Pre fling event chain. Called by children when they are about to perform fling to
+     * allow parents to intercept and consume part of the initial velocity
+     *
+     * @param available the velocity which is available to pre consume and with which the child
+     * is about to fling
+     *
+     * @return the amount this connection wants to consume and take from the child
+     */
+    fun onPreFling(available: Velocity): Velocity = Velocity.Zero
+
+    /**
+     * Post fling event chain. Called by the child when it is finished flinging (and sending
+     * [onPreScroll] & [onPostScroll] events)
+     *
+     * @param consumed the amount of velocity consumed by the child
+     * @param available the amount of velocity left for a parent to fling after the child (if
+     * desired)
+     * @param onFinished callback to be called when this connection finished flinging, to
+     * be called with the amount of velocity consumed by the fling operation. This callback is
+     * crucial to be called in order to ensure nodes above will receive their [onPostFling].
+     */
+    // TODO: remove notifySelfFinish when b/174485541
+    fun onPostFling(
+        consumed: Velocity,
+        available: Velocity,
+        onFinished: (Velocity) -> Unit
+    ) {
+        onFinished(Velocity.Zero)
+    }
+}
+
+/**
+ * Nested scroll events dispatcher to notify the nested scroll system about the scrolling events
+ * that are happening on the element.
+ *
+ * If the element/modifier itself is able to receive scroll events (from the touch, fling,
+ * mouse, etc) and it would like to respect nested scrolling by notifying elements above, it should
+ * properly dispatch nested scroll events when being scrolled
+ *
+ * It is important to dispatch these events at the right time, provide valid information to the
+ * parents and react to the feedback received from them in order to provide good user experience
+ * with other nested scrolling nodes.
+ *
+ * @see nestedScroll for the reference of the nested scroll process and more details
+ * @see NestedScrollConnection to connect to the nested scroll system
+ */
+class NestedScrollDispatcher {
+
+    /**
+     * Parent to be set when attached to nested scrolling chain. `null` is valid and means there no
+     * nested scrolling parent above
+     */
+    internal var parent: NestedScrollConnection? = null
+
+    /**
+     * Dispatch pre scroll pass. This triggers [NestedScrollConnection.onPreScroll] on all the
+     * ancestors giving them possibility to pre-consume delta if they desire so.
+     *
+     * @param available the delta arrived from a scroll event
+     * @param source the source of the scroll event
+     *
+     * @return total delta that is pre-consumed by all ancestors in the chain. This delta is
+     * unavailable for this node to consume, so it should adjust the consumption accordingly
+     */
+    fun dispatchPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        return parent?.onPreScroll(available, source) ?: Offset.Zero
+    }
+
+    /**
+     * Dispatch nested post-scrolling pass. This triggers [NestedScrollConnection.onPostScroll] on
+     * all the ancestors giving them possibility to react of the scroll deltas that are left
+     * after the dispatching node itself and other [NestedScrollConnection]s below consumed the
+     * desired amount.
+     *
+     * @param consumed the amount that this node consumed already
+     * @param available the amount of delta left for ancestors
+     * @param source source of the scroll
+     *
+     * @return the amount of scroll that was consumed by all ancestors
+     */
+    fun dispatchPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset {
+        return parent?.onPostScroll(consumed, available, source) ?: Offset.Zero
+    }
+
+    /**
+     * Dispatch pre fling pass. This triggers [NestedScrollConnection.onPreFling] on all the
+     * ancestors giving them a possibility to react on the fling that is about to happen and
+     * consume part of the velocity.
+     *
+     * @param available velocity from the scroll evens that this node is about to fling with
+     *
+     * @return total velocity that is pre-consumed by all ancestors in the chain. This velocity is
+     * unavailable for this node to consume, so it should adjust the consumption accordingly
+     */
+    fun dispatchPreFling(available: Velocity): Velocity {
+        return parent?.onPreFling(available) ?: Velocity.Zero
+    }
+
+    /**
+     * Dispatch post fling pass. This triggers [NestedScrollConnection.onPostFling] on all the
+     * ancestors, giving them possibility to react of the velocity that is left after the
+     * dispatching node itself flung with the desired amount.
+     *
+     * @param consumed velocity already consumed by this node
+     * @param available velocity that is left for ancestors to consume
+     */
+    fun dispatchPostFling(consumed: Velocity, available: Velocity) {
+        parent?.onPostFling(consumed, available) {}
+    }
+}
+
+/**
+ * Possible sources of scroll events in the [NestedScrollConnection]
+ */
+enum class NestedScrollSource {
+    /**
+     * Dragging via mouse/touch/etc events
+     */
+    Drag,
+
+    /**
+     * Flinging after the drag has ended with velocity
+     */
+    Fling
+}
+
+/**
+ * Modify element to make it participate in the nested scrolling hierarchy.
+ *
+ * There are two ways to participate in the nested scroll: as a scrolling child by dispatching
+ * scrolling events via [NestedScrollDispatcher] to the nested scroll chain; and as a member of
+ * nested scroll chain by providing [NestedScrollConnection], which will be called when another
+ * nested scrolling child below dispatches scrolling events.
+ *
+ * It's a mandatory to participate as a [NestedScrollConnection] in the chain, but scrolling
+ * events dispatch is optional since there are cases when element wants to participate in the
+ * nested scroll, but not a scrollable thing itself.
+ *
+ * Note: It is recommended to reuse [NestedScrollConnection] and [NestedScrollDispatcher] objects
+ * between recompositions since different object will cause nested scroll graph to be
+ * recalculated unnecessary.
+ *
+ * There are 4 main passes in nested scrolling system:
+ *
+ * 1. Pre-scroll. This callback is triggered when the descendant is about to perform a scroll
+ * operation and gives parent an opportunity to consume part of child's delta beforehand. This
+ * pass should happen every time scrollable components receives delta and dispatches it via
+ * [NestedScrollDispatcher]. Dispatching child should take into account how much all ancestors
+ * above the hierarchy consumed and adjust the consumption accordingly.
+ *
+ * 2. Post-scroll. This callback is triggered when the descendant consumed the delta already
+ * (after taking into account what parents pre-consumed in 1.) and wants to notify the ancestors
+ * with the amount of delta unconsumed. This pass should happen every time scrollable components
+ * receives delta and dispatches it via [NestedScrollDispatcher]. Any parent that receives
+ * [NestedScrollConnection.onPostScroll] should consume no more than `left` and return the amount
+ * consumed.
+ *
+ * 3. Pre-fling. Pass that happens when the scrolling descendant stopped dragging and about to
+ * fling with the some velocity. This callback allows ancestors to consume part of the velocity.
+ * This pass should happen before the fling itself happens. Similar to pre-scroll, parent can
+ * consume part of the velocity and nodes below (including the dispatching child) should adjust
+ * their logic to accommodate only the velocity left.
+ *
+ * 4. Post-fling. Pass that happens after the scrolling descendant stopped flinging and wants to
+ * notify ancestors about that fact, providing velocity left to consume as a part of this. This
+ * pass should happen after the fling itself happens on the scrolling child. Ancestors of the
+ * dispatching node will have opportunity to fling themselves with the `velocityLeft` provided.
+ * Parent must call `notifySelfFinish` callback in order to continue the propagation of the
+ * velocity that is left to ancestors above.
+ *
+ * Example of the nested scrolling interaction where component both dispatches and consumed
+ * children's delta:
+ * @sample androidx.compose.ui.samples.NestedScrollSample
+ *
+ * @param connection connection to the nested scroll system to participate in the event chaining,
+ * receiving events when scrollable descendant is being scrolled.
+ * @param dispatcher object to be attached to the nested scroll system on which `dispatch*`
+ * methods can be called to notify ancestors within nested scroll system about scrolling happening
+ */
+fun Modifier.nestedScroll(
+    connection: NestedScrollConnection,
+    dispatcher: NestedScrollDispatcher? = null
+): Modifier = composed(
+    inspectorInfo = debugInspectorInfo {
+        name = "nestedScroll"
+        properties["connection"] = connection
+        properties["dispatcher"] = dispatcher
+    }
+) {
+    // provide noop dispatcher if needed
+    val resolvedDispatcher = dispatcher ?: remember { NestedScrollDispatcher() }
+    remember(connection, resolvedDispatcher) {
+        object : NestedScrollModifier {
+            override val dispatcher: NestedScrollDispatcher = resolvedDispatcher
+            override val connection: NestedScrollConnection = connection
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLocker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLocker.kt
index 7ea0160..8d43c717 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLocker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLocker.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.gesture.scrollorientationlocking
 
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.CustomEvent
 import androidx.compose.ui.input.pointer.CustomEventDispatcher
 import androidx.compose.ui.input.pointer.PointerEventPass
@@ -46,7 +45,6 @@
  * [onCancel] to use
  * this correctly.
  */
-@ExperimentalPointerInput
 class ScrollOrientationLocker(private val customEventDispatcher: CustomEventDispatcher) {
 
     private var locker: InternalScrollOrientationLocker? = null
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt
index f65a27d..2078c8b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt
@@ -113,10 +113,14 @@
 }
 
 class VectorApplier(root: VNode) : AbstractApplier<VNode>(root) {
-    override fun insert(index: Int, instance: VNode) {
+    override fun insertTopDown(index: Int, instance: VNode) {
         current.asGroup().insertAt(index, instance)
     }
 
+    override fun insertBottomUp(index: Int, instance: VNode) {
+        // Ignored as the tree is built top-down.
+    }
+
     override fun remove(index: Int, count: Int) {
         current.asGroup().remove(index, count)
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
index ebd129a..6738ed7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.input.pointer
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.util.fastForEach
@@ -24,7 +23,7 @@
 /**
  * The core element that receives [PointerInputEvent]s and process them in Compose UI.
  */
-@OptIn(ExperimentalLayoutNodeApi::class, InternalCoreApi::class)
+@OptIn(InternalCoreApi::class)
 internal class PointerInputEventProcessor(val root: LayoutNode) {
 
     private val hitPathTracker = HitPathTracker()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
index 6f534f2..33d0394 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
@@ -21,7 +21,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.platform.AmbientViewConfiguration
 import androidx.compose.ui.platform.ViewConfiguration
@@ -29,7 +28,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastAll
-import androidx.compose.ui.util.fastMapTo
+import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlin.coroutines.Continuation
 import kotlin.coroutines.ContinuationInterceptor
@@ -47,7 +46,6 @@
  * resume synchronously and the caller may mutate the result **before** the next await call to
  * affect the next stage of the input processing pipeline.
  */
-@ExperimentalPointerInput
 @RestrictsSuspension
 interface HandlePointerInputScope : Density {
     /**
@@ -58,9 +56,9 @@
     val size: IntSize
 
     /**
-     * The state of the pointers as of the most recent event
+     * The [PointerEvent] from the most recent touch event.
      */
-    val currentPointers: List<PointerInputData>
+    val currentEvent: PointerEvent
 
     /**
      * The [ViewConfiguration] used to tune gesture detectors.
@@ -106,7 +104,6 @@
 // gesture detectors as suspending extensions with a PointerInputScope receiver, also making this
 // interface implement CoroutineScope would be an invitation to break structured concurrency in
 // these extensions, leaving other launched coroutines running in the calling scope.
-@ExperimentalPointerInput
 interface PointerInputScope : Density {
     /**
      * The measured size of the pointer input region. Input events will be reported with
@@ -149,7 +146,6 @@
  * pointer input events. Extension functions on [PointerInputScope] or [HandlePointerInputScope]
  * may be defined to perform higher-level gesture detection.
  */
-@ExperimentalPointerInput
 fun Modifier.pointerInput(
     block: suspend PointerInputScope.() -> Unit
 ): Modifier = composed(
@@ -177,14 +173,13 @@
  * a LayoutNode.
  *
  * [SuspendingPointerInputFilter] implements the [PointerInputScope] used to offer the
- * [Modifier.pointerInput] DSL and carries the [Density] from [DensityAmbient] at the point of
+ * [Modifier.pointerInput] DSL and carries the [Density] from [AmbientDensity] at the point of
  * the modifier's materialization. Even if this value were returned to the [PointerInputFilter]
  * callbacks, we would still need the value at composition time in order for [Modifier.pointerInput]
  * to begin its internal [LaunchedEffect] for the provided code block.
  */
 // TODO: Suppressing deprecation for synchronized; need to move to atomicfu wrapper
 @Suppress("DEPRECATION_ERROR")
-@ExperimentalPointerInput
 internal class SuspendingPointerInputFilter(
     override val viewConfiguration: ViewConfiguration,
     density: Density = Density(1f)
@@ -198,7 +193,7 @@
 
     private var _customEventDispatcher: CustomEventDispatcher? = null
 
-    val currentPointers = mutableListOf<PointerInputData>()
+    private var currentEvent: PointerEvent? = null
 
     /**
      * TODO: work out whether this is actually a race or not.
@@ -236,6 +231,13 @@
     private var lastPointerEvent: PointerEvent? = null
 
     /**
+     * The size of the bounds of this input filter. Normally [PointerInputFilter.size] can
+     * be used, but for tests, it is better to not rely on something set to an `internal`
+     * method.
+     */
+    private var boundsSize: IntSize = IntSize.Zero
+
+    /**
      * Snapshot the current [pointerHandlers] and run [block] on each one.
      * May not be called reentrant or concurrent with itself.
      *
@@ -255,13 +257,9 @@
         try {
             when (pass) {
                 PointerEventPass.Initial, PointerEventPass.Final ->
-                    dispatchingPointerHandlers.forEach {
-                        block(it)
-                    }
+                    dispatchingPointerHandlers.forEach(block)
                 PointerEventPass.Main ->
-                    dispatchingPointerHandlers.forEachReversed {
-                        block(it)
-                    }
+                    dispatchingPointerHandlers.forEachReversed(block)
             }
         } finally {
             dispatchingPointerHandlers.clear()
@@ -286,9 +284,9 @@
         pass: PointerEventPass,
         bounds: IntSize
     ) {
+        boundsSize = bounds
         if (pass == PointerEventPass.Initial) {
-            currentPointers.clear()
-            pointerEvent.changes.fastMapTo(currentPointers) { it.current }
+            currentEvent = pointerEvent
         }
         dispatchPointerEvent(pointerEvent, pass)
 
@@ -333,13 +331,7 @@
     override suspend fun <R> handlePointerInput(
         handler: suspend HandlePointerInputScope.() -> R
     ): R = suspendCancellableCoroutine { continuation ->
-        val handlerCoroutine = PointerEventHandlerCoroutine(
-            continuation,
-            currentPointers,
-            size,
-            viewConfiguration,
-            this
-        )
+        val handlerCoroutine = PointerEventHandlerCoroutine(continuation)
         synchronized(pointerHandlers) {
             pointerHandlers += handlerCoroutine
 
@@ -358,6 +350,10 @@
             // without running too late due to dispatch.
             handler.createCoroutine(handlerCoroutine, handlerCoroutine).resume(Unit)
         }
+
+        // Restricted suspension handler coroutines can't propagate structured job cancellation
+        // automatically as the context must be EmptyCoroutineContext; do it manually instead.
+        continuation.invokeOnCancellation { handlerCoroutine.cancel(it) }
     }
 
     /**
@@ -370,15 +366,20 @@
      */
     private inner class PointerEventHandlerCoroutine<R>(
         private val completion: Continuation<R>,
-        override val currentPointers: List<PointerInputData>,
-        override val size: IntSize,
-        override val viewConfiguration: ViewConfiguration,
-        density: Density
-    ) : HandlePointerInputScope, Density by density, Continuation<R> {
-        private var pointerAwaiter: Continuation<PointerEvent>? = null
-        private var customAwaiter: Continuation<CustomEvent>? = null
+    ) : HandlePointerInputScope, Density by this@SuspendingPointerInputFilter, Continuation<R> {
+        private var pointerAwaiter: CancellableContinuation<PointerEvent>? = null
+        private var customAwaiter: CancellableContinuation<CustomEvent>? = null
         private var awaitPass: PointerEventPass = PointerEventPass.Main
 
+        override val currentEvent: PointerEvent
+            get() = checkNotNull(this@SuspendingPointerInputFilter.currentEvent) {
+                "cannot access currentEvent outside of input dispatch"
+            }
+        override val size: IntSize
+            get() = this@SuspendingPointerInputFilter.boundsSize
+        override val viewConfiguration: ViewConfiguration
+            get() = this@SuspendingPointerInputFilter.viewConfiguration
+
         fun offerPointerEvent(event: PointerEvent, pass: PointerEventPass) {
             if (pass == awaitPass) {
                 pointerAwaiter?.run {
@@ -397,8 +398,16 @@
             }
         }
 
-        override val context: CoroutineContext =
-            EmptyCoroutineContext
+        // Called to run any finally blocks in the handlePointerInput block
+        fun cancel(cause: Throwable?) {
+            pointerAwaiter?.cancel(cause)
+            pointerAwaiter = null
+            customAwaiter?.cancel(cause)
+            customAwaiter = null
+        }
+
+        // context must be EmptyCoroutineContext for restricted suspension coroutines
+        override val context: CoroutineContext = EmptyCoroutineContext
 
         // Implementation of Continuation; clean up and resume our wrapped continuation.
         override fun resumeWith(result: Result<R>) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 628d7f4..675a0a0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -29,9 +29,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.materialize
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutEmitHelper
 import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.node.MeasureBlocks
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.platform.AmbientLayoutDirection
 import androidx.compose.ui.platform.simpleIdentityToString
@@ -80,7 +80,6 @@
  */
 @Suppress("ComposableLambdaParameterPosition")
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class)
 fun Layout(
     content: @Composable () -> Unit,
     minIntrinsicWidthMeasureBlock: IntrinsicMeasureBlock,
@@ -101,7 +100,7 @@
 }
 
 /**
- * Creates an instance of [LayoutNode.MeasureBlocks] to pass to [Layout] given
+ * Creates an instance of [MeasureBlocks] to pass to [Layout] given
  * intrinsic measures and a measure block.
  *
  * @sample androidx.compose.ui.samples.LayoutWithMeasureBlocksWithIntrinsicUsage
@@ -130,15 +129,14 @@
  * @see Layout
  * @see WithConstraints
  */
-@ExperimentalLayoutNodeApi
 fun measureBlocksOf(
     minIntrinsicWidthMeasureBlock: IntrinsicMeasureBlock,
     minIntrinsicHeightMeasureBlock: IntrinsicMeasureBlock,
     maxIntrinsicWidthMeasureBlock: IntrinsicMeasureBlock,
     maxIntrinsicHeightMeasureBlock: IntrinsicMeasureBlock,
     measureBlock: MeasureBlock
-): LayoutNode.MeasureBlocks {
-    return object : LayoutNode.MeasureBlocks {
+): MeasureBlocks {
+    return object : MeasureBlocks {
         override fun measure(
             measureScope: MeasureScope,
             measurables: List<Measurable>,
@@ -191,7 +189,6 @@
  */
 @Suppress("ComposableLambdaParameterPosition")
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class)
 /*inline*/ fun Layout(
     /*crossinline*/
     content: @Composable () -> Unit,
@@ -218,7 +215,7 @@
  *
  * @param content The children composable to be laid out.
  * @param modifier Modifiers to be applied to the layout.
- * @param measureBlocks An [LayoutNode.MeasureBlocks] instance defining the measurement and
+ * @param measureBlocks An [MeasureBlocks] instance defining the measurement and
  * positioning of the layout.
  *
  * @see Layout
@@ -227,10 +224,9 @@
  */
 
 @Suppress("ComposableLambdaParameterPosition")
-@ExperimentalLayoutNodeApi
 @Composable inline fun Layout(
     content: @Composable () -> Unit,
-    measureBlocks: LayoutNode.MeasureBlocks,
+    measureBlocks: MeasureBlocks,
     modifier: Modifier = Modifier
 ) {
     @OptIn(ExperimentalComposeApi::class)
@@ -246,7 +242,6 @@
     )
 }
 
-@ExperimentalLayoutNodeApi
 @PublishedApi
 internal fun materializerOf(
     modifier: Modifier
@@ -263,7 +258,6 @@
     "This composable is temporary to enable quicker prototyping in ConstraintLayout. " +
         "It should not be used in app code directly."
 )
-@OptIn(ExperimentalLayoutNodeApi::class)
 fun MultiMeasureLayout(
     modifier: Modifier = Modifier,
     children: @Composable () -> Unit,
@@ -374,19 +368,17 @@
  * call.
  */
 @PublishedApi
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class IntrinsicsMeasureScope(
     density: Density,
     override val layoutDirection: LayoutDirection
 ) : MeasureScope, Density by density
 
 /**
- * Default [LayoutNode.MeasureBlocks] object implementation, providing intrinsic measurements
+ * Default [MeasureBlocks] object implementation, providing intrinsic measurements
  * that use the measure block replacing the measure calls with intrinsic measurement calls.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 fun MeasuringIntrinsicsMeasureBlocks(measureBlock: MeasureBlock) =
-    object : LayoutNode.MeasureBlocks {
+    object : MeasureBlocks {
         override fun measure(
             measureScope: MeasureScope,
             measurables: List<Measurable>,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutInfo.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutInfo.kt
new file mode 100644
index 0000000..14f8ce2
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutInfo.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.layout
+
+import androidx.compose.ui.Modifier
+
+/**
+ * The public information about the layouts used internally as nodes in the Compose UI hierarchy.
+ */
+interface LayoutInfo {
+
+    /**
+     * This returns a new List of [Modifier]s and the coordinates and any extra information
+     * that may be useful. This is used for tooling to retrieve layout modifier and layer
+     * information.
+     */
+    fun getModifierInfo(): List<ModifierInfo>
+
+    /**
+     * The measured width of this layout and all of its modifiers.
+     */
+    val width: Int
+
+    /**
+     * The measured height of this layout and all of its modifiers.
+     */
+    val height: Int
+
+    /**
+     * Coordinates of just the contents of the layout, after being affected by all modifiers.
+     */
+    val coordinates: LayoutCoordinates
+
+    /**
+     * Whether or not this layout and all of its parents have been placed in the hierarchy.
+     */
+    val isPlaced: Boolean
+
+    /**
+     * Parent of this layout.
+     */
+    val parentInfo: LayoutInfo?
+
+    /**
+     * Returns true if this layout is currently a part of the layout tree.
+     */
+    val isAttached: Boolean
+}
+
+/**
+ * Used by tooling to examine the modifiers on a [LayoutInfo].
+ */
+class ModifierInfo(
+    val modifier: Modifier,
+    val coordinates: LayoutCoordinates,
+    val extra: Any? = null
+)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasureBlocks.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasureBlocks.kt
index e33b7e9..6a74225 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasureBlocks.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasureBlocks.kt
@@ -16,14 +16,12 @@
 
 package androidx.compose.ui.layout
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.fastForEach
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal object RootMeasureBlocks : LayoutNode.NoIntrinsicsMeasureBlocks(
     "Undefined intrinsics block and it is required"
 ) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index 99741dd..2814f27 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -29,11 +29,10 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.materialize
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutEmitHelper
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNode.LayoutState
-import androidx.compose.ui.node.isAttached
+import androidx.compose.ui.node.MeasureBlocks
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.platform.AmbientLayoutDirection
 import androidx.compose.ui.platform.subcomposeInto
@@ -60,7 +59,7 @@
  * @param measureBlock Measure block which provides ability to subcompose during the measuring.
  */
 @Composable
-@OptIn(ExperimentalLayoutNodeApi::class, ExperimentalComposeApi::class)
+@OptIn(ExperimentalComposeApi::class)
 fun SubcomposeLayout(
     modifier: Modifier = Modifier,
     measureBlock: SubcomposeMeasureScope.(Constraints) -> MeasureResult
@@ -102,7 +101,6 @@
     fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable>
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 private class SubcomposeLayoutState :
     SubcomposeMeasureScope,
     CompositionLifecycleObserver {
@@ -162,7 +160,7 @@
 
     fun subcomposeIfRemeasureNotScheduled() {
         val root = root!!
-        if (root.layoutState != LayoutState.NeedsRemeasure && root.isAttached()) {
+        if (root.layoutState != LayoutState.NeedsRemeasure && root.isAttached) {
             root.foldedChildren.fastForEach {
                 subcompose(it, nodeToNodeState.getValue(it))
             }
@@ -196,7 +194,7 @@
 
     private fun createMeasureBlocks(
         block: SubcomposeMeasureScope.(Constraints) -> MeasureResult
-    ): LayoutNode.MeasureBlocks = object : LayoutNode.NoIntrinsicsMeasureBlocks(
+    ): MeasureBlocks = object : LayoutNode.NoIntrinsicsMeasureBlocks(
         error = "Intrinsic measurements are not currently supported by SubcomposeLayout"
     ) {
         override fun measure(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
index 9d6bd30..f7963a4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
@@ -34,7 +34,6 @@
 /**
  * [LayoutNodeWrapper] with default implementations for methods.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal open class DelegatingLayoutNodeWrapper<T : Modifier.Element>(
     override var wrapped: LayoutNodeWrapper,
     open var modifier: T
@@ -138,6 +137,10 @@
         wrappedBy?.propagateFocusStateChange(focusState)
     }
 
+    override fun findPreviousNestedScrollWrapper() = wrappedBy?.findPreviousNestedScrollWrapper()
+
+    override fun findNextNestedScrollWrapper() = wrapped.findNextNestedScrollWrapper()
+
     override fun findPreviousKeyInputWrapper() = wrappedBy?.findPreviousKeyInputWrapper()
 
     override fun findNextKeyInputWrapper() = wrapped.findNextKeyInputWrapper()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
index dbf3b46..d439d47 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
@@ -28,7 +28,6 @@
  * as any of this modifications can break the comparator's contract which can cause
  * to not find the item in the tree set, which we previously added.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class DepthSortedSet(
     private val extraAssertions: Boolean = true
 ) {
@@ -59,7 +58,7 @@
     }
 
     fun add(node: LayoutNode) {
-        check(node.isAttached())
+        check(node.isAttached)
         if (extraAssertions) {
             val usedDepth = mapOfOriginalDepth[node]
             if (usedDepth == null) {
@@ -72,7 +71,7 @@
     }
 
     fun remove(node: LayoutNode) {
-        check(node.isAttached())
+        check(node.isAttached)
         val contains = set.remove(node)
         if (extraAssertions) {
             val usedDepth = mapOfOriginalDepth.remove(node)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ExperimentalLayoutNodeApi.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ExperimentalLayoutNodeApi.kt
deleted file mode 100644
index fd9d914..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ExperimentalLayoutNodeApi.kt
+++ /dev/null
@@ -1,29 +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.node
-
-@RequiresOptIn(
-    level = RequiresOptIn.Level.ERROR,
-    message = "This is an experimental API for Compose UI LayoutNode and is likely to change " +
-        "before becoming stable."
-)
-@Target(
-    AnnotationTarget.CLASS,
-    AnnotationTarget.FUNCTION,
-    AnnotationTarget.PROPERTY
-)
-annotation class ExperimentalLayoutNodeApi
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
index 23a7493..baba3c4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
@@ -19,6 +19,7 @@
 import androidx.compose.ui.focus.ExperimentalFocus
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollDelegatingWrapper
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.GraphicsLayerScope
@@ -31,7 +32,6 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class InnerPlaceable(
     layoutNode: LayoutNode
 ) : LayoutNodeWrapper(layoutNode), Density by layoutNode.measureScope {
@@ -67,6 +67,10 @@
 
     override fun findPreviousKeyInputWrapper() = wrappedBy?.findPreviousKeyInputWrapper()
 
+    override fun findPreviousNestedScrollWrapper() = wrappedBy?.findPreviousNestedScrollWrapper()
+
+    override fun findNextNestedScrollWrapper(): NestedScrollDelegatingWrapper? = null
+
     override fun findNextKeyInputWrapper(): ModifiedKeyInputNode? = null
 
     override fun findLastKeyInputWrapper(): ModifiedKeyInputNode? = findPreviousKeyInputWrapper()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index f4e5345..e4a5b3d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -17,19 +17,16 @@
 
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.FocusModifier
 import androidx.compose.ui.FocusObserverModifier
 import androidx.compose.ui.FocusRequesterModifier
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.focus.ExperimentalFocus
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollDelegatingWrapper
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollModifier
 import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
 import androidx.compose.ui.input.key.KeyInputModifier
 import androidx.compose.ui.input.pointer.PointerInputFilter
 import androidx.compose.ui.input.pointer.PointerInputModifier
@@ -38,10 +35,12 @@
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.LayoutInfo
 import androidx.compose.ui.layout.LayoutModifier
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.ModifierInfo
 import androidx.compose.ui.layout.OnGloballyPositionedModifier
 import androidx.compose.ui.layout.OnRemeasuredModifier
 import androidx.compose.ui.layout.ParentDataModifier
@@ -78,12 +77,8 @@
 /**
  * An element in the layout hierarchy, built with compose UI.
  */
-@ExperimentalLayoutNodeApi
-@OptIn(
-    ExperimentalFocus::class,
-    ExperimentalLayoutNodeApi::class
-)
-class LayoutNode : Measurable, Remeasurement, OwnerScope {
+@OptIn(ExperimentalFocus::class)
+class LayoutNode : Measurable, Remeasurement, OwnerScope, LayoutInfo {
 
     constructor() : this(false)
 
@@ -133,7 +128,7 @@
             unfoldedVirtualChildrenListDirty = true
         }
         if (isVirtual) {
-            parent?.unfoldedVirtualChildrenListDirty = true
+            this.parent?.unfoldedVirtualChildrenListDirty = true
         }
     }
 
@@ -151,8 +146,8 @@
     val children: List<LayoutNode> get() = _children.asMutableList()
 
     /**
-     * The parent node in the LayoutNode hierarchy. This is `null` when the `LayoutNode`
-     * is attached (has an [owner]) and is the root of the tree or has not had [add] called for it.
+     * The parent node in the LayoutNode hierarchy. This is `null` when the [LayoutNode]
+     * is not attached attached to a hierarchy or is the root of the hierarchy.
      */
     var parent: LayoutNode? = null
         get() {
@@ -168,9 +163,15 @@
         private set
 
     /**
-     * The tree depth of the LayoutNode. This is valid only when [owner] is not `null`.
+     * Returns true if this [LayoutNode] currently has an [LayoutNode.owner].  Semantically,
+     * this means that the LayoutNode is currently a part of a component tree.
      */
-    var depth: Int = 0
+    override val isAttached: Boolean get() = owner != null
+
+    /**
+     * The tree depth of the [LayoutNode]. This is valid only when it is attached to a hierarchy.
+     */
+    internal var depth: Int = 0
 
     /**
      * The layout state the node is currently in.
@@ -189,7 +190,7 @@
      * Inserts a child [LayoutNode] at a particular index. If this LayoutNode [owner] is not `null`
      * then [instance] will become [attach]ed also. [instance] must have a `null` [parent].
      */
-    fun insertAt(index: Int, instance: LayoutNode) {
+    internal fun insertAt(index: Int, instance: LayoutNode) {
         check(instance.parent == null) {
             "Cannot insert $instance because it already has a parent"
         }
@@ -222,7 +223,7 @@
     /**
      * Removes one or more children, starting at [index].
      */
-    fun removeAt(index: Int, count: Int) {
+    internal fun removeAt(index: Int, count: Int) {
         require(count >= 0) {
             "count ($count) must be greater than 0"
         }
@@ -249,7 +250,7 @@
     /**
      * Removes all children.
      */
-    fun removeAll() {
+    internal fun removeAll() {
         val attached = owner != null
         for (i in _foldedChildren.size - 1 downTo 0) {
             val child = _foldedChildren[i]
@@ -272,7 +273,7 @@
      * were LayoutNodes A B C D E, calling `move(1, 3, 1)` would result in the LayoutNodes
      * being reordered to A C B D E.
      */
-    fun move(from: Int, to: Int, count: Int) {
+    internal fun move(from: Int, to: Int, count: Int) {
         if (from == to) {
             return // nothing to do
         }
@@ -299,11 +300,11 @@
      * Set the [Owner] of this LayoutNode. This LayoutNode must not already be attached.
      * [owner] must match its [parent].[owner].
      */
-    fun attach(owner: Owner) {
+    internal fun attach(owner: Owner) {
         check(this.owner == null) {
             "Cannot attach $this as it already is attached"
         }
-        val parent = parent
+        val parent = this.parent
         check(parent == null || parent.owner == owner) {
             "Attaching to a different owner($owner) than the parent's owner(${parent?.owner})"
         }
@@ -336,15 +337,15 @@
      * and its [parent]'s [owner] must be `null` before calling this. This will also [detach] all
      * children. After executing, the [owner] will be `null`.
      */
-    fun detach() {
+    internal fun detach() {
         val owner = owner
         checkNotNull(owner) {
             "Cannot detach node that is already detached!"
         }
-        val parentLayoutNode = parent
-        if (parentLayoutNode != null) {
-            parentLayoutNode.invalidateLayer()
-            parentLayoutNode.requestRemeasure()
+        val parent = this.parent
+        if (parent != null) {
+            parent.invalidateLayer()
+            parent.requestRemeasure()
         }
         alignmentLinesQueryOwner = null
         alignmentUsageByParent = UsageByParent.NotUsed
@@ -389,7 +390,7 @@
         }
 
     override val isValid: Boolean
-        get() = isAttached()
+        get() = isAttached
 
     override fun toString(): String {
         return "${simpleIdentityToString(this, null)} children: ${children.size} " +
@@ -420,55 +421,7 @@
         return tree.toString()
     }
 
-    interface MeasureBlocks {
-        /**
-         * The function used to measure the child. It must call [MeasureScope.layout] before
-         * completing.
-         */
-        fun measure(
-            measureScope: MeasureScope,
-            measurables: List<Measurable>,
-            constraints: Constraints
-        ): MeasureResult
-
-        /**
-         * The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth].
-         */
-        fun minIntrinsicWidth(
-            intrinsicMeasureScope: IntrinsicMeasureScope,
-            measurables: List<IntrinsicMeasurable>,
-            h: Int
-        ): Int
-
-        /**
-         * The lambda used to calculate [IntrinsicMeasurable.minIntrinsicHeight].
-         */
-        fun minIntrinsicHeight(
-            intrinsicMeasureScope: IntrinsicMeasureScope,
-            measurables: List<IntrinsicMeasurable>,
-            w: Int
-        ): Int
-
-        /**
-         * The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth].
-         */
-        fun maxIntrinsicWidth(
-            intrinsicMeasureScope: IntrinsicMeasureScope,
-            measurables: List<IntrinsicMeasurable>,
-            h: Int
-        ): Int
-
-        /**
-         * The lambda used to calculate [IntrinsicMeasurable.maxIntrinsicHeight].
-         */
-        fun maxIntrinsicHeight(
-            intrinsicMeasureScope: IntrinsicMeasureScope,
-            measurables: List<IntrinsicMeasurable>,
-            w: Int
-        ): Int
-    }
-
-    abstract class NoIntrinsicsMeasureBlocks(private val error: String) : MeasureBlocks {
+    internal abstract class NoIntrinsicsMeasureBlocks(private val error: String) : MeasureBlocks {
         override fun minIntrinsicWidth(
             intrinsicMeasureScope: IntrinsicMeasureScope,
             measurables: List<IntrinsicMeasurable>,
@@ -497,7 +450,7 @@
     /**
      * Blocks that define the measurement and intrinsic measurement of the layout.
      */
-    var measureBlocks: MeasureBlocks = ErrorMeasureBlocks
+    internal var measureBlocks: MeasureBlocks = ErrorMeasureBlocks
         set(value) {
             if (field != value) {
                 field = value
@@ -508,13 +461,13 @@
     /**
      * The screen density to be used by this layout.
      */
-    var density: Density = Density(1f)
+    internal var density: Density = Density(1f)
 
     /**
      * The scope used to run the [MeasureBlocks.measure]
      * [MeasureBlock][androidx.compose.ui.layout.MeasureBlock].
      */
-    val measureScope: MeasureScope = object : MeasureScope, Density {
+    internal val measureScope: MeasureScope = object : MeasureScope, Density {
         override val density: Float get() = this@LayoutNode.density.density
         override val fontScale: Float get() = this@LayoutNode.density.fontScale
         override val layoutDirection: LayoutDirection get() = this@LayoutNode.layoutDirection
@@ -534,12 +487,12 @@
     /**
      * The measured width of this layout and all of its [modifier]s. Shortcut for `size.width`.
      */
-    val width: Int get() = outerMeasurablePlaceable.width
+    override val width: Int get() = outerMeasurablePlaceable.width
 
     /**
      * The measured height of this layout and all of its [modifier]s. Shortcut for `size.height`.
      */
-    val height: Int get() = outerMeasurablePlaceable.height
+    override val height: Int get() = outerMeasurablePlaceable.height
 
     /**
      * The alignment lines of this layout, inherited + intrinsic
@@ -554,9 +507,9 @@
     internal val mDrawScope: LayoutNodeDrawScope = sharedDrawScope
 
     /**
-     * Whether or not this LayoutNode and all of its parents have been placed in the hierarchy.
+     * Whether or not this [LayoutNode] and all of its parents have been placed in the hierarchy.
      */
-    var isPlaced = false
+    override var isPlaced: Boolean = false
         private set
 
     /**
@@ -614,7 +567,7 @@
     private val previousAlignmentLines = mutableMapOf<AlignmentLine, Int>()
 
     @Deprecated("Temporary API to support ConstraintLayout prototyping.")
-    var canMultiMeasure: Boolean = false
+    internal var canMultiMeasure: Boolean = false
 
     internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
     private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)
@@ -651,7 +604,7 @@
         if (innerLayerWrapper != null) {
             innerLayerWrapper.invalidateLayer()
         } else {
-            val parent = parent
+            val parent = this.parent
             parent?.invalidateLayer()
         }
     }
@@ -671,9 +624,9 @@
 
             copyWrappersToCache()
 
-            // Rebuild layoutNodeWrapper
+            // Rebuild LayoutNodeWrapper
             val oldOuterWrapper = outerMeasurablePlaceable.outerWrapper
-            if (outerSemantics != null && isAttached()) {
+            if (outerSemantics != null && isAttached) {
                 owner!!.onSemanticsChange()
             }
             val addedCallback = hasNewPositioningCallback()
@@ -720,6 +673,9 @@
                     if (mod is PointerInputModifier) {
                         wrapper = PointerInputDelegatingWrapper(wrapper, mod).assignChained(toWrap)
                     }
+                    if (mod is NestedScrollModifier) {
+                        wrapper = NestedScrollDelegatingWrapper(wrapper, mod).assignChained(toWrap)
+                    }
                     if (mod is LayoutModifier) {
                         wrapper = ModifiedLayoutNode(wrapper, mod).assignChained(toWrap)
                     }
@@ -736,7 +692,7 @@
             outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
             outerMeasurablePlaceable.outerWrapper = outerWrapper
 
-            if (isAttached()) {
+            if (isAttached) {
                 // call detach() on all removed LayoutNodeWrappers
                 wrapperCache.forEach {
                     it.detach()
@@ -782,21 +738,20 @@
         }
 
     /**
-     * Coordinates of just the contents of the LayoutNode, after being affected by all modifiers.
+     * Coordinates of just the contents of the [LayoutNode], after being affected by all modifiers.
      */
-    // TODO(mount): remove this
-    val coordinates: LayoutCoordinates
+    override val coordinates: LayoutCoordinates
         get() = innerLayoutNodeWrapper
 
     /**
      * Callback to be executed whenever the [LayoutNode] is attached to a new [Owner].
      */
-    var onAttach: ((Owner) -> Unit)? = null
+    internal var onAttach: ((Owner) -> Unit)? = null
 
     /**
      * Callback to be executed whenever the [LayoutNode] is detached from an [Owner].
      */
-    var onDetach: ((Owner) -> Unit)? = null
+    internal var onDetach: ((Owner) -> Unit)? = null
 
     /**
      * List of all OnPositioned callbacks in the modifier chain.
@@ -815,7 +770,7 @@
      */
     internal var needsOnPositionedDispatch = false
 
-    fun place(x: Int, y: Int) {
+    internal fun place(x: Int, y: Int) {
         Placeable.PlacementScope.executeWithRtlMirroringValues(
             outerMeasurablePlaceable.measuredWidth,
             layoutDirection
@@ -831,7 +786,7 @@
         outerMeasurablePlaceable.replace()
     }
 
-    fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)
+    internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)
 
     /**
      * Carries out a hit test on the [PointerInputModifier]s associated with this [LayoutNode] and
@@ -846,7 +801,7 @@
      * @param hitPointerInputFilters The collection that the hit [PointerInputFilter]s will be
      * added to if hit.
      */
-    fun hitTest(
+    internal fun hitTest(
         pointerPositionRelativeToScreen: Offset,
         hitPointerInputFilters: MutableList<PointerInputFilter>
     ) {
@@ -857,7 +812,7 @@
      * Returns the alignment line value for a given alignment line without affecting whether
      * the flag for whether the alignment line was read.
      */
-    fun getAlignmentLine(line: AlignmentLine): Int? {
+    internal fun getAlignmentLine(line: AlignmentLine): Int? {
         val linePos = alignmentLines[line] ?: return null
         var pos = Offset(linePos.toFloat(), linePos.toFloat())
         var wrapper = innerLayoutNodeWrapper
@@ -957,7 +912,7 @@
         // as a result of the previous operation we can figure out a child has been resized
         // and we need to be remeasured, not relaid out
         if (layoutState == NeedsRelayout) {
-            layoutState = LayoutState.LayingOut
+            layoutState = LayingOut
             val owner = requireOwner()
             owner.snapshotObserver.observeLayoutSnapshotReads(this) {
                 // reset the place order counter which will be used by the children
@@ -1074,7 +1029,7 @@
         val parent = parent
         if (parent != null) {
             if (alignmentUsageByParent == UsageByParent.InMeasureBlock &&
-                parent.layoutState != LayoutState.LayingOut
+                parent.layoutState != LayingOut
             ) {
                 parent.requestRemeasure()
             } else if (alignmentUsageByParent == UsageByParent.InLayoutBlock) {
@@ -1090,7 +1045,7 @@
         alignmentLinesQueriedSinceLastLayout = true
         val newUsageByParent = when (parent?.layoutState) {
             Measuring -> UsageByParent.InMeasureBlock
-            LayoutState.LayingOut -> UsageByParent.InLayoutBlock
+            LayingOut -> UsageByParent.InLayoutBlock
             else -> UsageByParent.NotUsed
         }
         val newUsageHasLowerPriority = newUsageByParent == UsageByParent.InLayoutBlock &&
@@ -1137,14 +1092,14 @@
     /**
      * Used to request a new measurement + layout pass from the owner.
      */
-    fun requestRemeasure() {
+    internal fun requestRemeasure() {
         owner?.onRequestMeasure(this)
     }
 
     /**
      * Used to request a new layout pass from the owner.
      */
-    fun requestRelayout() {
+    internal fun requestRelayout() {
         owner?.onRequestRelayout(this)
     }
 
@@ -1152,7 +1107,7 @@
      * Execute your code within the [block] if you want some code to not be observed for the
      * model reads even if you are currently inside some observed scope like measuring.
      */
-    fun ignoreModelReads(block: () -> Unit) {
+    internal fun ignoreModelReads(block: () -> Unit) {
         requireOwner().snapshotObserver.pauseSnapshotReadObservation(block)
     }
 
@@ -1171,7 +1126,7 @@
      * that may be useful. This is used for tooling to retrieve layout modifier and layer
      * information.
      */
-    fun getModifierInfo(): List<ModifierInfo> {
+    override fun getModifierInfo(): List<ModifierInfo> {
         val infoList = mutableVectorOf<ModifierInfo>()
         forEachDelegate { wrapper ->
             wrapper as DelegatingLayoutNodeWrapper<*>
@@ -1320,6 +1275,9 @@
         }
     }
 
+    override val parentInfo: LayoutInfo?
+        get() = parent
+
     internal companion object {
         private val ErrorMeasureBlocks: NoIntrinsicsMeasureBlocks =
             object : NoIntrinsicsMeasureBlocks(
@@ -1375,39 +1333,20 @@
 /**
  * Object of pre-allocated lambdas used to make emits to LayoutNodes allocation-less.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 @PublishedApi
 internal object LayoutEmitHelper {
     val constructor: () -> LayoutNode = { LayoutNode() }
     val setModifier: LayoutNode.(Modifier) -> Unit = { this.modifier = it }
     val setDensity: LayoutNode.(Density) -> Unit = { this.density = it }
-    val setMeasureBlocks: LayoutNode.(LayoutNode.MeasureBlocks) -> Unit =
+    val setMeasureBlocks: LayoutNode.(MeasureBlocks) -> Unit =
         { this.measureBlocks = it }
     val setRef: LayoutNode.(Ref<LayoutNode>) -> Unit = { it.value = this }
     val setLayoutDirection: LayoutNode.(LayoutDirection) -> Unit = { this.layoutDirection = it }
 }
 
 /**
- * Returns true if this [LayoutNode] currently has an [LayoutNode.owner].  Semantically,
- * this means that the LayoutNode is currently a part of a component tree.
- */
-@Suppress("NOTHING_TO_INLINE")
-@OptIn(ExperimentalLayoutNodeApi::class)
-internal inline fun LayoutNode.isAttached() = owner != null
-
-/**
- * Used by tooling to examine the modifiers on a [LayoutNode].
- */
-class ModifierInfo(
-    val modifier: Modifier,
-    val coordinates: LayoutCoordinates,
-    val extra: Any? = null
-)
-
-/**
  * Returns [LayoutNode.owner] or throws if it is null.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal fun LayoutNode.requireOwner(): Owner {
     val owner = owner
     checkNotNull(owner) {
@@ -1417,73 +1356,15 @@
 }
 
 /**
- * Inserts a child [LayoutNode] at a last index. If this LayoutNode [isAttached]
- * then [child] will become [isAttached]ed also. [child] must have a `null` [LayoutNode.parent].
+ * Inserts a child [LayoutNode] at a last index. If this LayoutNode [LayoutNode.isAttached]
+ * then [child] will become [LayoutNode.isAttached] also. [child] must have a `null`
+ * [LayoutNode.parent].
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal fun LayoutNode.add(child: LayoutNode) {
     insertAt(children.size, child)
 }
 
 /**
- * Executes [selector] on every parent of this [LayoutNode] and returns the closest
- * [LayoutNode] to return `true` from [selector] or null if [selector] returns false
- * for all ancestors.
- */
-@OptIn(ExperimentalLayoutNodeApi::class)
-fun LayoutNode.findClosestParentNode(selector: (LayoutNode) -> Boolean): LayoutNode? {
-    var currentParent = parent
-    while (currentParent != null) {
-        if (selector(currentParent)) {
-            return currentParent
-        } else {
-            currentParent = currentParent.parent
-        }
-    }
-
-    return null
-}
-
-/**
- * [ContentDrawScope] implementation that extracts density and layout direction information
- * from the given LayoutNodeWrapper
- */
-@OptIn(ExperimentalLayoutNodeApi::class)
-internal class LayoutNodeDrawScope(
-    private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
-) : DrawScope by canvasDrawScope, ContentDrawScope {
-
-    // NOTE, currently a single ComponentDrawScope is shared across composables
-    // which done to allocate a single set of Paint objects and re-use them across
-    // draw calls for all composables.
-    // As a result there could be thread safety concerns here for multi-threaded drawing
-    // scenarios, generally a single ComponentDrawScope should be shared for a particular thread
-    private var wrapped: LayoutNodeWrapper? = null
-
-    override fun drawContent() {
-        drawIntoCanvas { canvas -> wrapped?.draw(canvas) }
-    }
-
-    internal inline fun draw(
-        canvas: Canvas,
-        size: Size,
-        layoutNodeWrapper: LayoutNodeWrapper,
-        block: DrawScope.() -> Unit
-    ) {
-        val previousWrapper = wrapped
-        wrapped = layoutNodeWrapper
-        canvasDrawScope.draw(
-            layoutNodeWrapper.measureScope,
-            layoutNodeWrapper.measureScope.layoutDirection,
-            canvas,
-            size,
-            block
-        )
-        wrapped = previousWrapper
-    }
-}
-
-/**
  * Sets [DelegatingLayoutNodeWrapper#isChained] to `true` of the [wrapped][this.wrapped] when it
  * is part of a chain of LayoutNodes for the same modifier.
  *
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
new file mode 100644
index 0000000..8493379
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.node
+
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+
+/**
+ * [ContentDrawScope] implementation that extracts density and layout direction information
+ * from the given LayoutNodeWrapper
+ */
+internal class LayoutNodeDrawScope(
+    private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
+) : DrawScope by canvasDrawScope, ContentDrawScope {
+
+    // NOTE, currently a single ComponentDrawScope is shared across composables
+    // which done to allocate a single set of Paint objects and re-use them across
+    // draw calls for all composables.
+    // As a result there could be thread safety concerns here for multi-threaded drawing
+    // scenarios, generally a single ComponentDrawScope should be shared for a particular thread
+    private var wrapped: LayoutNodeWrapper? = null
+
+    override fun drawContent() {
+        drawIntoCanvas { canvas -> wrapped?.draw(canvas) }
+    }
+
+    internal inline fun draw(
+        canvas: Canvas,
+        size: Size,
+        LayoutNodeWrapper: LayoutNodeWrapper,
+        block: DrawScope.() -> Unit
+    ) {
+        val previousWrapper = wrapped
+        wrapped = LayoutNodeWrapper
+        canvasDrawScope.draw(
+            LayoutNodeWrapper.measureScope,
+            LayoutNodeWrapper.measureScope.layoutDirection,
+            canvas,
+            size,
+            block
+        )
+        wrapped = previousWrapper
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index de4f6a4..545fdd9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.toRect
+import androidx.compose.ui.gesture.nestedscroll.NestedScrollDelegatingWrapper
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.graphics.Matrix
@@ -47,7 +48,6 @@
 /**
  * Measurable and Placeable type that has a position.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal abstract class LayoutNodeWrapper(
     internal val layoutNode: LayoutNode
 ) : Placeable(), Measurable, LayoutCoordinates, OwnerScope, (Canvas) -> Unit {
@@ -72,7 +72,7 @@
     override val isAttached: Boolean
         get() {
             if (_isAttached) {
-                require(layoutNode.isAttached())
+                require(layoutNode.isAttached)
             }
             return _isAttached
         }
@@ -110,9 +110,10 @@
     var isShallowPlacing = false
 
     private var _rectCache: MutableRect? = null
-    private val rectCache: MutableRect get() = _rectCache ?: MutableRect(0f, 0f, 0f, 0f).also {
-        _rectCache = it
-    }
+    private val rectCache: MutableRect
+        get() = _rectCache ?: MutableRect(0f, 0f, 0f, 0f).also {
+            _rectCache = it
+        }
 
     private val snapshotObserver get() = layoutNode.requireOwner().snapshotObserver
 
@@ -193,7 +194,6 @@
     protected abstract fun performDraw(canvas: Canvas)
 
     // implementation of draw block passed to the OwnedLayer
-    @ExperimentalLayoutNodeApi
     override fun invoke(canvas: Canvas) {
         if (layoutNode.isPlaced) {
             require(layoutNode.layoutState == LayoutNode.LayoutState.Ready) {
@@ -414,7 +414,6 @@
         // which layer contained this one, but all layers in this modifier chain will be invalidated
         // in onModifierChanged(). Therefore the only possible layer that won't automatically be
         // invalidated is the parent's layer. We'll invalidate it here:
-        @OptIn(ExperimentalLayoutNodeApi::class)
         layoutNode.parent?.invalidateLayer()
     }
 
@@ -502,14 +501,40 @@
     }
 
     /**
+     * Returns the first [NestedScrollDelegatingWrapper] in the wrapper list that wraps this
+     * [LayoutNodeWrapper].
+     *
+     * Note: This method tried to find [NestedScrollDelegatingWrapper] in the
+     * modifiers before the one wrapped with this [LayoutNodeWrapper] and goes up the hierarchy of
+     * [LayoutNode]s if needed.
+     */
+    abstract fun findPreviousNestedScrollWrapper(): NestedScrollDelegatingWrapper?
+
+    /**
+     * Returns the first [NestedScrollDelegatingWrapper] in the wrapper list that is wrapped by this
+     * [LayoutNodeWrapper].
+     *
+     * Note: This method only goes to the modifiers that follow the one wrapped by
+     * this [LayoutNodeWrapper], it doesn't to the children [LayoutNode]s.
+     */
+    abstract fun findNextNestedScrollWrapper(): NestedScrollDelegatingWrapper?
+
+    /**
      * Returns the first [focus node][ModifiedFocusNode] in the wrapper list that wraps this
      * [LayoutNodeWrapper].
+     *
+     * Note: This method tried to find [NestedScrollDelegatingWrapper] in the
+     * modifiers before the one wrapped with this [LayoutNodeWrapper] and goes up the hierarchy of
+     * [LayoutNode]s if needed.
      */
     abstract fun findPreviousFocusWrapper(): ModifiedFocusNode?
 
     /**
      * Returns the next [focus node][ModifiedFocusNode] in the wrapper list that is wrapped by
      * this [LayoutNodeWrapper].
+     *
+     * Note: This method only goes to the modifiers that follow the one wrapped by
+     * this [LayoutNodeWrapper], it doesn't to the children [LayoutNode]s.
      */
     abstract fun findNextFocusWrapper(): ModifiedFocusNode?
 
@@ -576,12 +601,19 @@
     /**
      * Returns the first [ModifiedKeyInputNode] in the wrapper list that wraps this
      * [LayoutNodeWrapper].
+     *
+     * Note: This method tried to find [NestedScrollDelegatingWrapper] in the
+     * modifiers before the one wrapped with this [LayoutNodeWrapper] and goes up the hierarchy of
+     * [LayoutNode]s if needed.
      */
     abstract fun findPreviousKeyInputWrapper(): ModifiedKeyInputNode?
 
     /**
      * Returns the next [ModifiedKeyInputNode] in the wrapper list that is wrapped by this
      * [LayoutNodeWrapper].
+     *
+     * Note: This method only goes to the modifiers that follow the one wrapped by
+     * this [LayoutNodeWrapper], it doesn't to the children [LayoutNode]s.
      */
     abstract fun findNextKeyInputWrapper(): ModifiedKeyInputNode?
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
index d012797..289268a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
@@ -23,7 +23,6 @@
  * which is hard to enforce but important to maintain. This method is intended to do the
  * work only during our tests and will iterate through the tree to validate the states consistency.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class LayoutTreeConsistencyChecker(
     private val root: LayoutNode,
     private val relayoutNodes: DepthSortedSet,
@@ -59,7 +58,7 @@
                 return true
             }
             // remeasure or relayout is scheduled
-            val parentLayoutState = parent?.layoutState
+            val parentLayoutState = this.parent?.layoutState
             if (layoutState == LayoutNode.LayoutState.NeedsRemeasure) {
                 return relayoutNodes.contains(this) ||
                     parentLayoutState == LayoutNode.LayoutState.NeedsRemeasure ||
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 489e4e7..8024fb6 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
@@ -36,7 +36,6 @@
  * dispatch [OnPositionedModifier] callbacks for the nodes affected by the previous
  * [measureAndLayout] execution.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class MeasureAndLayoutDelegate(private val root: LayoutNode) {
     /**
      * LayoutNodes that need measure or layout.
@@ -190,7 +189,7 @@
      * Iterates through all LayoutNodes that have requested layout and measures and lays them out
      */
     fun measureAndLayout(): Boolean {
-        require(root.isAttached())
+        require(root.isAttached)
         require(root.isPlaced)
         require(!duringMeasureLayout)
         // we don't need to measure any children unless we have the correct root constraints
@@ -226,7 +225,7 @@
                     // execute postponed `onRequestMeasure`
                     if (postponedMeasureRequests.isNotEmpty()) {
                         postponedMeasureRequests.fastForEach {
-                            if (it.isAttached()) {
+                            if (it.isAttached) {
                                 requestRemeasure(it)
                             }
                         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureBlocks.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureBlocks.kt
new file mode 100644
index 0000000..d70ab9b
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureBlocks.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.node
+
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.IntrinsicMeasureScope
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.unit.Constraints
+
+interface MeasureBlocks {
+    /**
+     * The function used to measure the child. It must call [MeasureScope.layout] before
+     * completing.
+     */
+    fun measure(
+        measureScope: MeasureScope,
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ): MeasureResult
+
+    /**
+     * The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth].
+     */
+    fun minIntrinsicWidth(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurables: List<IntrinsicMeasurable>,
+        h: Int
+    ): Int
+
+    /**
+     * The lambda used to calculate [IntrinsicMeasurable.minIntrinsicHeight].
+     */
+    fun minIntrinsicHeight(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurables: List<IntrinsicMeasurable>,
+        w: Int
+    ): Int
+
+    /**
+     * The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth].
+     */
+    fun maxIntrinsicWidth(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurables: List<IntrinsicMeasurable>,
+        h: Int
+    ): Int
+
+    /**
+     * The lambda used to calculate [IntrinsicMeasurable.maxIntrinsicHeight].
+     */
+    fun maxIntrinsicHeight(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurables: List<IntrinsicMeasurable>,
+        w: Int
+    ): Int
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
index ec4356a..94ac6c4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
@@ -23,7 +23,6 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.unit.toSize
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class ModifiedDrawNode(
     wrapped: LayoutNodeWrapper,
     drawModifier: DrawModifier
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt
index 2a4055d..7da03ef 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusNode.kt
@@ -30,7 +30,6 @@
 
 @OptIn(
     ExperimentalFocus::class,
-    ExperimentalLayoutNodeApi::class
 )
 internal class ModifiedFocusNode(
     wrapped: LayoutNodeWrapper,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt
index c59af87..03f8140 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedFocusRequesterNode.kt
@@ -38,7 +38,6 @@
         }
 
     // Searches for the focus node associated with this focus requester node.
-    @OptIn(ExperimentalLayoutNodeApi::class)
     internal fun findFocusNode(): ModifiedFocusNode? {
         return findNextFocusWrapper() ?: layoutNode.searchChildrenForFocusNode()
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
index 0904cee..d01fe4c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
@@ -26,7 +26,6 @@
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class ModifiedLayoutNode(
     wrapped: LayoutNodeWrapper,
     modifier: LayoutModifier
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedParentDataNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedParentDataNode.kt
index adcfe33..521c2c0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedParentDataNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedParentDataNode.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.ui.layout.ParentDataModifier
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class ModifiedParentDataNode(
     wrapped: LayoutNodeWrapper,
     parentDataModifier: ParentDataModifier
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
index ebbc6c0..867cd1c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
@@ -23,7 +23,6 @@
  * Tracks the nodes being positioned and dispatches OnPositioned callbacks when we finished
  * the measure/layout pass.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class OnPositionedDispatcher {
     private val layoutNodes = mutableVectorOf<LayoutNode>()
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
index d8b010e..243173d9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
@@ -25,7 +25,6 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class OuterMeasurablePlaceable(
     private val layoutNode: LayoutNode,
     var outerWrapper: LayoutNodeWrapper
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
index f5107fe..3e311cb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
@@ -39,7 +39,6 @@
  * to Android [views][android.view.View] and all layout, draw, input, and accessibility is hooked
  * through them.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 interface Owner {
 
     /**
@@ -115,11 +114,6 @@
     fun onRequestRelayout(layoutNode: LayoutNode)
 
     /**
-     * Whether the Owner has pending layout work.
-     */
-    val hasPendingMeasureOrLayout: Boolean
-
-    /**
      * Called by [LayoutNode] when it is attached to the view system and now has an owner.
      * This is used by [Owner] to track which nodes are associated with it. It will only be
      * called when [node] is not already attached to an owner.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
index 663dde03..b7bec7f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
@@ -24,7 +24,7 @@
  * automatically when the snapshot value has been changed.
  */
 // TODO make it internal once Owner is internal
-@OptIn(ExperimentalComposeApi::class, ExperimentalLayoutNodeApi::class)
+@OptIn(ExperimentalComposeApi::class)
 @Suppress("CallbackName") // TODO rename this and SnapshotStateObserver. b/173401548
 class OwnerSnapshotObserver(onChangedExecutor: (callback: () -> Unit) -> Unit) {
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Subcomposition.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Subcomposition.kt
index d821c90..7e4506e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Subcomposition.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Subcomposition.kt
@@ -18,22 +18,12 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
 import androidx.compose.runtime.CompositionReference
-import androidx.compose.runtime.ExperimentalComposeApi
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.util.annotation.MainThread
 
-@OptIn(ExperimentalLayoutNodeApi::class)
-internal expect fun actualSubcomposeInto(
-    container: LayoutNode,
-    parent: CompositionReference,
-    composable: @Composable () -> Unit
-): Composition
-
-@OptIn(ExperimentalComposeApi::class, ExperimentalLayoutNodeApi::class)
 @MainThread
-fun subcomposeInto(
+internal expect fun subcomposeInto(
     container: LayoutNode,
     parent: CompositionReference,
     composable: @Composable () -> Unit
-): Composition = actualSubcomposeInto(container, parent, composable)
\ No newline at end of file
+): Composition
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selectable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selectable.kt
index 26d168e..b98c168 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selectable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selectable.kt
@@ -20,11 +20,13 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 
 /**
  * Provides [Selection] information for a composable to SelectionContainer. Composables who can
  * be selected should subscribe to [SelectionRegistrar] using this interface.
  */
+@ExperimentalTextApi
 interface Selectable {
     /**
      * Returns [Selection] information for a selectable composable. If no selection can be provided
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selection.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selection.kt
index 714ac17..bb80ac9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selection.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/Selection.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.selection
 
 import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.style.ResolvedTextDirection
 
@@ -24,6 +25,7 @@
  * Information about the current Selection.
  */
 @Immutable
+@OptIn(ExperimentalTextApi::class)
 data class Selection(
     /**
      * Information about the start of the selection.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionContainer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionContainer.kt
index 3e4b606..be22667 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionContainer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionContainer.kt
@@ -135,17 +135,12 @@
                         handle = null
                     )
                 }
-                SelectionFloatingToolBar(manager = manager)
             }
         }
     }
 
     onDispose {
         manager.selection = null
+        manager.hideSelectionToolbar()
     }
 }
-
-@Composable
-private fun SelectionFloatingToolBar(manager: SelectionManager) {
-    manager.showSelectionToolbar()
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionManager.kt
index b6cbf8e..ce33357 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionManager.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.TextToolbarStatus
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.InternalTextApi
 import androidx.compose.ui.text.length
 import androidx.compose.ui.text.subSequence
@@ -40,7 +41,10 @@
 /**
  * A bridge class between user interaction to the text composables for text selection.
  */
-@OptIn(InternalTextApi::class)
+@OptIn(
+    InternalTextApi::class,
+    ExperimentalTextApi::class
+)
 internal class SelectionManager(private val selectionRegistrar: SelectionRegistrarImpl) {
     /**
      * The current selection.
@@ -49,7 +53,6 @@
         set(value) {
             field = value
             updateHandleOffsets()
-            hideSelectionToolbar()
         }
 
     /**
@@ -123,9 +126,20 @@
     init {
         selectionRegistrar.onPositionChangeCallback = {
             updateHandleOffsets()
+            updateSelectionToolbarPosition()
+        }
+
+        selectionRegistrar.onSelectionUpdateStartCallback = { layoutCoordinates, startPosition ->
+            updateSelection(
+                startPosition = convertToContainerCoordinates(layoutCoordinates, startPosition),
+                endPosition = convertToContainerCoordinates(layoutCoordinates, startPosition),
+                isStartHandle = true,
+                longPress = true
+            )
             hideSelectionToolbar()
         }
-        selectionRegistrar.onUpdateSelectionCallback =
+
+        selectionRegistrar.onSelectionUpdateCallback =
             { layoutCoordinates, startPosition, endPosition ->
                 updateSelection(
                     startPosition = convertToContainerCoordinates(layoutCoordinates, startPosition),
@@ -134,6 +148,10 @@
                     longPress = true
                 )
             }
+
+        selectionRegistrar.onSelectionUpdateEndCallback = {
+            showSelectionToolbar()
+        }
     }
 
     private fun updateHandleOffsets() {
@@ -265,12 +283,9 @@
         }
     }
 
-    private fun hideSelectionToolbar() {
+    internal fun hideSelectionToolbar() {
         if (textToolbar?.status == TextToolbarStatus.Shown) {
-            val selection = selection
-            if (selection == null) {
-                textToolbar?.hide()
-            }
+            textToolbar?.hide()
         }
     }
 
@@ -356,12 +371,14 @@
             endPosition = Offset(-1f, -1f),
             previousSelection = selection
         )
+        hideSelectionToolbar()
         if (selection != null) onSelectionChange(null)
     }
 
     fun handleDragObserver(isStartHandle: Boolean): DragObserver {
         return object : DragObserver {
             override fun onStart(downPosition: Offset) {
+                hideSelectionToolbar()
                 val selection = selection!!
                 // The LayoutCoordinates of the composable where the drag gesture should begin. This
                 // is used to convert the position of the beginning of the drag gesture from the
@@ -375,13 +392,15 @@
                 // The position of the character where the drag gesture should begin. This is in
                 // the composable coordinates.
                 val beginCoordinates = getAdjustedCoordinates(
-                    if (isStartHandle)
+                    if (isStartHandle) {
                         selection.start.selectable.getHandlePosition(
                             selection = selection, isStartHandle = true
-                        ) else
+                        )
+                    } else {
                         selection.end.selectable.getHandlePosition(
                             selection = selection, isStartHandle = false
                         )
+                    }
                 )
 
                 // Convert the position where drag gesture begins from composable coordinates to
@@ -434,6 +453,14 @@
                 )
                 return dragDistance
             }
+
+            override fun onStop(velocity: Offset) {
+                showSelectionToolbar()
+            }
+
+            override fun onCancel() {
+                showSelectionToolbar()
+            }
         }
     }
 
@@ -468,6 +495,7 @@
     return lhs?.merge(rhs) ?: rhs
 }
 
+@OptIn(ExperimentalTextApi::class)
 internal fun getCurrentSelectedText(
     selectable: Selectable,
     selection: Selection
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrar.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrar.kt
index 3dbff76..7e1cded 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrar.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrar.kt
@@ -19,10 +19,12 @@
 import androidx.compose.runtime.ambientOf
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.ExperimentalTextApi
 
 /**
  *  An interface allowing a composable to subscribe and unsubscribe to selection changes.
  */
+@ExperimentalTextApi
 interface SelectionRegistrar {
     /**
      * Subscribe to SelectionContainer selection changes.
@@ -38,20 +40,58 @@
      * When the Global Position of a subscribed [Selectable] changes, this method
      * is called.
      */
-    fun onPositionChange()
+    fun notifyPositionChange()
 
     /**
-     * When selection changes, this method is called.
+     * Call this method to notify the [SelectionContainer] that the selection has been initiated.
+     * Depends on the input, [notifySelectionUpdate] may be called repeatedly after
+     * [notifySelectionUpdateStart] is called. And [notifySelectionUpdateEnd] should always be
+     * called after selection finished.
+     * For example:
+     *  1. User long pressed the text and then release. [notifySelectionUpdateStart] should be
+     *  called followed by [notifySelectionUpdateEnd] being called once.
+     *  2. User long pressed the text and then drag a distance and then release.
+     *  [notifySelectionUpdateStart] should be called first after the user long press, and then
+     *  [notifySelectionUpdate] is called several times reporting the updates, in the end
+     *  [notifySelectionUpdateEnd] is called to finish the selection.
+     *
+     * @param layoutCoordinates [LayoutCoordinates] of the [Selectable].
+     * @param startPosition coordinates of where the selection is initiated.
+     *
+     * @see notifySelectionUpdate
+     * @see notifySelectionUpdateEnd
+     */
+    fun notifySelectionUpdateStart(
+        layoutCoordinates: LayoutCoordinates,
+        startPosition: Offset
+    )
+
+    /**
+     * Call this method to notify the [SelectionContainer] that  the selection has been updated.
+     * The caller of this method should make sure that [notifySelectionUpdateStart] is always
+     * called once before calling this function. And [notifySelectionUpdateEnd] is always called
+     * once after the all updates finished.
      *
      * @param layoutCoordinates [LayoutCoordinates] of the [Selectable].
      * @param startPosition coordinates of where the selection starts.
      * @param endPosition coordinates of where the selection ends.
+     *
+     * @see notifySelectionUpdateStart
+     * @see notifySelectionUpdateEnd
      */
-    fun onUpdateSelection(
+    fun notifySelectionUpdate(
         layoutCoordinates: LayoutCoordinates,
         startPosition: Offset,
-        endPosition: Offset
+        endPosition: Offset,
     )
+
+    /**
+     * Call this method to notify the [SelectionContainer] that the selection update has stopped.
+     *
+     * @see notifySelectionUpdateStart
+     * @see notifySelectionUpdate
+     */
+    fun notifySelectionUpdateEnd()
 }
 
 /**
@@ -72,4 +112,5 @@
  * Ambient of SelectionRegistrar. Composables that implement selection logic can use this ambient
  * to get a [SelectionRegistrar] in order to subscribe and unsubscribe to [SelectionRegistrar].
  */
+@OptIn(ExperimentalTextApi::class)
 val AmbientSelectionRegistrar = ambientOf<SelectionRegistrar?>()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrarImpl.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrarImpl.kt
index c42a8d1..dd9250d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrarImpl.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/selection/SelectionRegistrarImpl.kt
@@ -18,7 +18,9 @@
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.ExperimentalTextApi
 
+@OptIn(ExperimentalTextApi::class)
 internal class SelectionRegistrarImpl : SelectionRegistrar {
     /**
      * A flag to check if the [Selectable]s have already been sorted.
@@ -42,6 +44,21 @@
      */
     internal var onPositionChangeCallback: (() -> Unit)? = null
 
+    /**
+     * The callback to be invoked when the selection is initiated.
+     */
+    internal var onSelectionUpdateStartCallback: ((LayoutCoordinates, Offset) -> Unit)? = null
+
+    /**
+     * The callback to be invoked when the selection is updated.
+     */
+    internal var onSelectionUpdateCallback: ((LayoutCoordinates, Offset, Offset) -> Unit)? = null
+
+    /**
+     * The callback to be invoked when selection update finished.
+     */
+    internal var onSelectionUpdateEndCallback: (() -> Unit)? = null
+
     override fun subscribe(selectable: Selectable): Selectable {
         _selectables.add(selectable)
         sorted = false
@@ -87,27 +104,29 @@
         return selectables
     }
 
-    override fun onPositionChange() {
+    override fun notifyPositionChange() {
         // Set the variable sorted to be false, when the global position of a registered
         // selectable changes.
         sorted = false
         onPositionChangeCallback?.invoke()
     }
 
-    /**
-     * The callback to be invoked when the selection change was triggered.
-     */
-    internal var onUpdateSelectionCallback: ((LayoutCoordinates, Offset, Offset) -> Unit)? = null
+    override fun notifySelectionUpdateStart(
+        layoutCoordinates: LayoutCoordinates,
+        startPosition: Offset
+    ) {
+        onSelectionUpdateStartCallback?.invoke(layoutCoordinates, startPosition)
+    }
 
-    override fun onUpdateSelection(
+    override fun notifySelectionUpdate(
         layoutCoordinates: LayoutCoordinates,
         startPosition: Offset,
         endPosition: Offset
     ) {
-        onUpdateSelectionCallback?.invoke(
-            layoutCoordinates,
-            startPosition,
-            endPosition
-        )
+        onSelectionUpdateCallback?.invoke(layoutCoordinates, startPosition, endPosition)
+    }
+
+    override fun notifySelectionUpdateEnd() {
+        onSelectionUpdateEndCallback?.invoke()
     }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt
index ff04b6a..ead5b9b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt
@@ -75,15 +75,7 @@
      * [SemanticsNode] representing the owning component.
      */
     var isMergingSemanticsOfDescendants: Boolean = false
-
-    /**
-     * Whether this configuration is empty.
-     *
-     * An empty configuration doesn't contain any semantic information that it
-     * wants to contribute to the semantics tree.
-     */
-    val isEmpty: Boolean
-        get() = props.isEmpty() && !isMergingSemanticsOfDescendants
+    var isClearingSemantics: Boolean = false
 
     // CONFIGURATION COMBINATION LOGIC
 
@@ -116,6 +108,9 @@
         if (peer.isMergingSemanticsOfDescendants) {
             isMergingSemanticsOfDescendants = true
         }
+        if (peer.isClearingSemantics) {
+            isClearingSemantics = true
+        }
         for ((key, nextValue) in peer.props) {
             if (!props.contains(key)) {
                 props[key] = nextValue
@@ -127,6 +122,7 @@
     fun copy(): SemanticsConfiguration {
         val copy = SemanticsConfiguration()
         copy.isMergingSemanticsOfDescendants = isMergingSemanticsOfDescendants
+        copy.isClearingSemantics = isClearingSemantics
         copy.props.putAll(props)
         return copy
     }
@@ -135,8 +131,9 @@
         if (this === other) return true
         if (other !is SemanticsConfiguration) return false
 
-        if (isMergingSemanticsOfDescendants != other.isMergingSemanticsOfDescendants) return false
         if (props != other.props) return false
+        if (isMergingSemanticsOfDescendants != other.isMergingSemanticsOfDescendants) return false
+        if (isClearingSemantics != other.isClearingSemantics) return false
 
         return true
     }
@@ -144,6 +141,7 @@
     override fun hashCode(): Int {
         var result = props.hashCode()
         result = 31 * result + isMergingSemanticsOfDescendants.hashCode()
+        result = 31 * result + isClearingSemantics.hashCode()
         return result
     }
 
@@ -157,6 +155,12 @@
             nextSeparator = ", "
         }
 
+        if (isClearingSemantics) {
+            propsString.append(nextSeparator)
+            propsString.append("isClearingSemantics=true")
+            nextSeparator = ", "
+        }
+
         for ((key, value) in props) {
             propsString.append(nextSeparator)
             propsString.append(key.name)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt
index 50c29ab..0135adf 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt
@@ -44,11 +44,13 @@
 internal class SemanticsModifierCore(
     override val id: Int,
     mergeDescendants: Boolean,
+    clearAndSetSemantics: Boolean,
     properties: (SemanticsPropertyReceiver.() -> Unit)
 ) : SemanticsModifier {
     override val semanticsConfiguration: SemanticsConfiguration =
         SemanticsConfiguration().also {
             it.isMergingSemanticsOfDescendants = mergeDescendants
+            it.isClearingSemantics = clearAndSetSemantics
 
             it.properties()
         }
@@ -73,11 +75,32 @@
 }
 
 /**
- * Add semantics key/value for use in testing, accessibility, and similar use cases.
+ * Add semantics key/value pairs to the layout node, for use in testing, accessibility, etc.
+ *
+ * The provided lambda receiver scope provides "key = value"-style setters for any
+ * [SemanticsPropertyKey]. Additionally, chaining multiple semantics modifiers is
+ * also a supported style.
+ *
+ * The resulting semantics produce two [SemanticsNode] trees:
+ *
+ * The "unmerged tree" rooted at [SemanticsOwner.unmergedRootSemanticsNode] has one
+ * [SemanticsNode] per layout node which has any [SemanticsModifier] on it.  This [SemanticsNode]
+ * contains all the properties set in all the [SemanticsModifier]s on that node.
+ *
+ * The "merged tree" rooted at [SemanticsOwner.rootSemanticsNode] has equal-or-fewer nodes: it
+ * simplifies the structure based on [mergeDescendants] and [clearAndSetSemantics].  For most
+ * purposes (especially accessibility, or the testing of accessibility), the merged semantics
+ * tree should be used.
  *
  * @param mergeDescendants Whether the semantic information provided by the owning component and
- * its descendants (which do not themselves merge descendants) should be treated as one logical
- * entity.
+ * its descendants should be treated as one logical entity.
+ * Most commonly set on screen-reader-focusable items such as buttons or form fields.
+ * In the merged semantics tree, all descendant nodes (except those themselves marked
+ * [mergeDescendants]) will disappear from the tree, and their properties will get merged
+ * into the parent's configuration (using a merging algorithm that varies based on the type
+ * of property -- for example, text properties will get concatenated, separated by commas).
+ * In the unmerged semantics tree, the node is simply marked with
+ * [SemanticsConfiguration.isMergingSemanticsOfDescendants].
  * @param properties properties to add to the semantics. [SemanticsPropertyReceiver] will be
  * provided in the scope to allow access for common properties and its values.
  */
@@ -92,5 +115,32 @@
     }
 ) {
     val id = remember { SemanticsModifierCore.generateSemanticsId() }
-    SemanticsModifierCore(id, mergeDescendants, properties)
+    SemanticsModifierCore(id, mergeDescendants, clearAndSetSemantics = false, properties)
+}
+
+/**
+ * Clears the semantics of all the descendant nodes and sets new semantics.
+ *
+ * In the merged semantics tree, this clears the semantic information provided
+ * by the node's descendants (but not those of the layout node itself, if any) and sets
+ * the provided semantics.  (In the unmerged tree, the semantics node is marked with
+ * "[SemanticsConfiguration.isClearingSemantics]", but nothing is actually cleared.)
+ *
+ * Compose's default semantics provide baseline usability for screen-readers, but this can be
+ * used to provide a more polished screen-reader experience: for example, clearing the
+ * semantics of a group of tiny buttons, and setting equivalent actions on the card containing them.
+ *
+ * @param properties properties to add to the semantics. [SemanticsPropertyReceiver] will be
+ * provided in the scope to allow access for common properties and its values.
+ */
+fun Modifier.clearAndSetSemantics(
+    properties: (SemanticsPropertyReceiver.() -> Unit)
+): Modifier = composed(
+    inspectorInfo = debugInspectorInfo {
+        name = "clearAndSetSemantics"
+        this.properties["properties"] = properties
+    }
+) {
+    val id = remember { SemanticsModifierCore.generateSemanticsId() }
+    SemanticsModifierCore(id, mergeDescendants = false, clearAndSetSemantics = true, properties)
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
index 4e12cbf..bca6c84 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
@@ -23,10 +23,10 @@
 import androidx.compose.ui.layout.globalBounds
 import androidx.compose.ui.layout.globalPosition
 import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
+import androidx.compose.ui.layout.LayoutInfo
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNodeWrapper
-import androidx.compose.ui.node.findClosestParentNode
+import androidx.compose.ui.node.Owner
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
 
@@ -47,7 +47,6 @@
  * of any other semantics modifiers on the same layout node, and if "mergeDescendants" is
  * specified and enabled, also the "merged" configuration of its subtree.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 class SemanticsNode internal constructor(
     /*
      * This is expected to be the outermost semantics modifier on a layout node.
@@ -67,8 +66,22 @@
 ) {
     internal val unmergedConfig = layoutNodeWrapper.collapsedSemanticsConfiguration()
     val id: Int = layoutNodeWrapper.modifier.id
-    // TODO(aelias): Make this internal and expose the Owner instead
-    val componentNode: LayoutNode = layoutNodeWrapper.layoutNode
+
+    /**
+     * The [LayoutInfo] that this is associated with.
+     */
+    val layoutInfo: LayoutInfo = layoutNodeWrapper.layoutNode
+
+    /**
+     * The [Owner] this node is attached to.
+     */
+    // TODO(b/174747742) Stop using Owner in tests and use RootForTest instead
+    val owner: Owner? get() = layoutNode.owner
+
+    /**
+     * The [LayoutNode] that this is associated with.
+     */
+    internal val layoutNode: LayoutNode = layoutNodeWrapper.layoutNode
 
     // GEOMETRY
 
@@ -77,7 +90,7 @@
      */
     val size: IntSize
         get() {
-            return componentNode.coordinates.size
+            return this.layoutNode.coordinates.size
         }
 
     /**
@@ -87,7 +100,7 @@
      */
     val boundsInRoot: Rect
         get() {
-            return componentNode.coordinates.boundsInRoot
+            return this.layoutNode.coordinates.boundsInRoot
         }
 
     /**
@@ -96,7 +109,7 @@
      */
     val positionInRoot: Offset
         get() {
-            return componentNode.coordinates.positionInRoot
+            return this.layoutNode.coordinates.positionInRoot
         }
 
     /**
@@ -105,7 +118,7 @@
      */
     val globalBounds: Rect
         get() {
-            return componentNode.coordinates.globalBounds
+            return this.layoutNode.coordinates.globalBounds
         }
 
     /**
@@ -113,7 +126,7 @@
      */
     val globalPosition: Offset
         get() {
-            return componentNode.coordinates.globalPosition
+            return this.layoutNode.coordinates.globalPosition
         }
 
     /**
@@ -121,7 +134,7 @@
      * if the line is not provided.
      */
     fun getAlignmentLinePosition(line: AlignmentLine): Int {
-        return componentNode.coordinates[line]
+        return this.layoutNode.coordinates[line]
     }
 
     // CHILDREN
@@ -139,9 +152,7 @@
         get() {
             if (isMergingSemanticsOfDescendants) {
                 val mergedConfig = unmergedConfig.copy()
-                unmergedChildren().fastForEach { child ->
-                    child.mergeConfig(mergedConfig)
-                }
+                mergeConfig(mergedConfig)
                 return mergedConfig
             } else {
                 return unmergedConfig
@@ -149,15 +160,17 @@
         }
 
     private fun mergeConfig(mergedConfig: SemanticsConfiguration) {
-        // Don't merge children that themselves merge all their descendants (because that
-        // indicates they're independently screen-reader-focusable).
-        if (isMergingSemanticsOfDescendants) {
-            return
-        }
+        if (!unmergedConfig.isClearingSemantics) {
+            unmergedChildren().fastForEach { child ->
+                // Don't merge children that themselves merge all their descendants (because that
+                // indicates they're independently screen-reader-focusable).
+                if (child.isMergingSemanticsOfDescendants) {
+                    return
+                }
 
-        mergedConfig.mergeChild(unmergedConfig)
-        unmergedChildren().fastForEach { child ->
-            child.mergeConfig(mergedConfig)
+                mergedConfig.mergeChild(child.unmergedConfig)
+                child.mergeConfig(mergedConfig)
+            }
         }
     }
 
@@ -167,7 +180,7 @@
     internal fun unmergedChildren(): List<SemanticsNode> {
         val unmergedChildren: MutableList<SemanticsNode> = mutableListOf()
 
-        val semanticsChildren = componentNode.findOneLayerOfSemanticsWrappers()
+        val semanticsChildren = this.layoutNode.findOneLayerOfSemanticsWrappers()
         semanticsChildren.fastForEach { semanticsChild ->
             unmergedChildren.add(SemanticsNode(semanticsChild, mergingEnabled))
         }
@@ -184,6 +197,11 @@
     //               optimize this when the merging algorithm is improved.
     val children: List<SemanticsNode>
         get() {
+            // Replacing semantics never appear to have any children in the merged tree.
+            if (mergingEnabled && unmergedConfig.isClearingSemantics) {
+                return listOf()
+            }
+
             if (isMergingSemanticsOfDescendants) {
                 // In most common merging scenarios like Buttons, this will return nothing.
                 // In cases like a clickable Row itself containing a Button, this will
@@ -233,7 +251,7 @@
         get() {
             var node: LayoutNode? = null
             if (mergingEnabled) {
-                node = componentNode.findClosestParentNode {
+                node = this.layoutNode.findClosestParentNode {
                     it.outerSemantics
                         ?.collapsedSemanticsConfiguration()
                         ?.isMergingSemanticsOfDescendants == true
@@ -241,7 +259,7 @@
             }
 
             if (node == null) {
-                node = componentNode.findClosestParentNode { it.outerSemantics != null }
+                node = this.layoutNode.findClosestParentNode { it.outerSemantics != null }
             }
 
             val outerSemantics = node?.outerSemantics
@@ -258,7 +276,9 @@
             if (child.isMergingSemanticsOfDescendants == true) {
                 list.add(child)
             } else {
-                child.findOneLayerOfMergingSemanticsNodes(list)
+                if (child.unmergedConfig.isClearingSemantics == false) {
+                    child.findOneLayerOfMergingSemanticsNodes(list)
+                }
             }
         }
         return list
@@ -268,7 +288,6 @@
 /**
  * Returns the outermost semantics node on a LayoutNode.
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal val LayoutNode.outerSemantics: SemanticsWrapper?
     get() {
         return outerLayoutNodeWrapper.nearestSemantics
@@ -296,7 +315,6 @@
     return null
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 private fun LayoutNode.findOneLayerOfSemanticsWrappers(
     list: MutableList<SemanticsWrapper> = mutableListOf<SemanticsWrapper>()
 ): List<SemanticsWrapper> {
@@ -310,3 +328,21 @@
     }
     return list
 }
+
+/**
+ * Executes [selector] on every parent of this [LayoutNode] and returns the closest
+ * [LayoutNode] to return `true` from [selector] or null if [selector] returns false
+ * for all ancestors.
+ */
+private fun LayoutNode.findClosestParentNode(selector: (LayoutNode) -> Boolean): LayoutNode? {
+    var currentParent = this.parent
+    while (currentParent != null) {
+        if (selector(currentParent)) {
+            return currentParent
+        } else {
+            currentParent = currentParent.parent
+        }
+    }
+
+    return null
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt
index 2eb1ecc..d7e2128 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsOwner.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.semantics
 
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.util.fastForEach
 
@@ -24,8 +23,7 @@
  * Owns [SemanticsNode] objects and notifies listeners of changes to the
  * semantics tree
  */
-@OptIn(ExperimentalLayoutNodeApi::class)
-class SemanticsOwner(val rootNode: LayoutNode) {
+class SemanticsOwner internal constructor(val rootNode: LayoutNode) {
     /**
      * The root node of the semantics tree.  Does not contain any unmerged data.
      * May contain merged data.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt
index efbe593..f40d31b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt
@@ -17,10 +17,8 @@
 package androidx.compose.ui.semantics
 
 import androidx.compose.ui.node.DelegatingLayoutNodeWrapper
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNodeWrapper
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class SemanticsWrapper(
     wrapped: LayoutNodeWrapper,
     semanticsModifier: SemanticsModifier
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
index 3188112..7635da5 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
@@ -41,7 +41,6 @@
 import androidx.compose.ui.input.pointer.PointerMoveEventFilter
 import androidx.compose.ui.layout.RootMeasureBlocks
 import androidx.compose.ui.layout.globalBounds
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.MeasureAndLayoutDelegate
@@ -60,7 +59,6 @@
 @OptIn(
     ExperimentalFocus::class,
     ExperimentalKeyInput::class,
-    ExperimentalLayoutNodeApi::class,
     ExperimentalComposeApi::class,
     InternalCoreApi::class
 )
@@ -78,6 +76,7 @@
     private val semanticsModifier = SemanticsModifierCore(
         id = SemanticsModifierCore.generateSemanticsId(),
         mergeDescendants = false,
+        clearAndSetSemantics = false,
         properties = {}
     )
 
@@ -178,9 +177,6 @@
         }
     }
 
-    override val hasPendingMeasureOrLayout
-        get() = measureAndLayoutDelegate.hasPendingMeasureOrLayout
-
     override fun createLayer(
         drawBlock: (Canvas) -> Unit,
         invalidateParentLayer: () -> Unit
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionManager.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionManager.kt
index 8cf64c5..ea143f0 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionManager.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionManager.kt
@@ -25,7 +25,9 @@
 import androidx.compose.ui.selection.getCurrentSelectedText
 import androidx.compose.ui.selection.merge
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 
+@OptIn(ExperimentalTextApi::class)
 internal class DesktopSelectionManager(private val selectionRegistrar: SelectionRegistrarImpl) {
     private var dragBeginPosition = Offset.Zero
 
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionRegistrar.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionRegistrar.kt
index ce9ba4b4..71eae61 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionRegistrar.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelectionRegistrar.kt
@@ -20,8 +20,10 @@
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.selection.Selectable
 import androidx.compose.ui.selection.SelectionRegistrar
+import androidx.compose.ui.text.ExperimentalTextApi
 
 // based on androidx.compose.ui.selection.SelectionRegistrarImpl
+@OptIn(ExperimentalTextApi::class)
 internal class DesktopSelectionRegistrar : SelectionRegistrar {
     internal var sorted: Boolean = false
 
@@ -71,12 +73,23 @@
         return selectables
     }
 
-    override fun onPositionChange() {
+    override fun notifyPositionChange() {
         sorted = false
         onPositionChangeCallback?.invoke()
     }
 
-    override fun onUpdateSelection(
+    override fun notifySelectionUpdateStart(
+        layoutCoordinates: LayoutCoordinates,
+        startPosition: Offset
+    ) {
+        onUpdateSelectionCallback?.invoke(
+            layoutCoordinates,
+            startPosition,
+            startPosition
+        )
+    }
+
+    override fun notifySelectionUpdate(
         layoutCoordinates: LayoutCoordinates,
         startPosition: Offset,
         endPosition: Offset
@@ -87,4 +100,6 @@
             endPosition
         )
     }
+
+    override fun notifySelectionUpdateEnd() { /* do nothing */ }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopUiApplier.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopUiApplier.kt
index 059de86..6fea479 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopUiApplier.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopUiApplier.kt
@@ -19,14 +19,16 @@
 
 import androidx.compose.runtime.AbstractApplier
 import androidx.compose.runtime.ExperimentalComposeApi
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 internal class DesktopUiApplier(
     root: LayoutNode
 ) : AbstractApplier<LayoutNode>(root) {
-    override fun insert(index: Int, instance: LayoutNode) {
+    override fun insertTopDown(index: Int, instance: LayoutNode) {
+        // ignored. Building tree bottom-up
+    }
+
+    override fun insertBottomUp(index: Int, instance: LayoutNode) {
         current.insertAt(index, instance)
     }
 
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
index 8555134..3ee2aa8 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
@@ -23,7 +23,6 @@
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.compositionFor
 import androidx.compose.ui.input.key.ExperimentalKeyInput
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.LayoutNode
 
 @OptIn(ExperimentalComposeApi::class, ExperimentalKeyInput::class)
@@ -64,8 +63,8 @@
     }
 }
 
-@OptIn(ExperimentalComposeApi::class, ExperimentalLayoutNodeApi::class)
-internal actual fun actualSubcomposeInto(
+@OptIn(ExperimentalComposeApi::class)
+internal actual fun subcomposeInto(
     container: LayoutNode,
     parent: CompositionReference,
     composable: @Composable () -> Unit
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilterTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilterTest.kt
index 1fed94d..d743d95 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilterTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilterTest.kt
@@ -146,7 +146,7 @@
             )
             Box(
                 Modifier
-                    .mouseScrollFilter { event, bounds ->
+                    .mouseScrollFilter { _, _ ->
                         false
                     }
                     .size(5.dp, 10.dp)
@@ -254,7 +254,7 @@
             ) {
                 Box(
                     Modifier
-                        .mouseScrollFilter { event, bounds ->
+                        .mouseScrollFilter { _, _ ->
                             false
                         }
                         .size(5.dp, 10.dp)
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
index 10adbae..cc7f3c6 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
@@ -44,7 +44,6 @@
 import androidx.compose.ui.input.mouse.MouseScrollEvent
 import androidx.compose.ui.input.mouse.MouseScrollUnit
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.test.junit4.DesktopScreenshotTestRule
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -54,7 +53,6 @@
 import org.junit.Test
 
 @OptIn(
-    ExperimentalLayoutNodeApi::class,
     ExperimentalComposeApi::class,
     ExperimentalCoroutinesApi::class
 )
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt
index 0a4c2b3..7485666 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/ComposedModifierTest.kt
@@ -241,7 +241,10 @@
     override val current: Unit = Unit
     override fun down(node: Unit) {}
     override fun up() {}
-    override fun insert(index: Int, instance: Unit) {
+    override fun insertTopDown(index: Int, instance: Unit) {
+        error("Unexpected")
+    }
+    override fun insertBottomUp(index: Int, instance: Unit) {
         error("Unexpected")
     }
     override fun remove(index: Int, count: Int) {
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
index cedded6..4caf023 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/focus/FocusManagerTest.kt
@@ -22,7 +22,6 @@
 import androidx.compose.ui.focus.FocusState.Captured
 import androidx.compose.ui.focus.FocusState.Disabled
 import androidx.compose.ui.focus.FocusState.Inactive
-import androidx.compose.ui.node.ExperimentalLayoutNodeApi
 import androidx.compose.ui.node.InnerPlaceable
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.ModifiedFocusNode
@@ -35,7 +34,6 @@
 
 @OptIn(
     ExperimentalFocus::class,
-    ExperimentalLayoutNodeApi::class
 )
 @RunWith(Parameterized::class)
 class FocusManagerTest(private val initialFocusState: FocusState) {
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt
index 20cffbc..3d5fc83 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DoubleTapGestureFilterTest.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture
 
 import androidx.compose.ui.Modifier
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt
index 778a789..f45f5c0 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/DragSlopExceededGestureFilterTest.kt
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
 @file:Suppress("PrivatePropertyName")
 
 package androidx.compose.ui.gesture
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt
index 9d686f0..658136a 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/TapGestureFilterTest.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture
 
 import androidx.compose.ui.Modifier
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerSetupTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerSetupTest.kt
index b70b2b6..70abfca 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerSetupTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerSetupTest.kt
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture.scrollorientationlocking
 
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.CustomEvent
 import androidx.compose.ui.input.pointer.CustomEventDispatcher
 import androidx.compose.ui.input.pointer.PointerEventPass
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerTest.kt
index 8d26027..37139367 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/ScrollOrientationLockerTest.kt
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalPointerInput::class)
-
 package androidx.compose.ui.gesture.scrollorientationlocking
 
-import androidx.compose.ui.gesture.ExperimentalPointerInput
 import androidx.compose.ui.input.pointer.CustomEventDispatcher
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerInputChange
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index b954d68..fe31fb5 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -66,7 +66,6 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@OptIn(ExperimentalLayoutNodeApi::class)
 class LayoutNodeTest {
     @get:Rule
     val thrown = ExpectedException.none()!!
@@ -80,13 +79,13 @@
         val owner = MockOwner()
         node.attach(owner)
         assertEquals(owner, node.owner)
-        assertTrue(node.isAttached())
+        assertTrue(node.isAttached)
 
         assertEquals(1, owner.onAttachParams.count { it === node })
 
         node.detach()
         assertNull(node.owner)
-        assertFalse(node.isAttached())
+        assertFalse(node.isAttached)
         assertEquals(1, owner.onDetachParams.count { it === node })
     }
 
@@ -1634,8 +1633,8 @@
         // Dispose
         root.removeAt(0, 1)
 
-        assertFalse(node1.isAttached())
-        assertFalse(node2.isAttached())
+        assertFalse(node1.isAttached)
+        assertFalse(node2.isAttached)
         assertEquals(0, owner.onRequestMeasureParams.count { it === node1 })
         assertEquals(0, owner.onRequestMeasureParams.count { it === node2 })
     }
@@ -1657,7 +1656,6 @@
 
 @OptIn(
     ExperimentalFocus::class,
-    ExperimentalLayoutNodeApi::class,
     InternalCoreApi::class
 )
 private class MockOwner(
@@ -1704,8 +1702,6 @@
         layoutNode.layoutState = LayoutNode.LayoutState.NeedsRelayout
     }
 
-    override val hasPendingMeasureOrLayout = false
-
     override fun onAttach(node: LayoutNode) {
         onAttachParams += node
     }
@@ -1781,7 +1777,6 @@
         get() = TODO("Not yet implemented")
 }
 
-@OptIn(ExperimentalLayoutNodeApi::class)
 fun LayoutNode(x: Int, y: Int, x2: Int, y2: Int, modifier: Modifier = Modifier) =
     LayoutNode().apply {
         this.modifier = modifier
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/MockSelectable.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/MockSelectable.kt
index ede87ef..baa520a 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/MockSelectable.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/MockSelectable.kt
@@ -20,7 +20,9 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 
+@OptIn(ExperimentalTextApi::class)
 class MockSelectable(
     var getSelectionValue: Selection? = null,
     var getHandlePositionValue: Offset = Offset.Zero,
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerDragTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerDragTest.kt
index 323adf5..b066747 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerDragTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerDragTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.any
@@ -32,6 +33,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@OptIn(ExperimentalTextApi::class)
 @RunWith(JUnit4::class)
 class SelectionManagerDragTest {
     private val selectionRegistrar = SelectionRegistrarImpl()
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerTest.kt
index ff70baa..18ce38e 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionManagerTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.platform.ClipboardManager
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.length
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import androidx.compose.ui.text.subSequence
@@ -42,6 +43,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@OptIn(ExperimentalTextApi::class)
 @RunWith(JUnit4::class)
 class SelectionManagerTest {
     private val selectionRegistrar = spy(SelectionRegistrarImpl())
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionRegistrarImplTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionRegistrarImplTest.kt
index 130db56..4bf9891a 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionRegistrarImplTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionRegistrarImplTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.ExperimentalTextApi
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.whenever
@@ -25,6 +26,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@OptIn(ExperimentalTextApi::class)
 @RunWith(JUnit4::class)
 class SelectionRegistrarImplTest {
     @Test
@@ -188,7 +190,7 @@
         assertThat(selectionRegistrar.sorted).isTrue()
 
         // Act.
-        selectionRegistrar.onPositionChange()
+        selectionRegistrar.notifyPositionChange()
 
         // Assert.
         assertThat(selectionRegistrar.sorted).isFalse()
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionTest.kt
index 5febaab..6afcab9 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/selection/SelectionTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.selection
 
+import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import com.google.common.truth.Truth.assertThat
@@ -24,6 +25,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@OptIn(ExperimentalTextApi::class)
 @RunWith(JUnit4::class)
 class SelectionTest {
     @Test
diff --git a/concurrent/futures-ktx/src/main/java/androidx/concurrent/futures/ListenableFuture.kt b/concurrent/futures-ktx/src/main/java/androidx/concurrent/futures/ListenableFuture.kt
index ddde289..8bf37d1 100644
--- a/concurrent/futures-ktx/src/main/java/androidx/concurrent/futures/ListenableFuture.kt
+++ b/concurrent/futures-ktx/src/main/java/androidx/concurrent/futures/ListenableFuture.kt
@@ -100,6 +100,6 @@
  * If this !! throws [NullPointerException], a Future is breaking its interface contract and losing
  * state - a serious fundamental bug.
  */
-private fun ExecutionException.nonNullCause(): Throwable {
+internal fun ExecutionException.nonNullCause(): Throwable {
     return this.cause!!
-}
\ No newline at end of file
+}
diff --git a/core/core-appdigest/api/current.txt b/core/core-appdigest/api/current.txt
index 6272e90..225b9a4 100644
--- a/core/core-appdigest/api/current.txt
+++ b/core/core-appdigest/api/current.txt
@@ -18,7 +18,7 @@
 
   public final class Checksums {
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.core.appdigest.Checksum![]!> getChecksums(android.content.Context, String, boolean, int, java.util.List<java.security.cert.Certificate!>, java.util.concurrent.Executor) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
-    field public static final java.util.List<java.security.cert.Certificate!>? TRUST_ALL;
+    field public static final java.util.List<java.security.cert.Certificate!> TRUST_ALL;
     field public static final java.util.List<java.security.cert.Certificate!> TRUST_NONE;
   }
 
diff --git a/core/core-appdigest/api/public_plus_experimental_current.txt b/core/core-appdigest/api/public_plus_experimental_current.txt
index 6272e90..225b9a4 100644
--- a/core/core-appdigest/api/public_plus_experimental_current.txt
+++ b/core/core-appdigest/api/public_plus_experimental_current.txt
@@ -18,7 +18,7 @@
 
   public final class Checksums {
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.core.appdigest.Checksum![]!> getChecksums(android.content.Context, String, boolean, int, java.util.List<java.security.cert.Certificate!>, java.util.concurrent.Executor) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
-    field public static final java.util.List<java.security.cert.Certificate!>? TRUST_ALL;
+    field public static final java.util.List<java.security.cert.Certificate!> TRUST_ALL;
     field public static final java.util.List<java.security.cert.Certificate!> TRUST_NONE;
   }
 
diff --git a/core/core-appdigest/api/restricted_current.txt b/core/core-appdigest/api/restricted_current.txt
index 6272e90..225b9a4 100644
--- a/core/core-appdigest/api/restricted_current.txt
+++ b/core/core-appdigest/api/restricted_current.txt
@@ -18,7 +18,7 @@
 
   public final class Checksums {
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.core.appdigest.Checksum![]!> getChecksums(android.content.Context, String, boolean, int, java.util.List<java.security.cert.Certificate!>, java.util.concurrent.Executor) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
-    field public static final java.util.List<java.security.cert.Certificate!>? TRUST_ALL;
+    field public static final java.util.List<java.security.cert.Certificate!> TRUST_ALL;
     field public static final java.util.List<java.security.cert.Certificate!> TRUST_NONE;
   }
 
diff --git a/core/core-appdigest/src/main/java/androidx/core/appdigest/Checksums.java b/core/core-appdigest/src/main/java/androidx/core/appdigest/Checksums.java
index 2cc30c3..2046117 100644
--- a/core/core-appdigest/src/main/java/androidx/core/appdigest/Checksums.java
+++ b/core/core-appdigest/src/main/java/androidx/core/appdigest/Checksums.java
@@ -31,7 +31,6 @@
 import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.concurrent.futures.ResolvableFuture;
 import androidx.core.util.Preconditions;
 
@@ -60,7 +59,7 @@
      * Trust any Installer to provide checksums for the package.
      * @see #getChecksums
      */
-    public static final @Nullable List<Certificate> TRUST_ALL = Collections.singletonList(null);
+    public static final @NonNull List<Certificate> TRUST_ALL = Collections.singletonList(null);
 
     /**
      * Don't trust any Installer to provide checksums for the package.
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 2e71858..e207eab 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -842,8 +842,8 @@
   }
 
   public final class ShareCompat {
-    method public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
-    method public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
+    method @Deprecated public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
+    method @Deprecated public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
     method public static android.content.ComponentName? getCallingActivity(android.app.Activity);
     method public static String? getCallingPackage(android.app.Activity);
     field public static final String EXTRA_CALLING_ACTIVITY = "androidx.core.app.EXTRA_CALLING_ACTIVITY";
@@ -1524,7 +1524,7 @@
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.O_MR1) public static boolean isAtLeastOMR1();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.P) public static boolean isAtLeastP();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
-    method @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
+    method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
     method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
   }
 
@@ -2208,6 +2208,15 @@
     method public androidx.core.view.WindowInsetsCompat! onApplyWindowInsets(android.view.View!, androidx.core.view.WindowInsetsCompat!);
   }
 
+  public abstract class OnReceiveContentListener<T extends android.view.View> {
+    ctor public OnReceiveContentListener();
+    method public abstract java.util.Set<java.lang.String!> getSupportedMimeTypes();
+    method public abstract boolean onReceive(T, android.content.ClipData, int, int);
+    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
+    field public static final int SOURCE_CLIPBOARD = 0; // 0x0
+    field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
+  }
+
   public final class OneShotPreDrawListener implements android.view.View.OnAttachStateChangeListener android.view.ViewTreeObserver.OnPreDrawListener {
     method public static androidx.core.view.OneShotPreDrawListener add(android.view.View, Runnable);
     method public boolean onPreDraw();
@@ -3335,15 +3344,6 @@
     method public static void showAsDropDown(android.widget.PopupWindow, android.view.View, int, int, int);
   }
 
-  public abstract class RichContentReceiverCompat<T extends android.view.View> {
-    ctor public RichContentReceiverCompat();
-    method public abstract java.util.Set<java.lang.String!> getSupportedMimeTypes();
-    method public abstract boolean onReceive(T, android.content.ClipData, int, int);
-    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
-    field public static final int SOURCE_CLIPBOARD = 0; // 0x0
-    field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
-  }
-
   @Deprecated public final class ScrollerCompat {
     method @Deprecated public void abortAnimation();
     method @Deprecated public boolean computeScrollOffset();
@@ -3398,8 +3398,8 @@
     field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
   }
 
-  public abstract class TextViewRichContentReceiverCompat extends androidx.core.widget.RichContentReceiverCompat<android.widget.TextView> {
-    ctor public TextViewRichContentReceiverCompat();
+  public abstract class TextViewOnReceiveContentListener extends androidx.core.view.OnReceiveContentListener<android.widget.TextView> {
+    ctor public TextViewOnReceiveContentListener();
     method public java.util.Set<java.lang.String!> getSupportedMimeTypes();
     method public boolean onReceive(android.widget.TextView, android.content.ClipData, int, int);
   }
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 8c72f00..15f28ad 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -842,8 +842,8 @@
   }
 
   public final class ShareCompat {
-    method public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
-    method public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
+    method @Deprecated public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
+    method @Deprecated public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
     method public static android.content.ComponentName? getCallingActivity(android.app.Activity);
     method public static String? getCallingPackage(android.app.Activity);
     field public static final String EXTRA_CALLING_ACTIVITY = "androidx.core.app.EXTRA_CALLING_ACTIVITY";
@@ -1522,7 +1522,7 @@
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.O_MR1) public static boolean isAtLeastOMR1();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.P) public static boolean isAtLeastP();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
-    method @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
+    method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
     method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
   }
 
@@ -2206,6 +2206,15 @@
     method public androidx.core.view.WindowInsetsCompat! onApplyWindowInsets(android.view.View!, androidx.core.view.WindowInsetsCompat!);
   }
 
+  public abstract class OnReceiveContentListener<T extends android.view.View> {
+    ctor public OnReceiveContentListener();
+    method public abstract java.util.Set<java.lang.String!> getSupportedMimeTypes();
+    method public abstract boolean onReceive(T, android.content.ClipData, int, int);
+    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
+    field public static final int SOURCE_CLIPBOARD = 0; // 0x0
+    field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
+  }
+
   public final class OneShotPreDrawListener implements android.view.View.OnAttachStateChangeListener android.view.ViewTreeObserver.OnPreDrawListener {
     method public static androidx.core.view.OneShotPreDrawListener add(android.view.View, Runnable);
     method public boolean onPreDraw();
@@ -3333,15 +3342,6 @@
     method public static void showAsDropDown(android.widget.PopupWindow, android.view.View, int, int, int);
   }
 
-  public abstract class RichContentReceiverCompat<T extends android.view.View> {
-    ctor public RichContentReceiverCompat();
-    method public abstract java.util.Set<java.lang.String!> getSupportedMimeTypes();
-    method public abstract boolean onReceive(T, android.content.ClipData, int, int);
-    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
-    field public static final int SOURCE_CLIPBOARD = 0; // 0x0
-    field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
-  }
-
   @Deprecated public final class ScrollerCompat {
     method @Deprecated public void abortAnimation();
     method @Deprecated public boolean computeScrollOffset();
@@ -3396,8 +3396,8 @@
     field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
   }
 
-  public abstract class TextViewRichContentReceiverCompat extends androidx.core.widget.RichContentReceiverCompat<android.widget.TextView> {
-    ctor public TextViewRichContentReceiverCompat();
+  public abstract class TextViewOnReceiveContentListener extends androidx.core.view.OnReceiveContentListener<android.widget.TextView> {
+    ctor public TextViewOnReceiveContentListener();
     method public java.util.Set<java.lang.String!> getSupportedMimeTypes();
     method public boolean onReceive(android.widget.TextView, android.content.ClipData, int, int);
   }
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 729b498..bc48762 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -945,8 +945,8 @@
   }
 
   public final class ShareCompat {
-    method public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
-    method public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
+    method @Deprecated public static void configureMenuItem(android.view.MenuItem, androidx.core.app.ShareCompat.IntentBuilder);
+    method @Deprecated public static void configureMenuItem(android.view.Menu, @IdRes int, androidx.core.app.ShareCompat.IntentBuilder);
     method public static android.content.ComponentName? getCallingActivity(android.app.Activity);
     method public static String? getCallingPackage(android.app.Activity);
     field public static final String EXTRA_CALLING_ACTIVITY = "androidx.core.app.EXTRA_CALLING_ACTIVITY";
@@ -1834,7 +1834,7 @@
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.O_MR1) public static boolean isAtLeastOMR1();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.P) public static boolean isAtLeastP();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
-    method @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
+    method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
     method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
   }
 
@@ -2602,6 +2602,23 @@
     method public androidx.core.view.WindowInsetsCompat! onApplyWindowInsets(android.view.View!, androidx.core.view.WindowInsetsCompat!);
   }
 
+  public abstract class OnReceiveContentListener<T extends android.view.View> {
+    ctor public OnReceiveContentListener();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final androidx.core.view.inputmethod.InputConnectionCompat.OnCommitContentListener buildOnCommitContentListener(T);
+    method public abstract java.util.Set<java.lang.String!> getSupportedMimeTypes();
+    method public abstract boolean onReceive(T, android.content.ClipData, @androidx.core.view.OnReceiveContentListener.Source int, @androidx.core.view.OnReceiveContentListener.Flags int);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final void populateEditorInfoContentMimeTypes(android.view.inputmethod.InputConnection?, android.view.inputmethod.EditorInfo?);
+    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
+    field public static final int SOURCE_CLIPBOARD = 0; // 0x0
+    field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
+  }
+
+  @IntDef(flag=true, value={androidx.core.view.OnReceiveContentListener.FLAG_CONVERT_TO_PLAIN_TEXT}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface OnReceiveContentListener.Flags {
+  }
+
+  @IntDef({androidx.core.view.OnReceiveContentListener.SOURCE_CLIPBOARD, androidx.core.view.OnReceiveContentListener.SOURCE_INPUT_METHOD}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface OnReceiveContentListener.Source {
+  }
+
   public final class OneShotPreDrawListener implements android.view.View.OnAttachStateChangeListener android.view.ViewTreeObserver.OnPreDrawListener {
     method public static androidx.core.view.OneShotPreDrawListener add(android.view.View, Runnable);
     method public boolean onPreDraw();
@@ -3776,17 +3793,6 @@
     method public static void showAsDropDown(android.widget.PopupWindow, android.view.View, int, int, int);
   }
 
-  public abstract class RichContentReceiverCompat<T extends android.view.View> {
-    ctor public RichContentReceiverCompat();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final androidx.core.view.inputmethod.InputConnectionCompat.OnCommitContentListener buildOnCommitContentListener(T);
-    method public abstract java.util.Set<java.lang.String!> getSupportedMimeTypes();
-    method public abstract boolean onReceive(T, android.content.ClipData, int, int);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final void populateEditorInfoContentMimeTypes(android.view.inputmethod.InputConnection?, android.view.inputmethod.EditorInfo?);
-    field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
-    field public static final int SOURCE_CLIPBOARD = 0; // 0x0
-    field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
-  }
-
   @Deprecated public final class ScrollerCompat {
     method @Deprecated public void abortAnimation();
     method @Deprecated public boolean computeScrollOffset();
@@ -3845,10 +3851,10 @@
   @IntDef({androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE, androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface TextViewCompat.AutoSizeTextType {
   }
 
-  public abstract class TextViewRichContentReceiverCompat extends androidx.core.widget.RichContentReceiverCompat<android.widget.TextView> {
-    ctor public TextViewRichContentReceiverCompat();
+  public abstract class TextViewOnReceiveContentListener extends androidx.core.view.OnReceiveContentListener<android.widget.TextView> {
+    ctor public TextViewOnReceiveContentListener();
     method public java.util.Set<java.lang.String!> getSupportedMimeTypes();
-    method public boolean onReceive(android.widget.TextView, android.content.ClipData, int, int);
+    method public boolean onReceive(android.widget.TextView, android.content.ClipData, @androidx.core.view.OnReceiveContentListener.Source int, @androidx.core.view.OnReceiveContentListener.Flags int);
   }
 
   public interface TintableCompoundButton {
diff --git a/core/core/lint-baseline.xml b/core/core/lint-baseline.xml
index 7f2ea99..8c91e30 100644
--- a/core/core/lint-baseline.xml
+++ b/core/core/lint-baseline.xml
@@ -11537,17 +11537,6 @@
 
     <issue
         id="UnsafeNewApiCall"
-        message="This call is to a method from API 16, the call containing class androidx.core.widget.TextViewRichContentReceiverCompat is not annotated with @RequiresApi(x) where x is at least 16. Either annotate the containing class with at least @RequiresApi(16) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(16)."
-        errorLine1="                    paste = clip.getItemAt(i).coerceToStyledText(context);"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/core/widget/TextViewRichContentReceiverCompat.java"
-            line="82"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
         message="This call is to a method from API 29, the call containing class androidx.core.os.TraceCompat is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
         errorLine1="            return Trace.isEnabled();"
         errorLine2="                         ~~~~~~~~~">
diff --git a/core/core/src/androidTest/AndroidManifest.xml b/core/core/src/androidTest/AndroidManifest.xml
index d8170ee..9c5a7f8 100644
--- a/core/core/src/androidTest/AndroidManifest.xml
+++ b/core/core/src/androidTest/AndroidManifest.xml
@@ -34,7 +34,7 @@
 
         <activity android:name="androidx.core.widget.TextViewTestActivity"/>
 
-        <activity android:name="androidx.core.widget.RichContentReceiverTestActivity"/>
+        <activity android:name="androidx.core.widget.ReceiveContentTestActivity"/>
 
         <activity android:name="androidx.core.widget.TestContentViewActivity"/>
 
diff --git a/core/core/src/androidTest/java/androidx/core/widget/RichContentReceiverTestActivity.java b/core/core/src/androidTest/java/androidx/core/widget/ReceiveContentTestActivity.java
similarity index 85%
rename from core/core/src/androidTest/java/androidx/core/widget/RichContentReceiverTestActivity.java
rename to core/core/src/androidTest/java/androidx/core/widget/ReceiveContentTestActivity.java
index 83a6ba0..bc5a607 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/RichContentReceiverTestActivity.java
+++ b/core/core/src/androidTest/java/androidx/core/widget/ReceiveContentTestActivity.java
@@ -20,9 +20,9 @@
 
 import androidx.core.test.R;
 
-public class RichContentReceiverTestActivity extends BaseTestActivity {
+public class ReceiveContentTestActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return R.layout.rich_content_receiver_activity;
+        return R.layout.receive_content_activity;
     }
 }
diff --git a/core/core/src/androidTest/java/androidx/core/widget/RichContentReceiverCompatTest.java b/core/core/src/androidTest/java/androidx/core/widget/TextViewOnReceiveContentListenerTest.java
similarity index 92%
rename from core/core/src/androidTest/java/androidx/core/widget/RichContentReceiverCompatTest.java
rename to core/core/src/androidTest/java/androidx/core/widget/TextViewOnReceiveContentListenerTest.java
index 6310271..9608240 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/RichContentReceiverCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/widget/TextViewOnReceiveContentListenerTest.java
@@ -16,9 +16,9 @@
 
 package androidx.core.widget;
 
-import static androidx.core.widget.RichContentReceiverCompat.FLAG_CONVERT_TO_PLAIN_TEXT;
-import static androidx.core.widget.RichContentReceiverCompat.SOURCE_CLIPBOARD;
-import static androidx.core.widget.RichContentReceiverCompat.SOURCE_INPUT_METHOD;
+import static androidx.core.view.OnReceiveContentListener.FLAG_CONVERT_TO_PLAIN_TEXT;
+import static androidx.core.view.OnReceiveContentListener.SOURCE_CLIPBOARD;
+import static androidx.core.view.OnReceiveContentListener.SOURCE_INPUT_METHOD;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -37,6 +37,7 @@
 import android.widget.TextView;
 
 import androidx.core.test.R;
+import androidx.core.view.OnReceiveContentListener;
 import androidx.core.view.inputmethod.EditorInfoCompat;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -51,20 +52,20 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class RichContentReceiverCompatTest {
+public class TextViewOnReceiveContentListenerTest {
 
     @Rule
-    public final ActivityTestRule<RichContentReceiverTestActivity> mActivityTestRule =
-            new ActivityTestRule<>(RichContentReceiverTestActivity.class);
+    public final ActivityTestRule<ReceiveContentTestActivity> mActivityTestRule =
+            new ActivityTestRule<>(ReceiveContentTestActivity.class);
 
     private EditText mEditText;
-    private RichContentReceiverCompat<TextView> mReceiver;
+    private TextViewOnReceiveContentListener mReceiver;
 
     @Before
     public void before() {
-        RichContentReceiverTestActivity activity = mActivityTestRule.getActivity();
-        mEditText = activity.findViewById(R.id.edit_text_for_rich_content_receiver);
-        mReceiver = new TextViewRichContentReceiverCompat() {};
+        ReceiveContentTestActivity activity = mActivityTestRule.getActivity();
+        mEditText = activity.findViewById(R.id.edit_text);
+        mReceiver = new TextViewOnReceiveContentListener() {};
     }
 
     @UiThreadTest
@@ -281,8 +282,8 @@
         assertThat(EditorInfoCompat.getContentMimeTypes(outAttrs)).isEqualTo(new String[0]);
     }
 
-    private boolean onReceive(final RichContentReceiverCompat<TextView> receiver,
-            final ClipData clip, @RichContentReceiverCompat.Source final int source,
+    private boolean onReceive(final OnReceiveContentListener<TextView> receiver,
+            final ClipData clip, @OnReceiveContentListener.Source final int source,
             final int flags) {
         return receiver.onReceive(mEditText, clip, source, flags);
     }
diff --git a/core/core/src/androidTest/res/layout/rich_content_receiver_activity.xml b/core/core/src/androidTest/res/layout/receive_content_activity.xml
similarity index 93%
rename from core/core/src/androidTest/res/layout/rich_content_receiver_activity.xml
rename to core/core/src/androidTest/res/layout/receive_content_activity.xml
index 81b6479..a4ea03c 100644
--- a/core/core/src/androidTest/res/layout/rich_content_receiver_activity.xml
+++ b/core/core/src/androidTest/res/layout/receive_content_activity.xml
@@ -21,7 +21,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <EditText
-        android:id="@+id/edit_text_for_rich_content_receiver"
+        android:id="@+id/edit_text"
         android:layout_width="200dip"
         android:layout_height="60dip" />
 </FrameLayout>
diff --git a/core/core/src/main/java/androidx/core/app/ShareCompat.java b/core/core/src/main/java/androidx/core/app/ShareCompat.java
index 79bce4d8..8d6b8ac 100644
--- a/core/core/src/main/java/androidx/core/app/ShareCompat.java
+++ b/core/core/src/main/java/androidx/core/app/ShareCompat.java
@@ -230,7 +230,10 @@
      *
      * @param item MenuItem to configure for sharing
      * @param shareIntent IntentBuilder with data about the content to share
+     *
+     * @deprecated Use the system sharesheet. See https://developer.android.com/training/sharing/send
      */
+    @Deprecated
     public static void configureMenuItem(@NonNull MenuItem item,
             @NonNull IntentBuilder shareIntent) {
         ActionProvider itemProvider = item.getActionProvider();
@@ -259,7 +262,10 @@
      * @param menuItemId ID of the share item within menu
      * @param shareIntent IntentBuilder with data about the content to share
      * @see #configureMenuItem(MenuItem, IntentBuilder)
+     *
+     * @deprecated Use the system sharesheet. See https://developer.android.com/training/sharing/send
      */
+    @Deprecated
     public static void configureMenuItem(@NonNull Menu menu, @IdRes int menuItemId,
             @NonNull IntentBuilder shareIntent) {
         MenuItem item = menu.findItem(menuItemId);
@@ -423,12 +429,6 @@
 
         /**
          * Start a chooser activity for the current share intent.
-         *
-         * <p>Note that under most circumstances you should use
-         * {@link ShareCompat#configureMenuItem(MenuItem, IntentBuilder)
-         *  ShareCompat.configureMenuItem()} to add a Share item to the menu while
-         * presenting a detail view of the content to be shared instead
-         * of invoking this directly.</p>
          */
         public void startChooser() {
             mContext.startActivity(createChooserIntent());
diff --git a/core/core/src/main/java/androidx/core/os/BuildCompat.java b/core/core/src/main/java/androidx/core/os/BuildCompat.java
index e14d683..ba91322 100644
--- a/core/core/src/main/java/androidx/core/os/BuildCompat.java
+++ b/core/core/src/main/java/androidx/core/os/BuildCompat.java
@@ -114,18 +114,17 @@
     }
 
     /**
-     * Checks if the device is running on a pre-release version of Android R or a release
-     * version of Android R or newer.
+     * Checks if the device is running on release version of Android R or newer.
      * <p>
-     * <strong>Note:</strong> When Android R is finalized for release, this method will be
-     * deprecated and all calls should be replaced with
-     * {@code Build.VERSION.SDK_INT >= Build.VERSION_CODES.R}.
-     *
      * @return {@code true} if R APIs are available for use, {@code false} otherwise
+     * @deprecated Android R is a finalized release and this method is no longer necessary. It
+     *             will be removed in a future release of the Support Library. Instead, use
+     *             {@code Build.VERSION.SDK_INT >= Build.VERSION_CODES.R}.
      */
     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
+    @Deprecated
     public static boolean isAtLeastR() {
-        return VERSION.SDK_INT >= 30 || VERSION.CODENAME.equals("R");
+        return VERSION.SDK_INT >= 30;
     }
 
     /**
diff --git a/core/core/src/main/java/androidx/core/widget/RichContentReceiverCompat.java b/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
similarity index 91%
rename from core/core/src/main/java/androidx/core/widget/RichContentReceiverCompat.java
rename to core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
index 2a66954..9a829f4 100644
--- a/core/core/src/main/java/androidx/core/widget/RichContentReceiverCompat.java
+++ b/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.core.widget;
+package androidx.core.view;
 
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -43,11 +43,11 @@
  *
  * <p>This callback can be attached to different types of UI components. For editable
  * {@link android.widget.TextView} components, implementations should typically extend from
- * {@link TextViewRichContentReceiverCompat}.
+ * {@link androidx.core.widget.TextViewOnReceiveContentListener}.
  *
  * <p>Example implementation:<br>
  * <pre class="prettyprint">
- *   public class MyRichContentReceiver extends TextViewRichContentReceiverCompat {
+ *   public class MyRichContentReceiver extends TextViewOnReceiveContentListenerCompat {
  *
  *       private static final Set&lt;String&gt; SUPPORTED_MIME_TYPES = Collections.unmodifiableSet(
  *           Set.of("text/*", "image/gif", "image/png", "image/jpg"));
@@ -75,15 +75,19 @@
  *
  * @param <T> The type of {@link View} with which this receiver can be associated.
  */
-public abstract class RichContentReceiverCompat<T extends View> {
-    private static final String TAG = "RichContentReceiver";
+@SuppressWarnings("ListenerInterface")
+public abstract class OnReceiveContentListener<T extends View> {
+    private static final String TAG = "ReceiveContent";
 
     /**
      * Specifies the UI through which content is being inserted.
+     *
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     @IntDef(value = {SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD})
     @Retention(RetentionPolicy.SOURCE)
-    @interface Source {}
+    public @interface Source {}
 
     /**
      * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or
@@ -100,10 +104,13 @@
 
     /**
      * Flags to configure the insertion behavior.
+     *
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     @IntDef(flag = true, value = {FLAG_CONVERT_TO_PLAIN_TEXT})
     @Retention(RetentionPolicy.SOURCE)
-    @interface Flags {}
+    public @interface Flags {}
 
     /**
      * Flag for {@link #onReceive} requesting that the content should be converted to plain text
@@ -149,12 +156,13 @@
      * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the
      * keyboard, etc) may use this function to conditionally alter their behavior. For example, the
      * keyboard may choose to hide its UI for inserting GIFs if the input field that has focus has
-     * a {@link RichContentReceiverCompat} set and the MIME types returned from this function
+     * a {@link OnReceiveContentListener} set and the MIME types returned from this function
      * don't include "image/gif".
      *
      * @return An immutable set with the MIME types supported by this callback. The returned
      * MIME types may contain wildcards such as "text/*", "image/*", etc.
      */
+    @SuppressWarnings("CallbackMethodName")
     @NonNull
     public abstract Set<String> getSupportedMimeTypes();
 
diff --git a/core/core/src/main/java/androidx/core/widget/TextViewRichContentReceiverCompat.java b/core/core/src/main/java/androidx/core/widget/TextViewOnReceiveContentListener.java
similarity index 65%
rename from core/core/src/main/java/androidx/core/widget/TextViewRichContentReceiverCompat.java
rename to core/core/src/main/java/androidx/core/widget/TextViewOnReceiveContentListener.java
index 31c51b1..f727f2f 100644
--- a/core/core/src/main/java/androidx/core/widget/TextViewRichContentReceiverCompat.java
+++ b/core/core/src/main/java/androidx/core/widget/TextViewOnReceiveContentListener.java
@@ -25,29 +25,33 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.core.view.OnReceiveContentListener;
 
 import java.util.Collections;
 import java.util.Set;
 
 /**
- * Base implementation of {@link RichContentReceiverCompat} for editable {@link TextView}
+ * Base implementation of {@link OnReceiveContentListener} for editable {@link TextView}
  * components.
  *
  * <p>This class handles insertion of text (plain text, styled text, HTML, etc) but not images or
  * other rich content. It should be used as a base class when implementing a custom
- * {@link RichContentReceiverCompat}, to provide consistent behavior for insertion of text while
- * implementing custom behavior for insertion of other content (images, etc).
+ * {@link OnReceiveContentListener}, to provide consistent behavior for insertion of text
+ * while implementing custom behavior for insertion of other content (images, etc).
  *
- * <p>See {@link RichContentReceiverCompat} for an example of how to implement a custom receiver.
+ * <p>See {@link OnReceiveContentListener} for an example of how to implement the listener.
  */
-public abstract class TextViewRichContentReceiverCompat extends
-        RichContentReceiverCompat<TextView> {
+@SuppressWarnings("ListenerInterface")
+public abstract class TextViewOnReceiveContentListener extends
+        OnReceiveContentListener<TextView> {
 
     private static final Set<String> MIME_TYPES_ALL_TEXT = Collections.singleton("text/*");
 
     /**
      * {@inheritDoc}
      */
+    @SuppressWarnings("CallbackMethodName")
     @Override
     @NonNull
     public Set<String> getSupportedMimeTypes() {
@@ -74,15 +78,10 @@
         boolean didFirst = false;
         for (int i = 0; i < clip.getItemCount(); i++) {
             CharSequence paste;
-            if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) {
-                paste = clip.getItemAt(i).coerceToText(context);
-                paste = (paste instanceof Spanned) ? paste.toString() : paste;
+            if (Build.VERSION.SDK_INT >= 16) {
+                paste = CoerceToTextApi16Impl.coerce(context, clip.getItemAt(i), flags);
             } else {
-                if (Build.VERSION.SDK_INT >= 16) {
-                    paste = clip.getItemAt(i).coerceToStyledText(context);
-                } else {
-                    paste = clip.getItemAt(i).coerceToText(context);
-                }
+                paste = CoerceToTextImpl.coerce(context, clip.getItemAt(i), flags);
             }
             if (paste != null) {
                 if (!didFirst) {
@@ -101,4 +100,30 @@
         }
         return didFirst;
     }
+
+    @RequiresApi(16) // For ClipData.Item.coerceToStyledText()
+    private static final class CoerceToTextApi16Impl {
+        private CoerceToTextApi16Impl() {}
+
+        static CharSequence coerce(Context context, ClipData.Item item, @Flags int flags) {
+            if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) {
+                CharSequence text = item.coerceToText(context);
+                return (text instanceof Spanned) ? text.toString() : text;
+            } else {
+                return item.coerceToStyledText(context);
+            }
+        }
+    }
+
+    private static final class CoerceToTextImpl {
+        private CoerceToTextImpl() {}
+
+        static CharSequence coerce(Context context, ClipData.Item item, @Flags int flags) {
+            CharSequence text = item.coerceToText(context);
+            if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0 && text instanceof Spanned) {
+                text = text.toString();
+            }
+            return text;
+        }
+    }
 }
diff --git a/datastore/datastore-core/src/main/java/androidx/datastore/core/DataMigration.kt b/datastore/datastore-core/src/main/java/androidx/datastore/core/DataMigration.kt
index c858653..4318b91 100644
--- a/datastore/datastore-core/src/main/java/androidx/datastore/core/DataMigration.kt
+++ b/datastore/datastore-core/src/main/java/androidx/datastore/core/DataMigration.kt
@@ -35,6 +35,9 @@
      *
      * Note that this will always be called before each call to [migrate].
      *
+     * Note that accessing any data from DataStore directly from inside this function will result
+     * in deadlock, since DataStore doesn't return data until all migrations complete.
+     *
      * @param currentData the current data (which might already populated from previous runs of this
      * or other migrations)
      */
@@ -49,6 +52,9 @@
      *
      * Note that this will always be called before a call to [cleanUp].
      *
+     * Note that accessing any data from DataStore directly from inside this function will result
+     * in deadlock, since DataStore doesn't return data until all migrations complete.
+     *
      * @param currentData the current data (it might be populated from other migrations or from
      * manual changes before this migration was added to the app)
      * @return The migrated data.
@@ -61,6 +67,10 @@
      * back to the DataStore call that triggered the migration and future calls to DataStore will
      * result in DataMigrations being attempted again. This method may be run multiple times when
      * any failure is encountered.
+     *
+     * This is useful for cleaning up files or data outside of DataStore and accessing any
+     * data from DataStore directly from inside this function will result in deadlock, since
+     * DataStore doesn't return data until all migrations complete.
      */
     public suspend fun cleanUp()
 }
\ No newline at end of file
diff --git a/datastore/datastore-rxjava2/api/current.txt b/datastore/datastore-rxjava2/api/current.txt
index 42e32ea..b52a1fc 100644
--- a/datastore/datastore-rxjava2/api/current.txt
+++ b/datastore/datastore-rxjava2/api/current.txt
@@ -1,20 +1,37 @@
 // Signature format: 4.0
 package androidx.datastore.rxjava2 {
 
+  public interface RxDataMigration<T> {
+    method public io.reactivex.Completable cleanUp();
+    method public io.reactivex.Single<T!> migrate(T?);
+    method public io.reactivex.Single<java.lang.Boolean!> shouldMigrate(T?);
+  }
+
   public final class RxDataStore {
     method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<T> data(androidx.datastore.core.DataStore<T>);
     method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Single<T> updateDataAsync(androidx.datastore.core.DataStore<T>, io.reactivex.functions.Function<T,io.reactivex.Single<T>> transform);
   }
 
   public final class RxDataStoreBuilder<T> {
-    ctor public RxDataStoreBuilder();
+    ctor public RxDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile, androidx.datastore.core.Serializer<T> serializer);
+    ctor public RxDataStoreBuilder(android.content.Context context, String fileName, androidx.datastore.core.Serializer<T> serializer);
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addDataMigration(androidx.datastore.core.DataMigration<T> dataMigration);
+    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addRxDataMigration(androidx.datastore.rxjava2.RxDataMigration<T> rxDataMigration);
     method public androidx.datastore.core.DataStore<T> build();
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T> corruptionHandler);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setFileName(android.content.Context context, String fileName);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setFileProducer(java.util.concurrent.Callable<java.io.File> produceFile);
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setIoScheduler(io.reactivex.Scheduler ioScheduler);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setSerializer(androidx.datastore.core.Serializer<T> serializer);
+  }
+
+  public interface RxSharedPreferencesMigration<T> {
+    method public io.reactivex.Single<T> migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T? currentData);
+    method public default io.reactivex.Single<java.lang.Boolean> shouldMigrate(T? currentData);
+  }
+
+  public final class RxSharedPreferencesMigrationBuilder<T> {
+    ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
+    method public androidx.datastore.core.DataMigration<T> build();
+    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
+    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
 }
diff --git a/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt b/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt
index 42e32ea..b52a1fc 100644
--- a/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt
+++ b/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt
@@ -1,20 +1,37 @@
 // Signature format: 4.0
 package androidx.datastore.rxjava2 {
 
+  public interface RxDataMigration<T> {
+    method public io.reactivex.Completable cleanUp();
+    method public io.reactivex.Single<T!> migrate(T?);
+    method public io.reactivex.Single<java.lang.Boolean!> shouldMigrate(T?);
+  }
+
   public final class RxDataStore {
     method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<T> data(androidx.datastore.core.DataStore<T>);
     method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Single<T> updateDataAsync(androidx.datastore.core.DataStore<T>, io.reactivex.functions.Function<T,io.reactivex.Single<T>> transform);
   }
 
   public final class RxDataStoreBuilder<T> {
-    ctor public RxDataStoreBuilder();
+    ctor public RxDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile, androidx.datastore.core.Serializer<T> serializer);
+    ctor public RxDataStoreBuilder(android.content.Context context, String fileName, androidx.datastore.core.Serializer<T> serializer);
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addDataMigration(androidx.datastore.core.DataMigration<T> dataMigration);
+    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addRxDataMigration(androidx.datastore.rxjava2.RxDataMigration<T> rxDataMigration);
     method public androidx.datastore.core.DataStore<T> build();
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T> corruptionHandler);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setFileName(android.content.Context context, String fileName);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setFileProducer(java.util.concurrent.Callable<java.io.File> produceFile);
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setIoScheduler(io.reactivex.Scheduler ioScheduler);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setSerializer(androidx.datastore.core.Serializer<T> serializer);
+  }
+
+  public interface RxSharedPreferencesMigration<T> {
+    method public io.reactivex.Single<T> migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T? currentData);
+    method public default io.reactivex.Single<java.lang.Boolean> shouldMigrate(T? currentData);
+  }
+
+  public final class RxSharedPreferencesMigrationBuilder<T> {
+    ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
+    method public androidx.datastore.core.DataMigration<T> build();
+    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
+    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
 }
diff --git a/datastore/datastore-rxjava2/api/restricted_current.txt b/datastore/datastore-rxjava2/api/restricted_current.txt
index 42e32ea..b52a1fc 100644
--- a/datastore/datastore-rxjava2/api/restricted_current.txt
+++ b/datastore/datastore-rxjava2/api/restricted_current.txt
@@ -1,20 +1,37 @@
 // Signature format: 4.0
 package androidx.datastore.rxjava2 {
 
+  public interface RxDataMigration<T> {
+    method public io.reactivex.Completable cleanUp();
+    method public io.reactivex.Single<T!> migrate(T?);
+    method public io.reactivex.Single<java.lang.Boolean!> shouldMigrate(T?);
+  }
+
   public final class RxDataStore {
     method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<T> data(androidx.datastore.core.DataStore<T>);
     method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Single<T> updateDataAsync(androidx.datastore.core.DataStore<T>, io.reactivex.functions.Function<T,io.reactivex.Single<T>> transform);
   }
 
   public final class RxDataStoreBuilder<T> {
-    ctor public RxDataStoreBuilder();
+    ctor public RxDataStoreBuilder(java.util.concurrent.Callable<java.io.File> produceFile, androidx.datastore.core.Serializer<T> serializer);
+    ctor public RxDataStoreBuilder(android.content.Context context, String fileName, androidx.datastore.core.Serializer<T> serializer);
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addDataMigration(androidx.datastore.core.DataMigration<T> dataMigration);
+    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> addRxDataMigration(androidx.datastore.rxjava2.RxDataMigration<T> rxDataMigration);
     method public androidx.datastore.core.DataStore<T> build();
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setCorruptionHandler(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler<T> corruptionHandler);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setFileName(android.content.Context context, String fileName);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setFileProducer(java.util.concurrent.Callable<java.io.File> produceFile);
     method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setIoScheduler(io.reactivex.Scheduler ioScheduler);
-    method public androidx.datastore.rxjava2.RxDataStoreBuilder<T> setSerializer(androidx.datastore.core.Serializer<T> serializer);
+  }
+
+  public interface RxSharedPreferencesMigration<T> {
+    method public io.reactivex.Single<T> migrate(androidx.datastore.migrations.SharedPreferencesView sharedPreferencesView, T? currentData);
+    method public default io.reactivex.Single<java.lang.Boolean> shouldMigrate(T? currentData);
+  }
+
+  public final class RxSharedPreferencesMigrationBuilder<T> {
+    ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
+    method public androidx.datastore.core.DataMigration<T> build();
+    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
+    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
 }
diff --git a/datastore/datastore-rxjava2/build.gradle b/datastore/datastore-rxjava2/build.gradle
index 6cf3913..83003c2 100644
--- a/datastore/datastore-rxjava2/build.gradle
+++ b/datastore/datastore-rxjava2/build.gradle
@@ -27,19 +27,12 @@
 }
 
 android {
-    buildTypes{
-        debug {
-            multiDexEnabled = true
-        }
-    }
-
     sourceSets {
         test.java.srcDirs += 'src/test-common/java'
         androidTest.java.srcDirs += 'src/test-common/java'
     }
 }
 
-
 dependencies {
     api(KOTLIN_STDLIB)
     api(KOTLIN_COROUTINES_CORE)
@@ -55,11 +48,7 @@
     testImplementation(TRUTH)
     testImplementation(project(":internal-testutils-truth"))
 
-    androidTestImplementation(project(":datastore:datastore-core"))
-    androidTestImplementation(project(":datastore:datastore"))
     androidTestImplementation(JUNIT)
-    androidTestImplementation(KOTLIN_COROUTINES_TEST)
-    androidTestImplementation(TRUTH)
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
     androidTestImplementation(ANDROIDX_TEST_CORE)
@@ -73,10 +62,3 @@
     description = "Android DataStore Core - contains wrappers for using DataStore using RxJava2"
     legacyDisableKotlinStrictApiMode = true
 }
-
-// Allow usage of Kotlin's @OptIn.
-tasks.withType(KotlinCompile).configureEach {
-    kotlinOptions {
-        freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
-    }
-}
\ No newline at end of file
diff --git a/car/app/app/src/androidTest/AndroidManifest.xml b/datastore/datastore-rxjava2/src/androidTest/AndroidManifest.xml
similarity index 94%
rename from car/app/app/src/androidTest/AndroidManifest.xml
rename to datastore/datastore-rxjava2/src/androidTest/AndroidManifest.xml
index 3bc2684..bf9d5c2 100644
--- a/car/app/app/src/androidTest/AndroidManifest.xml
+++ b/datastore/datastore-rxjava2/src/androidTest/AndroidManifest.xml
@@ -15,5 +15,6 @@
   limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="androidx.car.app">
+    package="androidx.datastore.rxjava2">
+
 </manifest>
diff --git a/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/AssertThrows.kt b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/AssertThrows.kt
index f734adc..cc1f3493 100644
--- a/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/AssertThrows.kt
+++ b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/AssertThrows.kt
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.datastore.rxjava2
 
 @Suppress("UNCHECKED_CAST")
@@ -35,4 +34,4 @@
             expectedType.simpleName
         )
     )
-}
\ No newline at end of file
+}
diff --git a/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxDataStoreBuilderTest.java b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxDataStoreBuilderTest.java
index 6eceb67..2d3a0ae 100644
--- a/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxDataStoreBuilderTest.java
+++ b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxDataStoreBuilderTest.java
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.datastore.rxjava2;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
 
+import androidx.annotation.NonNull;
 import androidx.datastore.core.DataStore;
 import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler;
 import androidx.test.core.app.ApplicationProvider;
@@ -32,6 +32,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 
+import io.reactivex.Completable;
 import io.reactivex.Scheduler;
 import io.reactivex.Single;
 import io.reactivex.schedulers.Schedulers;
@@ -47,60 +48,68 @@
     @Test
     public void testConstructWithProduceFile() throws Exception {
         File file = tempFolder.newFile();
-
         DataStore<Byte> dataStore =
-                new RxDataStoreBuilder<Byte>()
-                        .setFileProducer(() -> file)
-                        .setSerializer(new TestingSerializer())
+                new RxDataStoreBuilder<Byte>(() -> file, new TestingSerializer())
                         .build();
-
         Single<Byte> incrementByte = RxDataStore.updateDataAsync(dataStore,
                 RxDataStoreBuilderTest::incrementByte);
         assertThat(incrementByte.blockingGet()).isEqualTo(1);
-
         // Construct it again and confirm that the data is still there:
         dataStore =
-                new RxDataStoreBuilder<Byte>()
-                        .setFileProducer(() -> file)
-                        .setSerializer(new TestingSerializer())
+                new RxDataStoreBuilder<Byte>(() -> file, new TestingSerializer())
                         .build();
-
         assertThat(RxDataStore.data(dataStore).blockingFirst()).isEqualTo(1);
     }
 
     @Test
     public void testConstructWithContextAndName() throws Exception {
-
         Context context = ApplicationProvider.getApplicationContext();
         String name = "my_data_store";
-
         DataStore<Byte> dataStore =
-                new RxDataStoreBuilder<Byte>()
-                        .setFileName(context, name)
-                        .setSerializer(new TestingSerializer())
+                new RxDataStoreBuilder<Byte>(context, name, new TestingSerializer())
                         .build();
-
         Single<Byte> set1 = RxDataStore.updateDataAsync(dataStore, input -> Single.just((byte) 1));
         assertThat(set1.blockingGet()).isEqualTo(1);
-
         // Construct it again and confirm that the data is still there:
         dataStore =
-                new RxDataStoreBuilder<Byte>()
-                        .setFileName(context, name)
-                        .setSerializer(new TestingSerializer())
+                new RxDataStoreBuilder<Byte>(context, name, new TestingSerializer())
                         .build();
-
         assertThat(RxDataStore.data(dataStore).blockingFirst()).isEqualTo(1);
-
         // Construct it again with the expected file path and confirm that the data is there:
         dataStore =
-                new RxDataStoreBuilder<Byte>()
-                        .setFileProducer(
-                                () -> new File(
-                                        context.getFilesDir().getPath()
-                                                + "/datastore/" + name))
-                        .setSerializer(new TestingSerializer())
+                new RxDataStoreBuilder<Byte>(() -> new File(context.getFilesDir().getPath()
+                        + "/datastore/" + name), new TestingSerializer()
+                )
                         .build();
+        assertThat(RxDataStore.data(dataStore).blockingFirst()).isEqualTo(1);
+    }
+
+    @Test
+    public void testMigrationsAreInstalledAndRun() throws Exception {
+        RxDataMigration<Byte> plusOneMigration = new RxDataMigration<Byte>() {
+            @NonNull
+            @Override
+            public Single<Boolean> shouldMigrate(@NonNull Byte currentData) {
+                return Single.just(true);
+            }
+
+            @NonNull
+            @Override
+            public Single<Byte> migrate(@NonNull Byte currentData) {
+                return incrementByte(currentData);
+            }
+
+            @NonNull
+            @Override
+            public Completable cleanUp() {
+                return Completable.complete();
+            }
+        };
+
+        DataStore<Byte> dataStore = new RxDataStoreBuilder<Byte>(
+                () -> tempFolder.newFile(), new TestingSerializer())
+                .addRxDataMigration(plusOneMigration)
+                .build();
 
         assertThat(RxDataStore.data(dataStore).blockingFirst()).isEqualTo(1);
     }
@@ -112,82 +121,41 @@
                     @Override
                     public Thread newThread(Runnable r) {
                         return new Thread(r, "TestingThread");
-
                     }
                 }));
 
 
-        DataStore<Byte> dataStore = new RxDataStoreBuilder<Byte>()
-                .setFileProducer(() -> tempFolder.newFile())
-                .setSerializer(new TestingSerializer())
+        DataStore<Byte> dataStore = new RxDataStoreBuilder<Byte>(() -> tempFolder.newFile(),
+                new TestingSerializer())
                 .setIoScheduler(singleThreadedScheduler)
                 .build();
-
         Single<Byte> update = RxDataStore.updateDataAsync(dataStore, input -> {
             Thread currentThread = Thread.currentThread();
             assertThat(currentThread.getName()).isEqualTo("TestingThread");
-
             return Single.just(input);
         });
-
         assertThat(update.blockingGet()).isEqualTo((byte) 0);
-
         Single<Byte> subsequentUpdate = RxDataStore.updateDataAsync(dataStore, input -> {
             Thread currentThread = Thread.currentThread();
             assertThat(currentThread.getName()).isEqualTo("TestingThread");
-
             return Single.just(input);
         });
-
         assertThat(subsequentUpdate.blockingGet()).isEqualTo((byte) 0);
-
     }
 
     @Test
     public void testCorruptionHandlerIsUser() {
         TestingSerializer testingSerializer = new TestingSerializer();
         testingSerializer.setFailReadWithCorruptionException(true);
-
         ReplaceFileCorruptionHandler<Byte> replaceFileCorruptionHandler =
-                 new ReplaceFileCorruptionHandler<Byte>(exception -> (byte) 99);
+                new ReplaceFileCorruptionHandler<Byte>(exception -> (byte) 99);
 
 
-        DataStore<Byte> dataStore = new RxDataStoreBuilder<Byte>()
-                .setFileProducer(() -> tempFolder.newFile())
-                .setSerializer(testingSerializer)
+        DataStore<Byte> dataStore = new RxDataStoreBuilder<Byte>(
+                () -> tempFolder.newFile(),
+                testingSerializer)
                 .setCorruptionHandler(replaceFileCorruptionHandler)
                 .build();
-
         assertThat(RxDataStore.data(dataStore).blockingFirst()).isEqualTo(99);
     }
-
-    @Test
-    public void testSerializerIsRequired() {
-        AssertThrowsKt.assertThrows(IllegalStateException.class,
-                () -> new RxDataStoreBuilder<Byte>().setFileProducer(
-                        () -> tempFolder.newFile()).build());
-    }
-
-    @Test
-    public void testOneOfProduceFileOrContextAndNameIsRequired() {
-        // Set file producer then context and name
-        AssertThrowsKt.assertThrows(IllegalStateException.class,
-                () -> new RxDataStoreBuilder<Byte>()
-                        .setSerializer(new TestingSerializer())
-                        .setFileProducer(() -> tempFolder.newFile())
-                        .setFileName(ApplicationProvider.getApplicationContext(), "name"));
-
-        // Set context and name then file producer
-        AssertThrowsKt.assertThrows(IllegalStateException.class,
-                () -> new RxDataStoreBuilder<Byte>()
-                        .setSerializer(new TestingSerializer())
-                        .setFileName(ApplicationProvider.getApplicationContext(), "name")
-                        .setFileProducer(() -> tempFolder.newFile()));
-
-        // Set neither and try to build.
-        AssertThrowsKt.assertThrows(IllegalStateException.class,
-                () -> new RxDataStoreBuilder<Byte>()
-                        .setSerializer(new TestingSerializer())
-                        .build());
-    }
 }
diff --git a/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxSharedPreferencesMigrationTest.java b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxSharedPreferencesMigrationTest.java
new file mode 100644
index 0000000..c8e0b79
--- /dev/null
+++ b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxSharedPreferencesMigrationTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.datastore.rxjava2;
+
+import static androidx.testutils.AssertionsKt.assertThrows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.datastore.core.DataMigration;
+import androidx.datastore.core.DataStore;
+import androidx.datastore.migrations.SharedPreferencesView;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+
+import io.reactivex.Single;
+
+public class RxSharedPreferencesMigrationTest {
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    private final String mSharedPrefsName = "shared_prefs_name";
+
+
+    private Context mContext;
+    private SharedPreferences mSharedPrefs;
+    private File mDatastoreFile;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        mSharedPrefs = mContext.getSharedPreferences(mSharedPrefsName, Context.MODE_PRIVATE);
+        mDatastoreFile = temporaryFolder.newFile("test_file.preferences_pb");
+
+        assertThat(mSharedPrefs.edit().clear().commit()).isTrue();
+    }
+
+    @Test
+    public void testShouldMigrateSkipsMigration() {
+        RxSharedPreferencesMigration<Byte> skippedMigration =
+                new RxSharedPreferencesMigration<Byte>() {
+                    @NotNull
+                    @Override
+                    public Single<Boolean> shouldMigrate(Byte currentData) {
+                        return Single.just(false);
+                    }
+
+                    @NotNull
+                    @Override
+                    public Single<Byte> migrate(
+                            @NotNull SharedPreferencesView sharedPreferencesView,
+                            Byte currentData) {
+                        return Single.error(
+                                new IllegalStateException("We shouldn't reach this point!"));
+                    }
+                };
+
+
+        DataMigration<Byte> spMigration =
+                getSpMigrationBuilder(skippedMigration).build();
+
+        DataStore<Byte> dataStoreWithMigrations = getDataStoreWithMigration(spMigration);
+
+        assertThat(RxDataStore.data(dataStoreWithMigrations).blockingFirst()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSharedPrefsViewContainsSpecifiedKeys() {
+        String includedKey = "key1";
+        int includedVal = 99;
+        String notMigratedKey = "key2";
+
+        assertThat(mSharedPrefs.edit().putInt(includedKey, includedVal).putInt(notMigratedKey,
+                123).commit()).isTrue();
+
+        DataMigration<Byte> dataMigration =
+                getSpMigrationBuilder(
+                        new DefaultMigration() {
+                            @NotNull
+                            @Override
+                            public Single<Byte> migrate(
+                                    @NotNull SharedPreferencesView sharedPreferencesView,
+                                    Byte currentData) {
+                                assertThat(sharedPreferencesView.contains(includedKey)).isTrue();
+                                assertThat(sharedPreferencesView.getAll().size()).isEqualTo(1);
+                                assertThrows(IllegalStateException.class,
+                                        () -> sharedPreferencesView.getInt(notMigratedKey, -1));
+
+                                return Single.just((byte) 50);
+                            }
+                        }
+                ).setKeysToMigrate(includedKey).build();
+
+        DataStore<Byte> byteStore = getDataStoreWithMigration(dataMigration);
+
+        assertThat(RxDataStore.data(byteStore).blockingFirst()).isEqualTo(50);
+
+        assertThat(mSharedPrefs.contains(includedKey)).isFalse();
+        assertThat(mSharedPrefs.contains(notMigratedKey)).isTrue();
+    }
+
+
+    @Test
+    public void testSharedPrefsViewWithAllKeysSpecified() {
+        String includedKey = "key1";
+        String includedKey2 = "key2";
+        int value = 99;
+
+        assertThat(mSharedPrefs.edit().putInt(includedKey, value).putInt(includedKey2,
+                value).commit()).isTrue();
+
+        DataMigration<Byte> dataMigration =
+                getSpMigrationBuilder(
+                        new DefaultMigration() {
+                            @NotNull
+                            @Override
+                            public Single<Byte> migrate(
+                                    @NotNull SharedPreferencesView sharedPreferencesView,
+                                    Byte currentData) {
+                                assertThat(sharedPreferencesView.contains(includedKey)).isTrue();
+                                assertThat(sharedPreferencesView.contains(includedKey2)).isTrue();
+                                assertThat(sharedPreferencesView.getAll().size()).isEqualTo(2);
+
+                                return Single.just((byte) 50);
+                            }
+                        }
+                ).build();
+
+        DataStore<Byte> byteStore = getDataStoreWithMigration(dataMigration);
+
+        assertThat(RxDataStore.data(byteStore).blockingFirst()).isEqualTo(50);
+
+        assertThat(mSharedPrefs.contains(includedKey)).isFalse();
+        assertThat(mSharedPrefs.contains(includedKey2)).isFalse();
+    }
+
+    @Test
+    public void testDeletesEmptySharedPreferences() {
+        String key = "key";
+        String value = "value";
+        assertThat(mSharedPrefs.edit().putString(key, value).commit()).isTrue();
+
+        DataMigration<Byte> dataMigration =
+                getSpMigrationBuilder(new DefaultMigration()).setDeleteEmptyPreferences(
+                        true).build();
+        DataStore<Byte> byteStore = getDataStoreWithMigration(dataMigration);
+        assertThat(RxDataStore.data(byteStore).blockingFirst()).isEqualTo(0);
+
+        // Check that the shared preferences files are deleted
+        File prefsDir = new File(mContext.getApplicationInfo().dataDir, "shared_prefs");
+        File prefsFile = new File(prefsDir, mSharedPrefsName + ".xml");
+        File backupPrefsFile = new File(prefsFile.getPath() + ".bak");
+        assertThat(prefsFile.exists()).isFalse();
+        assertThat(backupPrefsFile.exists()).isFalse();
+    }
+
+    private RxSharedPreferencesMigrationBuilder<Byte> getSpMigrationBuilder(
+            RxSharedPreferencesMigration<Byte> rxSharedPreferencesMigration) {
+        return new RxSharedPreferencesMigrationBuilder<Byte>(mContext, mSharedPrefsName,
+                rxSharedPreferencesMigration);
+    }
+
+    private DataStore<Byte> getDataStoreWithMigration(DataMigration<Byte> dataMigration) {
+        return new RxDataStoreBuilder<Byte>(() -> mDatastoreFile, new TestingSerializer())
+                .addDataMigration(dataMigration).build();
+    }
+
+
+    private static class DefaultMigration implements RxSharedPreferencesMigration<Byte> {
+
+        @NotNull
+        @Override
+        public Single<Boolean> shouldMigrate(Byte currentData) {
+            return Single.just(true);
+        }
+
+        @NotNull
+        @Override
+        public Single<Byte> migrate(@NotNull SharedPreferencesView sharedPreferencesView,
+                Byte currentData) {
+            return Single.just(currentData);
+        }
+    }
+}
diff --git a/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxDataMigration.java b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxDataMigration.java
new file mode 100644
index 0000000..79df6a1
--- /dev/null
+++ b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxDataMigration.java
@@ -0,0 +1,79 @@
+/*
+ * 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.datastore.rxjava2;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import io.reactivex.Completable;
+import io.reactivex.Single;
+
+/**
+ * Interface for migrations to DataStore. Methods on this migration ([shouldMigrate], [migrate]
+ * and [cleanUp]) may be called multiple times, so their implementations must be idempotent.
+ * These methods may be called multiple times if DataStore encounters issues when writing the
+ * newly migrated data to disk or if any migration installed in the same DataStore throws an
+ * Exception.
+ *
+ * If you're migrating from SharedPreferences see [SharedPreferencesMigration].
+ *
+ * @param <T> the exception type
+ */
+public interface RxDataMigration<T> {
+
+    /**
+     * Return whether this migration needs to be performed. If this returns false, no migration or
+     * cleanup will occur. Apps should do the cheapest possible check to determine if this migration
+     * should run, since this will be called every time the DataStore is initialized. This method
+     * may be run multiple times when any failure is encountered.
+     *
+     * Note that this will always be called before each call to [migrate].
+     *
+     * @param currentData the current data (which might already populated from previous runs of this
+     *                    or other migrations). Only Nullable if the type used with DataStore is
+     *                    Nullable.
+     */
+    @NonNull
+    Single<Boolean> shouldMigrate(@Nullable T currentData);
+
+    /**
+     * Perform the migration. Implementations should be idempotent since this may be called
+     * multiple times. If migrate fails, DataStore will not commit any data to disk, cleanUp will
+     * not be called, and the exception will be propagated back to the DataStore call that
+     * triggered the migration. Future calls to DataStore will result in DataMigrations being
+     * attempted again. This method may be run multiple times when any failure is encountered.
+     *
+     * Note that this will always be called before a call to [cleanUp].
+     *
+     * @param currentData the current data (it might be populated from other migrations or from
+     *                    manual changes before this migration was added to the app). Only
+     *                    Nullable if the type used with DataStore is Nullable.
+     * @return The migrated data.
+     */
+    @NonNull
+    Single<T> migrate(@Nullable T currentData);
+
+    /**
+     * Clean up any old state/data that was migrated into the DataStore. This will not be called
+     * if the migration fails. If cleanUp throws an exception, the exception will be propagated
+     * back to the DataStore call that triggered the migration and future calls to DataStore will
+     * result in DataMigrations being attempted again. This method may be run multiple times when
+     * any failure is encountered.
+     */
+    @NonNull
+    Completable cleanUp();
+}
diff --git a/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxDataStoreBuilder.kt b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxDataStoreBuilder.kt
index 1685286..070d0a1c 100644
--- a/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxDataStoreBuilder.kt
+++ b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxDataStoreBuilder.kt
@@ -28,6 +28,7 @@
 import io.reactivex.schedulers.Schedulers
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.rx2.asCoroutineDispatcher
+import kotlinx.coroutines.rx2.await
 import java.io.File
 import java.util.concurrent.Callable
 
@@ -35,15 +36,49 @@
  * RxSharedPreferencesMigrationBuilder class for a DataStore that works on a single process.
  */
 @SuppressLint("TopLevelBuilder")
-public class RxDataStoreBuilder<T>() {
+public class RxDataStoreBuilder<T> {
 
-    // Either produceFile or context & name must be set, but not both.
+    /**
+     * Create a RxDataStoreBuilder with the callable which returns the File that DataStore acts on.
+     * The user is responsible for ensuring that there is never more than one DataStore acting on
+     * a file at a time.
+     *
+     * @param produceFile Function which returns the file that the new DataStore will act on. The
+     * function must return the same path every time. No two instances of DataStore should act on
+     * the same file at the same time.
+     * @param serializer the serializer for the type that this DataStore acts on.
+     */
+    public constructor(produceFile: Callable<File>, serializer: Serializer<T>) {
+        this.produceFile = produceFile
+        this.serializer = serializer
+    }
+
+    /**
+     * Create a RxDataStoreBuilder with the Context and name from which to derive the DataStore
+     * file. The file is generated by See [Context.createDataStore] for more info. The user is
+     * responsible for ensuring that there is never more than one DataStore acting on a file at a
+     * time.
+     *
+     * @param context the context from which we retrieve files directory.
+     * @param fileName the filename relative to Context.filesDir that DataStore acts on. The File is
+     * obtained by calling File(context.filesDir, fileName). No two instances of DataStore should
+     * act on the same file at the same time.
+     * @param serializer the serializer for the type that this DataStore acts on.
+     */
+    public constructor(context: Context, fileName: String, serializer: Serializer<T>) {
+        this.context = context
+        this.name = fileName
+        this.serializer = serializer
+    }
+
+    // Either produceFile or context & name must be set, but not both. This is enforced by the
+    // two constructors.
     private var produceFile: Callable<File>? = null
 
     private var context: Context? = null
     private var name: String? = null
 
-    // Required
+    // Required. This is enforced by the constructors.
     private var serializer: Serializer<T>? = null
 
     // Optional
@@ -52,61 +87,6 @@
     private val dataMigrations: MutableList<DataMigration<T>> = mutableListOf()
 
     /**
-     * Set the callable which returns the File that DataStore acts on. The user is responsible for
-     * ensuring that there is never more than one DataStore acting on a file at a time.
-     *
-     * It is required to call either this method or [setFileName] before calling [build].
-     *
-     *
-     * @param produceFile Function which returns the file that the new DataStore will act on. The
-     * function must return the same path every time. No two instances of DataStore should act on
-     * the same file at the same time.
-     * @throws IllegalStateException if context and name are already set
-     * @return this
-     */
-    @Suppress("MissingGetterMatchingBuilder")
-    public fun setFileProducer(produceFile: Callable<File>): RxDataStoreBuilder<T> = apply {
-        check(context == null) { "Only call setFileProducer or setContextAndName" }
-        check(name == null) { "Only call setFileProducer or setContextAndName" }
-        this.produceFile = produceFile
-    }
-
-    /**
-     * Set the Context and name from which to derive the DataStore file. The file is generated by
-     * See [Context.createDataStore] for more info. The user is responsible for ensuring that
-     * there is never more than one DataStore acting on a file at a time.
-     *
-     * It is required to call either this method or [setFileProducer] before calling [build].
-     *
-     * @param context the context from which we retrieve files directory.
-     * @param fileName the filename relative to Context.filesDir that DataStore acts on. The File is
-     * obtained by calling File(context.filesDir, fileName). No two instances of DataStore should
-     * act on the same file at the same time.
-     * @throws IllegalStateException if produceFile is already set
-     * @return this
-     */
-    @Suppress("MissingGetterMatchingBuilder")
-    public fun setFileName(context: Context, fileName: String): RxDataStoreBuilder<T> =
-        apply {
-            check(produceFile == null) { "Only call setFileProducer or setContextAndName" }
-            this.context = context
-            this.name = fileName
-        }
-
-    /**
-     * Set the serializer that this DataStore acts on.
-     *
-     * This parameter is required.
-     *
-     * @param serializer the serializer for the type that this DataStore acts on.
-     * @return this
-     */
-    @Suppress("MissingGetterMatchingBuilder")
-    public fun setSerializer(serializer: Serializer<T>): RxDataStoreBuilder<T> = apply {
-        this.serializer = serializer
-    }
-
-    /**
      * Set the Scheduler on which to perform IO and transform operations. This is converted into
      * a CoroutineDispatcher before being added to DataStore.
      *
@@ -132,6 +112,18 @@
         RxDataStoreBuilder<T> = apply { this.corruptionHandler = corruptionHandler }
 
     /**
+     * Add an RxDataMigration to the DataStore. Migrations are run in the order they are added.
+     *
+     * @param rxDataMigration the migration to add.
+     * @return this
+     */
+    @Suppress("MissingGetterMatchingBuilder")
+    public fun addRxDataMigration(rxDataMigration: RxDataMigration<T>): RxDataStoreBuilder<T> =
+        apply {
+            this.dataMigrations.add(DataMigrationFromRxDataMigration(rxDataMigration))
+        }
+
+    /**
      * Add a DataMigration to the Datastore. Migrations are run in the order they are added.
      *
      * @param dataMigration the migration to add
@@ -145,15 +137,9 @@
     /**
      * Build the DataStore.
      *
-     * @throws IllegalStateException if serializer is not set or if neither produceFile not
-     * context and name are set.
      * @return the DataStore with the provided parameters
      */
     public fun build(): DataStore<T> {
-        check(serializer != null) {
-            "Serializer must be set."
-        }
-
         val scope = CoroutineScope(ioScheduler.asCoroutineDispatcher())
 
         return if (produceFile != null) {
@@ -175,7 +161,24 @@
                 migrations = dataMigrations
             )
         } else {
-            throw IllegalStateException("Either produceFile or context and name must be set.")
+            error(
+                "Either produceFile or context and name must be set. This should never happen."
+            )
         }
     }
 }
+
+internal class DataMigrationFromRxDataMigration<T>(private val migration: RxDataMigration<T>) :
+    DataMigration<T> {
+    override suspend fun shouldMigrate(currentData: T): Boolean {
+        return migration.shouldMigrate(currentData).await()
+    }
+
+    override suspend fun migrate(currentData: T): T {
+        return migration.migrate(currentData).await()
+    }
+
+    override suspend fun cleanUp() {
+        migration.cleanUp().await()
+    }
+}
diff --git a/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxSharedPreferencesMigration.kt b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxSharedPreferencesMigration.kt
new file mode 100644
index 0000000..e84e377
--- /dev/null
+++ b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxSharedPreferencesMigration.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.datastore.rxjava2
+
+import android.annotation.SuppressLint
+import android.content.Context
+import androidx.datastore.core.DataMigration
+import androidx.datastore.migrations.SharedPreferencesMigration
+import androidx.datastore.migrations.SharedPreferencesView
+import io.reactivex.Single
+import kotlinx.coroutines.rx2.await
+
+/**
+ * Client implemented migration interface.
+ **/
+public interface RxSharedPreferencesMigration<T> {
+    /**
+     * Whether or not the migration should be run. This can be used to skip a read from the
+     * SharedPreferences.
+     *
+     * @param currentData the most recently persisted data
+     * @return a Single indicating whether or not the migration should be run.
+     */
+    public fun shouldMigrate(currentData: T): Single<Boolean> {
+        return Single.just(true)
+    }
+
+    /**
+     * Maps SharedPreferences into T. Implementations should be idempotent
+     * since this may be called multiple times. See [DataMigration.migrate] for more
+     * information. The method accepts a SharedPreferencesView which is the view of the
+     * SharedPreferences to migrate from (limited to [keysToMigrate] and a T which represent
+     * the current data. The function must return the migrated data.
+     *
+     * @param sharedPreferencesView the current state of the SharedPreferences
+     * @param currentData the most recently persisted data
+     * @return a Single of the updated data
+     */
+    public fun migrate(sharedPreferencesView: SharedPreferencesView, currentData: T): Single<T>
+}
+
+/**
+ * RxSharedPreferencesMigrationBuilder for the RxSharedPreferencesMigration.
+ */
+@SuppressLint("TopLevelBuilder")
+public class RxSharedPreferencesMigrationBuilder<T>
+/**
+ * Construct a RxSharedPreferencesMigrationBuilder.
+ *
+ * @param context the Context used for getting the SharedPreferences.
+ * @param sharedPreferencesName the name of the SharedPreference from which to migrate.
+ * @param rxSharedPreferencesMigration the user implemented migration for this SharedPreference.
+ */
+constructor(
+    private val context: Context,
+    private val sharedPreferencesName: String,
+    private val rxSharedPreferencesMigration: RxSharedPreferencesMigration<T>
+) {
+
+    /** Optional */
+    private var deleteEmptyPreference: Boolean = true
+    private var keysToMigrate: Set<String>? = null
+
+    /**
+     * Set the list of keys to migrate. The keys will be mapped to datastore.Preferences with
+     * their same values. If the key is already present in the new Preferences, the key
+     * will not be migrated again. If the key is not present in the SharedPreferences it
+     * will not be migrated.
+     *
+     * This method is optional and if keysToMigrate is not set, all keys will be migrated from the
+     * existing SharedPreferences.
+     *
+     * @param keys the keys to migrate
+     * @return this
+     */
+    @Suppress("MissingGetterMatchingBuilder")
+    public fun setKeysToMigrate(vararg keys: String):
+        RxSharedPreferencesMigrationBuilder<T> = apply {
+            keysToMigrate = setOf(*keys)
+        }
+
+    /**
+     * If enabled and the SharedPreferences are empty (i.e. no remaining
+     * keys) after this migration runs, the leftover SharedPreferences file is deleted. Note that
+     * this cleanup runs only if the migration itself runs, i.e., if the keys were never in
+     * SharedPreferences to begin with then the (potentially) empty SharedPreferences
+     * won't be cleaned up by this option. This functionality is best effort - if there
+     * is an issue deleting the SharedPreferences file it will be silently ignored.
+     *
+     * This method is optional and defaults to true.
+     *
+     * @param deleteEmptyPreferences whether or not to delete the empty shared preferences file
+     * @return this
+     */
+    @Suppress("MissingGetterMatchingBuilder")
+    public fun setDeleteEmptyPreferences(deleteEmptyPreferences: Boolean):
+        RxSharedPreferencesMigrationBuilder<T> = apply {
+            this.deleteEmptyPreference = deleteEmptyPreferences
+        }
+
+    public fun build(): DataMigration<T> {
+        return SharedPreferencesMigration(
+            context = context,
+            sharedPreferencesName = sharedPreferencesName,
+            migrate = { spView, curData ->
+                rxSharedPreferencesMigration.migrate(spView, curData).await()
+            },
+            keysToMigrate = keysToMigrate,
+            deleteEmptyPreferences = deleteEmptyPreference,
+            shouldRunMigration = { curData ->
+                rxSharedPreferencesMigration.shouldMigrate(curData).await()
+            }
+        )
+    }
+}
diff --git a/development/build_log_simplifier/build_log_simplifier.py b/development/build_log_simplifier/build_log_simplifier.py
index 34d6e9d..96cc18a 100755
--- a/development/build_log_simplifier/build_log_simplifier.py
+++ b/development/build_log_simplifier/build_log_simplifier.py
@@ -160,7 +160,6 @@
 
       "w: ATTENTION!",
       "This build uses unsafe internal compiler arguments:",
-      "-XXLanguage:-NewInference",
       "-XXLanguage:+InlineClasses",
       "This mode is not recommended for production use,",
       "as no stability/compatibility guarantees are given on",
diff --git a/development/build_log_simplifier/message-flakes.ignore b/development/build_log_simplifier/message-flakes.ignore
index 798e21b..eb09b8e 100644
--- a/development/build_log_simplifier/message-flakes.ignore
+++ b/development/build_log_simplifier/message-flakes.ignore
@@ -15,4 +15,4 @@
 Stream closed
 # > Task :compose:compiler:compiler-hosted:integration-tests:testDebugUnitTest
 # If a test fails, we don't want the build to fail, we want to pass the test output to the tests server and for the tests server to report the failure
-[0-9]+ tests .*failed.*
+[0-9]+ test.*failed.*
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 537f909..421dbe7 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -42,6 +42,7 @@
 Use '\-\-warning\-mode all' to show the individual deprecation warnings\.
 See https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/command_line_interface\.html\#sec:command_line_warnings
 BUILD SUCCESSFUL in .*
+[0-9]+ actionable tasks: [0-9]+ up\-to\-date
 [0-9]+ actionable tasks: [0-9]+ executed
 [0-9]+ actionable tasks: [0-9]+ executed, [0-9]+ from cache, [0-9]+ up\-to\-date
 [0-9]+ actionable task: [0-9]+ executed
@@ -407,7 +408,6 @@
 WARNING: Use \-\-illegal\-access=warn to enable warnings of further illegal reflective access operations
 WARNING: All illegal access operations will be denied in a future release
 # > Task :compose:compiler:compiler-hosted:integration-tests:testDebugUnitTest
-[0-9]+ tests completed, [0-9]+ failed, [0-9]+ skipped
 WARNING: Illegal reflective access by org\.robolectric\.util\.ReflectionHelpers \(file:\$CHECKOUT/prebuilts/androidx/external/org/robolectric/shadowapi/[0-9]+\.[0-9]+\-alpha\-[0-9]+/shadowapi\-[0-9]+\.[0-9]+\-alpha\-[0-9]+\.jar\) to field java\.lang\.reflect\.Field\.modifiers
 WARNING: Please consider reporting this to the maintainers of org\.robolectric\.util\.ReflectionHelpers
 # > Task :compose:compiler:compiler-hosted:integration-tests:test
@@ -445,9 +445,6 @@
 WARNING: Please consider reporting this to the maintainers of androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion\$isFromCompiledClass\$[0-9]+
 # > Task :wear:wear-watchface-complications-rendering:compileDebugUnitTestJavaWithJavac
 Note\: \$SUPPORT\/wear\/wear\-watchface\-complications\-rendering\/src\/test\/java\/androidx\/wear\/watchface\/complications\/rendering\/RoundedDrawableTest\.java uses or overrides a deprecated API\.
-# > Task :wear:wear-watchface:testDebugUnitTest
-System\.logW\: A resource was acquired at attached stack trace but never released\. See java\.io\.Closeable for information on avoiding resource leaks\.java\.lang\.Throwable\: Explicit termination method \'dispose\' not called
-System\.logW\: A resource was acquired at attached stack trace but never released\. See java\.io\.Closeable for information on avoiding resource leaks\.java\.lang\.Throwable\: Explicit termination method \'release\' not called
 # > Task :benchmark:benchmark-perfetto:mergeDebugAndroidTestJavaResource
 More than one file was found with OS independent path '.*'\. This version of the Android Gradle Plugin chooses the file from the app or dynamic\-feature module, but this can cause unexpected behavior or errors at runtime\. Future versions of the Android Gradle Plugin will throw an error in this case\.
 # > Task :docs-runner:dokkaJavaTipOfTreeDocs
@@ -466,7 +463,6 @@
 # > Task :annotation:annotation-experimental-lint:compileKotlin
 w\: ATTENTION\!
 This build uses unsafe internal compiler arguments\:
-\-XXLanguage\:\-NewInference
 This mode is not recommended for production use\,
 as no stability\/compatibility guarantees are given on
 compiler or generated code\. Use it at your own risk\!
@@ -609,7 +605,6 @@
 w: \$SUPPORT/navigation/navigation\-dynamic\-features\-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment\.kt: \([0-9]+, [0-9]+\): 'startIntentSenderForResult\(IntentSender!, Int, Intent\?, Int, Int, Int, Bundle\?\): Unit' is deprecated\. Deprecated in Java
 w: \$SUPPORT/navigation/navigation\-dynamic\-features\-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment\.kt: \([0-9]+, [0-9]+\): 'onActivityResult\(Int, Int, Intent\?\): Unit' is deprecated\. Deprecated in Java
 # > Task :inspection:inspection-gradle-plugin:test
-[0-9]+ test completed, [0-9]+ failed
 There were failing tests\. See the report at: .*.html
 # > Task :compose:ui:ui:processDebugUnitTestManifest
 \$OUT_DIR/androidx/compose/ui/ui/build/intermediates/tmp/manifest/test/debug/manifestMerger[0-9]+\.xml Warning:
@@ -651,6 +646,7 @@
 java\.lang\.Object androidx\.compose\.foundation\.gestures\.DragGestureDetectorKt\.awaitHorizontalTouchSlopOrCancellation\-s[0-9]+qLkbw\(androidx\.compose\.ui\.input\.pointer\.HandlePointerInputScope, long, kotlin\.jvm\.functions\.Function[0-9]+, kotlin\.coroutines\.Continuation\)
 java\.lang\.Object androidx\.compose\.foundation\.gestures\.MultitouchGestureDetectorKt\$detectMultitouchGestures\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 Attempt to define local of type int as it\$iv:java\.lang\.Object
+Type information in locals\-table is inconsistent\. Cannot constrain type: INT for value: v380\(index\$iv\$iv\) by constraint FLOAT\.
 java\.lang\.Object androidx\.compose\.foundation\.gestures\.TapGestureDetectorKt\.waitForUpOrCancellation\(androidx\.compose\.ui\.input\.pointer\.HandlePointerInputScope, kotlin\.coroutines\.Continuation\)
 # > Task :hilt:integration-tests:hilt-testapp-viewmodel:kaptDebugKotlin
 warning: The following options were not recognized by any processor: '\[dagger\.fastInit, dagger\.hilt\.android\.internal\.disableAndroidSuperclassValidation, kapt\.kotlin\.generated\]'
@@ -668,4 +664,4 @@
 w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: \'consumeSystemWindowInsets\(\)\: WindowInsetsCompat\' is deprecated\. Deprecated in Java
 w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: Unnecessary non\-null assertion \(\!\!\) on a non\-null receiver of type WindowInsetsCompat
 w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: \'consumeStableInsets\(\)\: WindowInsetsCompat\' is deprecated\. Deprecated in Java
-w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: \'consumeDisplayCutout\(\)\: WindowInsetsCompat\' is deprecated\. Deprecated in Java
\ No newline at end of file
+w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: \'consumeDisplayCutout\(\)\: WindowInsetsCompat\' is deprecated\. Deprecated in Java
diff --git a/development/simplify-build-failure/simplify-build-failure.sh b/development/simplify-build-failure/simplify-build-failure.sh
index ddd75ab..734a109 100755
--- a/development/simplify-build-failure/simplify-build-failure.sh
+++ b/development/simplify-build-failure/simplify-build-failure.sh
@@ -363,6 +363,9 @@
   else
     failed
   fi
+  echo Copying minimal set of files into $fewestFilesOutputPath
+  rm -rf "$fewestFilesOutputPath"
+  cp -rT "$filtererStep1Output" "$fewestFilesOutputPath"
 fi
 
 if [ "$subfilePath" == "" ]; then
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 440faef..e2d581f 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -191,6 +191,7 @@
     docs(project(":room:room-testing"))
     docs(project(":savedstate:savedstate"))
     docs(project(":savedstate:savedstate-ktx"))
+    docs(project(":security:security-app-authenticator"))
     docs(project(":security:security-biometric"))
     docs(project(":security:security-crypto"))
     docs(project(":security:security-crypto-ktx"))
diff --git a/docs/api_guidelines.md b/docs/api_guidelines.md
new file mode 100644
index 0000000..3781200
--- /dev/null
+++ b/docs/api_guidelines.md
@@ -0,0 +1,1522 @@
+# Library API guidelines
+
+[TOC]
+
+## Introduction {#introduction}
+
+s.android.com/api-guidelines,
+which covers standard and practices for designing platform APIs.
+
+All platform API design guidelines also apply to Jetpack libraries, with any
+additional guidelines or exceptions noted in this document. Jetpack libraries
+also follow
+[explicit API mode](https://kotlinlang.org/docs/reference/whatsnew14.html#explicit-api-mode-for-library-authors)
+for Kotlin libraries.
+
+## Modules {#module}
+
+### Packaging and naming {#module-naming}
+
+Java packages within Jetpack follow the format `androidx.<feature-name>`. All
+classes within a feature's artifact must reside within this package, and may
+further subdivide into `androidx.<feature-name>.<layer>` using standard Android
+layers (app, widget, etc.) or layers specific to the feature.
+
+Maven artifacts use the groupId format `androidx.<feature-name>` and artifactId
+format `<feature-name>` to match the Java package.
+
+Sub-features that can be separated into their own artifact should use the
+following formats:
+
+Java package: `androidx.<feature-name>.<sub-feature>.<layer>`
+
+Maven groupId: `androidx.<feature-name>`
+
+Maven artifactId: `<feature-name>-<sub-feature>`
+
+#### Common sub-feature names {#module-naming-subfeature}
+
+*   `-testing` for an artifact intended to be used while testing usages of your
+    library, e.g. `androidx.room:room-testing`
+*   `-core` for a low-level artifact that *may* contain public APIs but is
+    primarily intended for use by other libraries in the group
+*   `-ktx` for an Kotlin artifact that exposes idiomatic Kotlin APIs as an
+    extension to a Java-only library
+*   `-java8` for a Java 8 artifact that exposes idiomatic Java 8 APIs as an
+    extension to a Java 7 library
+*   `-<third-party>` for an artifact that integrates an optional third-party API
+    surface, e.g. `-proto` or `-rxjava2`. Note that a major version is included
+    in the sub-feature name for third-party API surfaces where the major version
+    indicates binary compatibility (only needed for post-1.x).
+
+Artifacts **should not** use `-impl` or `-base` to indicate that a library is an
+implementation detail shared within the group. Instead, use `-core`.
+
+#### Splitting existing modules
+
+Existing modules _should not_ be split into smaller modules; doing so creates
+the potential for class duplication issues when a developer depends on a new
+sub-module alongside the older top-level module. Consider the following
+scenario:
+
+*   `androidx.library:1.0.0`
+    *   contains classes `androidx.library.A` and `androidx.library.util.B`
+
+This module is split, moving `androidx.library.util.B` to a new module:
+
+*   `androidx.library:1.1.0`
+    *   contains class `androidx.library.A`
+    *   depends on `androidx.library.util:1.0.0`
+*   `androidx.library.util:1.0.0`
+    *   depends on `androidx.library.util.B`
+
+A developer writes an app that depends directly on `androidx.library.util:1.0.0`
+and transitively pulls in `androidx.library:1.0.0`. Their app will no longer
+compile due to class duplication of `androidx.library.util.B`.
+
+While it is possible for the developer to fix this by manually specifying a
+dependency on `androidx.library:1.1.0`, there is no easy way for the developer
+to discover this solution from the class duplication error raised at compile
+time.
+
+#### Same-version (atomic) groups
+
+Library groups are encouraged to opt-in to a same-version policy whereby all
+libraries in the group use the same version and express exact-match dependencies
+on libraries within the group. Such groups must increment the version of every
+library at the same time and release all libraries at the same time.
+
+Atomic groups are specified in
+[`LibraryGroups.kt`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt):
+
+```kotlin
+// Non-atomic library group
+val APPCOMPAT = LibraryGroup("androidx.appcompat", null)
+// Atomic library group
+val APPSEARCH = LibraryGroup("androidx.appsearch", LibraryVersions.APPSEARCH)
+```
+
+Libraries within an atomic group should not specify a version in their
+`build.gradle`:
+
+```groovy
+androidx {
+    name = 'AppSearch'
+    publish = Publish.SNAPSHOT_AND_RELEASE
+    mavenGroup = LibraryGroups.APPSEARCH
+    inceptionYear = '2019'
+    description = 'Provides local and centralized app indexing'
+}
+```
+
+There is one exception to this policy. Newly-added libraries within an atomic
+group may stay within the `1.0.0-alphaXX` before conforming to the same-version
+policy. When the library would like to move to `beta`, it must match the version
+used by the atomic group (which must be `beta` at the time).
+
+The benefits of using an atomic group are:
+
+-   Easier for developers to understand dependency versioning
+-   `@RestrictTo(LIBRARY_GROUP)` APIs are treated as private APIs and not
+    tracked for binary compatibility
+-   `@RequiresOptIn` APIs defined within the group may be used without any
+    restrictions between libraries in the group
+
+Potential drawbacks include:
+
+-   All libraries within the group must be versioned identically at head
+-   All libraries within the group must release at the same time
+
+
+### Choosing a `minSdkVersion` {#module-minsdkversion}
+
+The recommended minimum SDK version for new Jetpack libraries is currently
+**17** (Android 4.2, Jelly Bean). This SDK was chosen to represent 99% of active
+devices based on Play Store check-ins (see Android Studio
+[distribution metadata](https://dl.google.com/android/studio/metadata/distributions.json)
+for current statistics). This maximizes potential users for external developers
+while minimizing the amount of overhead necessary to support legacy versions.
+
+However, if no explicit minimum SDK version is specified for a library, the
+default is 14.
+
+Note that a library **must not** depend on another library with a higher
+`minSdkVersion` that its own, so it may be necessary for a new library to match
+its dependent libraries' `minSdkVersion`.
+
+Individual modules may choose a higher minimum SDK version for business or
+technical reasons. This is common for device-specific modules such as Auto or
+Wear.
+
+Individual classes or methods may be annotated with the
+[@RequiresApi](https://developer.android.com/reference/android/annotation/RequiresApi.html)
+annotation to indicate divergence from the overall module's minimum SDK version.
+Note that this pattern is _not recommended_ because it leads to confusion for
+external developers and should be considered a last-resort when backporting
+behavior is not feasible.
+
+## Platform compatibility API patterns {#platform-compatibility-apis}
+
+### Static shims (ex. [ViewCompat](https://developer.android.com/reference/android/support/v4/view/ViewCompat.html)) {#static-shim}
+
+When to use?
+
+*   Platform class exists at module's `minSdkVersion`
+*   Compatibility implementation does not need to store additional metadata
+
+Implementation requirements
+
+*   Class name **must** be `<PlatformClass>Compat`
+*   Package name **must** be `androidx.<feature>.<platform.package>`
+*   Superclass **must** be `Object`
+*   Class **must** be non-instantiable, i.e. constructor is private no-op
+*   Static fields and static methods **must** match match signatures with
+    `PlatformClass`
+    *   Static fields that can be inlined, ex. integer constants, **must not**
+        be shimmed
+*   Public method names **must** match platform method names
+*   Public methods **must** be static and take `PlatformClass` as first
+    parameter
+*   Implementation _may_ delegate to `PlatformClass` methods when available
+
+#### Sample {#static-shim-sample}
+
+The following sample provides static helper methods for the platform class
+`android.os.Process`.
+
+```java
+/**
+ * Helper for accessing features in {@link Process}.
+ */
+public final class ProcessCompat {
+    private ProcessCompat() {
+        // This class is non-instantiable.
+    }
+
+    /**
+     * [Docs should match platform docs.]
+     *
+     * Compatibility behavior:
+     * <ul>
+     * <li>SDK 24 and above, this method matches platform behavior.
+     * <li>SDK 16 through 23, this method is a best-effort to match platform behavior, but may
+     * default to returning {@code true} if an accurate result is not available.
+     * <li>SDK 15 and below, this method always returns {@code true} as application UIDs and
+     * isolated processes did not exist yet.
+     * </ul>
+     *
+     * @param [match platform docs]
+     * @return [match platform docs], or a value based on platform-specific fallback behavior
+     */
+    public static boolean isApplicationUid(int uid) {
+        if (Build.VERSION.SDK_INT >= 24) {
+            return Api24Impl.isApplicationUid(uid);
+        } else if (Build.VERSION.SDK_INT >= 17) {
+            return Api17Impl.isApplicationUid(uid);
+        } else if (Build.VERSION.SDK_INT == 16) {
+            return Api16Impl.isApplicationUid(uid);
+        } else {
+            return true;
+        }
+    }
+
+    @RequiresApi(24)
+    static class Api24Impl {
+        static boolean isApplicationUid(int uid) {
+            // In N, the method was made public on android.os.Process.
+            return Process.isApplicationUid(uid);
+        }
+    }
+
+    @RequiresApi(17)
+    static class Api17Impl {
+        private static Method sMethod_isAppMethod;
+        private static boolean sResolved;
+
+        static boolean isApplicationUid(int uid) {
+            // In JELLY_BEAN_MR2, the equivalent isApp(int) hidden method moved to public class
+            // android.os.UserHandle.
+            try {
+                if (!sResolved) {
+                    sResolved = true;
+                    sMethod_isAppMethod = UserHandle.class.getDeclaredMethod("isApp",int.class);
+                }
+                if (sMethod_isAppMethod != null) {
+                    return (Boolean) sMethod_isAppMethod.invoke(null, uid);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return true;
+        }
+    }
+
+    ...
+}
+```
+
+### Wrapper (ex. [AccessibilityNodeInfoCompat](https://developer.android.com/reference/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.html)) {#wrapper}
+
+When to use?
+
+*   Platform class may not exist at module's `minSdkVersion`
+*   Compatibility implementation may need to store additional metadata
+*   Needs to integrate with platform APIs as return value or method argument
+*   **Note:** Should be avoided when possible, as using wrapper classes makes it
+    very difficult to deprecate classes and migrate source code when the
+    `minSdkVersion` is raised
+
+#### Sample {#wrapper-sample}
+
+The following sample wraps a hypothetical platform class `ModemInfo` that was
+added to the platform SDK in API level 23:
+
+```java
+public final class ModemInfoCompat {
+  // Only guaranteed to be non-null on SDK_INT >= 23. Note that referencing the
+  // class itself directly is fine -- only references to class members need to
+  // be pushed into static inner classes.
+  private final ModemInfo wrappedObj;
+
+  /**
+   * [Copy platform docs for matching constructor.]
+   */
+  public ModemInfoCompat() {
+    if (SDK_INT >= 23) {
+      wrappedObj = Api23Impl.create();
+    } else {
+      wrappedObj = null;
+    }
+    ...
+  }
+
+  @RequiresApi(23)
+  private ModemInfoCompat(@NonNull ModemInfo obj) {
+    mWrapped = obj;
+  }
+
+  /**
+   * Provides a backward-compatible wrapper for {@link ModemInfo}.
+   * <p>
+   * This method is not supported on devices running SDK < 23 since the platform
+   * class will not be available.
+   *
+   * @param info platform class to wrap
+   * @return wrapped class, or {@code null} if parameter is {@code null}
+   */
+  @RequiresApi(23)
+  @NonNull
+  public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info) {
+    return new ModemInfoCompat(obj);
+  }
+
+  /**
+   * Provides the {@link ModemInfo} represented by this object.
+   * <p>
+   * This method is not supported on devices running SDK < 23 since the platform
+   * class will not be available.
+   *
+   * @return platform class object
+   * @see ModemInfoCompat#toModemInfoCompat(ModemInfo)
+   */
+  @RequiresApi(23)
+  @NonNull
+  public ModemInfo toModemInfo() {
+    return mWrapped;
+  }
+
+  /**
+   * [Docs should match platform docs.]
+   *
+   * Compatibility behavior:
+   * <ul>
+   * <li>API level 23 and above, this method matches platform behavior.
+   * <li>API level 18 through 22, this method ...
+   * <li>API level 17 and earlier, this method always returns false.
+   * </ul>
+   *
+   * @return [match platform docs], or platform-specific fallback behavior
+   */
+  public boolean isLteSupported() {
+    if (SDK_INT >= 23) {
+      return Api23Impl.isLteSupported(mWrapped);
+    } else if (SDK_INT >= 18) {
+      // Smart fallback behavior based on earlier APIs.
+      ...
+    }
+    // Default behavior.
+    return false;
+  }
+
+  // All references to class members -- including the constructor -- must be
+  // made on an inner class to avoid soft-verification errors that slow class
+  // loading and prevent optimization.
+  @RequiresApi(23)
+  private static class Api23Impl {
+    @NonNull
+    static ModemInfo create() {
+      return new ModemInfo();
+    }
+
+    static boolean isLteSupported(PlatformClass obj) {
+      return obj.isLteSupported();
+    }
+  }
+}
+```
+
+Note that libraries written in Java should express conversion to and from the
+platform class differently than Kotlin classes. For Java classes, conversion
+from the platform class to the wrapper should be expressed as a `static` method,
+while conversion from the wrapper to the platform class should be a method on
+the wrapper object:
+
+```java
+@NonNull
+public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info);
+
+@NonNull
+public ModemInfo toModemInfo();
+```
+
+In cases where the primary library is written in Java and has an accompanying
+`-ktx` Kotlin extensions library, the following conversion should be provided as
+an extension function:
+
+```kotlin
+fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
+```
+
+Whereas in cases where the primary library is written in Kotlin, the conversion
+should be provided as an extension factory:
+
+```kotlin
+class ModemInfoCompat {
+  fun toModemInfo() : ModemInfo
+
+  companion object {
+    @JvmStatic
+    @JvmName("toModemInfoCompat")
+    fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
+  }
+}
+```
+
+#### API guidelines {#wrapper-api-guidelines}
+
+##### Naming {#wrapper-naming}
+
+*   Class name **must** be `<PlatformClass>Compat`
+*   Package name **must** be `androidx.core.<platform.package>`
+*   Superclass **must not** be `<PlatformClass>`
+
+##### Construction {#wrapper-construction}
+
+*   Class _may_ have public constructor(s) to provide parity with public
+    `PlatformClass` constructors
+    *   Constructor used to wrap `PlatformClass` **must not** be public
+*   Class **must** implement a static `PlatformClassCompat
+    toPlatformClassCompat(PlatformClass)` method to wrap `PlatformClass` on
+    supported SDK levels
+    *   If class does not exist at module's `minSdkVersion`, method must be
+        annotated with `@RequiresApi(<sdk>)` for SDK version where class was
+        introduced
+
+#### Implementation {#wrapper-implementation}
+
+*   Class **must** implement a `PlatformClass toPlatformClass()` method to
+    unwrap `PlatformClass` on supported SDK levels
+    *   If class does not exist at module's `minSdkVersion`, method must be
+        annotated with `@RequiresApi(<sdk>)` for SDK version where class was
+        introduced
+*   Implementation _may_ delegate to `PlatformClass` methods when available (see
+    below note for caveats)
+*   To avoid runtime class verification issues, all operations that interact
+    with the internal structure of `PlatformClass` must be implemented in inner
+    classes targeted to the SDK level at which the operation was added.
+    *   See the [sample](#wrapper-sample) for an example of interacting with a
+        method that was added in SDK level 23.
+
+### Standalone (ex. [ArraySet](https://developer.android.com/reference/android/support/v4/util/ArraySet.html), [Fragment](https://developer.android.com/reference/android/support/v4/app/Fragment.html)) {#standalone}
+
+When to use?
+
+*   Platform class may exist at module's `minSdkVersion`
+*   Does not need to integrate with platform APIs
+*   Does not need to coexist with platform class, ex. no potential `import`
+    collision due to both compatibility and platform classes being referenced
+    within the same source file
+
+Implementation requirements
+
+*   Class name **must** be `<PlatformClass>`
+*   Package name **must** be `androidx.<platform.package>`
+*   Superclass **must not** be `<PlatformClass>`
+*   Class **must not** expose `PlatformClass` in public API
+*   Implementation _may_ delegate to `PlatformClass` methods when available
+
+### Standalone JAR library (no Android dependencies) {#standalone-jar-library-no-android-dependencies}
+
+When to use
+
+*   General purpose library with minimal interaction with Android types
+    *   or when abstraction around types can be used (e.g. Room's SQLite
+        wrapper)
+*   Lib used in parts of app with minimal Android dependencies
+    *   ex. Repository, ViewModel
+*   When Android dependency can sit on top of common library
+*   Clear separation between android dependent and independent parts of your
+    library
+*   Clear that future integration with android dependencies can be layered
+    separately
+
+**Examples:**
+
+The **Paging Library** pages data from DataSources (such as DB content from Room
+or network content from Retrofit) into PagedLists, so they can be presented in a
+RecyclerView. Since the included Adapter receives a PagedList, and there are no
+other Android dependencies, Paging is split into two parts - a no-android
+library (paging-common) with the majority of the paging code, and an android
+library (paging-runtime) with just the code to present a PagedList in a
+RecyclerView Adapter. This way, tests of Repositories and their components can
+be tested in host-side tests.
+
+**Room** loads SQLite data on Android, but provides an abstraction for those
+that want to use a different SQL implementation on device. This abstraction, and
+the fact that Room generates code dynamically, means that Room interfaces can be
+used in host-side tests (though actual DB code should be tested on device, since
+DB impls may be significantly different on host).
+
+## Implementing compatibility {#compat}
+
+### Referencing new APIs {#compat-newapi}
+
+Generally, methods on extension library classes should be available to all
+devices above the library's `minSdkVersion`.
+
+#### Checking device SDK version {#compat-sdk}
+
+The most common way of delegating to platform or backport implementations is to
+compare the device's `Build.VERSION.SDK_INT` field to a known-good SDK version;
+for example, the SDK in which a method first appeared or in which a critical bug
+was first fixed.
+
+Non-reflective calls to new APIs gated on `SDK_INT` **must** be made from
+version-specific static inner classes to avoid verification errors that
+negatively affect run-time performance. For more information, see Chromium's
+guide to
+[Class Verification Failures](https://chromium.googlesource.com/chromium/src/+/HEAD/build/android/docs/class_verification_failures.md).
+
+Methods in implementation-specific classes **must** be paired with the
+`@DoNotInline` annotation to prevent them from being inlined.
+
+```java {.good}
+public static void saveAttributeDataForStyleable(@NonNull View view, ...) {
+  if (Build.VERSION.SDK_INT >= 29) {
+    Api29Impl.saveAttributeDataForStyleable(view, ...);
+  }
+}
+
+@RequiresApi(29)
+private static class Api29Impl {
+  @DoNotInline
+  static void saveAttributeDataForStyleable(@NonNull View view, ...) {
+    view.saveAttributeDataForStyleable(...);
+  }
+}
+```
+
+Alternatively, in Kotlin sources:
+
+```kotlin {.good}
+@RequiresApi(29)
+object Api25 {
+  @DoNotInline
+  fun saveAttributeDataForStyleable(view: View, ...) { ... }
+}
+```
+
+When developing against pre-release SDKs where the `SDK_INT` has not been
+finalized, SDK checks **must** use `BuildCompat.isAtLeastX()` methods.
+
+```java {.good}
+@NonNull
+public static List<Window> getAllWindows() {
+  if (BuildCompat.isAtLeastR()) {
+    return ApiRImpl.getAllWindows();
+  }
+  return Collections.emptyList();
+}
+```
+
+#### Device-specific issues {#compat-oem}
+
+Library code may work around device- or manufacturer-specific issues -- issues
+not present in AOSP builds of Android -- *only* if a corresponding CTS test
+and/or CDD policy is added to the next revision of the Android platform. Doing
+so ensures that such issues can be detected and fixed by OEMs.
+
+#### Handling `minSdkVersion` disparity {#compat-minsdk}
+
+Methods that only need to be accessible on newer devices, including
+`to<PlatformClass>()` methods, may be annotated with `@RequiresApi(<sdk>)` to
+indicate they will fail to link on older SDKs. This annotation is enforced at
+build time by Lint.
+
+#### Handling `targetSdkVersion` behavior changes {#compat-targetsdk}
+
+To preserve application functionality, device behavior at a given API level may
+change based on an application's `targetSdkVersion`. For example, if an app with
+`targetSdkVersion` set to API level 22 runs on a device with API level 29, all
+required permissions will be granted at installation time and the run-time
+permissions framework will emulate earlier device behavior.
+
+Libraries do not have control over the app's `targetSdkVersion` and -- in rare
+cases -- may need to handle variations in platform behavior. Refer to the
+following pages for version-specific behavior changes:
+
+*   API level 29:
+    [Android Q behavior changes: apps targeting Q](https://developer.android.com/preview/behavior-changes-q)
+*   API level 28:
+    [Behavior changes: apps targeting API level 28+](https://developer.android.com/about/versions/pie/android-9.0-changes-28)
+*   API level 26:
+    [Changes for apps targeting Android 8.0](https://developer.android.com/about/versions/oreo/android-8.0-changes#o-apps)
+*   API level 24:
+    [Changes for apps targeting Android 7.0](https://developer.android.com/about/versions/nougat/android-7.0-changes#n-apps)
+*   API level 21:
+    [Android 5.0 Behavior Changes](https://developer.android.com/about/versions/android-5.0-changes)
+*   API level 19:
+    [Android 4.4 APIs](https://developer.android.com/about/versions/android-4.4)
+
+#### Working around Lint issues {#compat-lint}
+
+In rare cases, Lint may fail to interpret API usages and yield a `NewApi` error
+and require the use of `@TargetApi` or `@SuppressLint('NewApi')` annotations.
+Both of these annotations are strongly discouraged and may only be used
+temporarily. They **must never** be used in a stable release. Any usage of these
+annotation **must** be associated with an active bug, and the usage must be
+removed when the bug is resolved.
+
+### Delegating to API-specific implementations {#delegating-to-api-specific-implementations}
+
+#### SDK-dependent reflection
+
+Starting in API level 28, the platform restricts which
+[non-SDK interfaces](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces)
+can be accessed via reflection by apps and libraries. As a general rule, you
+will **not** be able to use reflection to access hidden APIs on devices with
+`SDK_INT` greater than `Build.VERSION_CODES.P` (28).
+
+On earlier devices, reflection on hidden platform APIs is allowed **only** when
+an alternative public platform API exists in a later revision of the Android
+SDK. For example, the following implementation is allowed:
+
+```java
+public AccessibilityDelegate getAccessibilityDelegate(View v) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+        // Retrieve the delegate using a public API.
+        return v.getAccessibilityDelegate();
+    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+        // Retrieve the delegate by reflecting on a private field. If the
+        // field does not exist or cannot be accessed, this will no-op.
+        if (sAccessibilityDelegateField == null) {
+            try {
+                sAccessibilityDelegateField = View.class
+                        .getDeclaredField("mAccessibilityDelegate");
+                sAccessibilityDelegateField.setAccessible(true);
+            } catch (Throwable t) {
+                sAccessibilityDelegateCheckFailed = true;
+                return null;
+            }
+        }
+        try {
+            Object o = sAccessibilityDelegateField.get(v);
+            if (o instanceof View.AccessibilityDelegate) {
+                return (View.AccessibilityDelegate) o;
+            }
+            return null;
+        } catch (Throwable t) {
+            sAccessibilityDelegateCheckFailed = true;
+            return null;
+        }
+    } else {
+        // There is no way to retrieve the delegate, even via reflection.
+        return null;
+    }
+```
+
+Calls to public APIs added in pre-release revisions *must* be gated using
+`BuildCompat`:
+
+```java
+if (BuildCompat.isAtLeastQ()) {
+   // call new API added in Q
+} else if (Build.SDK_INT.VERSION >= Build.VERSION_CODES.SOME_RELEASE) {
+   // make a best-effort using APIs that we expect to be available
+} else {
+   // no-op or best-effort given no information
+}
+```
+
+### Inter-process communication {#inter-process-communication}
+
+Protocols and data structures used for IPC must support interoperability between
+different versions of libraries and should be treated similarly to public API.
+
+#### Data structures
+
+**Do not** use Parcelable for any class that may be used for IPC or otherwise
+exposed as public API. The data format used by Parcelable does not provide any
+compatibility guarantees and will result in crashes if fields are added or
+removed between library versions.
+
+**Do not** design your own serialization mechanism or wire format for disk
+storage or inter-process communication. Preserving and verifying compatibility
+is difficult and error-prone.
+
+If you expose a `Bundle` to callers that can cross processes, you should
+[prevent apps from adding their own custom parcelables](https://android.googlesource.com/platform/frameworks/base/+/6cddbe14e1ff67dc4691a013fe38a2eb0893fe03)
+as top-level entries; if *any* entry in a `Bundle` can't be loaded, even if it's
+not actually accessed, the receiving process is likely to crash.
+
+**Do** use protocol buffers or, in some simpler cases, `VersionedParcelable`.
+
+#### Communication protocols
+
+Any communication prototcol, handshake, etc. must maintain compatibility
+consistent with SemVer guidelines. Consider how your protocol will handle
+addition and removal of operations or constants, compatibility-breaking changes,
+and other modifications without crashing either the host or client process.
+
+## Deprecation and removal
+
+While SemVer's binary compatibility guarantees restrict the types of changes
+that may be made within a library revision and make it difficult to remove an
+API, there are many other ways to influence how developers interact with your
+library.
+
+### Deprecation (`@deprecated`)
+
+Deprecation lets a developer know that they should stop using an API or class.
+All deprecations must be marked with a `@Deprecated` Java annotation as well as
+a `@deprecated <migration-docs>` docs annotation explaining how the developer
+should migrate away from the API.
+
+Deprecation is an non-breaking API change that must occur in a **major** or
+**minor** release.
+
+### Soft removal (@removed)
+
+Soft removal preserves binary compatibility while preventing source code from
+compiling against an API. It is a *source-breaking change* and not recommended.
+
+Soft removals **must** do the following:
+
+*   Mark the API as deprecated for at least one stable release prior to removal.
+*   Mark the API with a `@RestrictTo(LIBRARY)` Java annotation as well as a
+    `@removed <reason>` docs annotation explaining why the API was removed.
+*   Maintain binary compatibility, as the API may still be called by existing
+    dependent libraries.
+*   Maintain behavioral compatibility and existing tests.
+
+This is a disruptive change and should be avoided when possible.
+
+Soft removal is a source-breaking API change that must occur in a **major** or
+**minor** release.
+
+### Hard removal
+
+Hard removal entails removing the entire implementation of an API that was
+exposed in a public release. Prior to removal, an API must be marked as
+`@deprecated` for a full **minor** version (`alpha`->`beta`->`rc`->stable),
+prior to being hard removed.
+
+This is a disruptive change and should be avoided when possible.
+
+Hard removal is a binary-breaking API change that must occur in a **major**
+release.
+
+### For entire artifacts
+
+We do not typically deprecate or remove entire artifacts; however, it may be
+useful in cases where we want to halt development and focus elsewhere or
+strongly discourage developers from using a library.
+
+Halting development, either because of staffing or prioritization issues, leaves
+the door open for future bug fixes or continued development. This quite simply
+means we stop releasing updates but retain the source in our tree.
+
+Deprecating an artifact provides developers with a migration path and strongly
+encourages them -- through Lint warnings -- to migrate elsewhere. This is
+accomplished by adding a `@Deprecated` and `@deprecated` (with migration
+comment) annotation pair to *every* class and interface in the artifact.
+
+The fully-deprecated artifact will be released as a deprecation release -- it
+will ship normally with accompanying release notes indicating the reason for
+deprecation and migration strategy, and it will be the last version of the
+artifact that ships. It will ship as a new minor stable release. For example, if
+`1.0.0` was the last stable release, then the deprecation release will be
+`1.1.0`. This is so Android Studio users will get a suggestion to update to a
+new stable version, which will contain the `@deprecated` annotations.
+
+After an artifact has been released as fully-deprecated, it can be removed from
+the source tree.
+
+## Resources {#resources}
+
+Generally, follow the official Android guidelines for
+[app resources](https://developer.android.com/guide/topics/resources/providing-resources).
+Special guidelines for library resources are noted below.
+
+### Defining new resources
+
+Libraries may define new value and attribute resources using the standard
+application directory structure used by Android Gradle Plugin:
+
+```
+src/main/res/
+  values/
+    attrs.xml   Theme attributes and styleables
+    dimens.xml  Dimensional values
+    public.xml  Public resource definitions
+    ...
+```
+
+However, some libraries may still be using non-standard, legacy directory
+structures such as `res-public` for their public resource declarations or a
+top-level `res` directory and accompanying custom source set in `build.gradle`.
+These libraries will eventually be migrated to follow standard guidelines.
+
+#### Naming conventions
+
+Libraries follow the Android platform's resource naming conventions, which use
+`camelCase` for attributes and `underline_delimited` for values. For example,
+`R.attr.fontProviderPackage` and `R.dimen.material_blue_grey_900`.
+
+#### Attribute formats
+
+At build time, attribute definitions are pooled globally across all libraries
+used in an application, which means attribute `format`s *must* be identical for
+a given `name` to avoid a conflict.
+
+Within Jetpack, new attribute names *must* be globally unique. Libraries *may*
+reference existing public attributes from their dependencies. See below for more
+information on public attributes.
+
+When adding a new attribute, the format should be defined *once* in an `<attr
+/>` element in the definitions block at the top of `src/main/res/attrs.xml`.
+Subsequent references in `<declare-styleable>` elements *must* not include a
+`format`:
+
+`src/main/res/attrs.xml`
+
+```xml
+<resources>
+  <attr name="fontProviderPackage" format="string" />
+
+  <declare-styleable name="FontFamily">
+      <attr name="fontProviderPackage" />
+  </declare-styleable>
+</resources>
+```
+
+### Public resources
+
+Library resources are private by default, which means developers are discouraged
+from referencing any defined attributes or values from XML or code; however,
+library resources may be declared public to make them available to developers.
+
+Public library resources are considered API surface and are thus subject to the
+same API consistency and documentation requirements as Java APIs.
+
+Libraries will typically only expose theme attributes, ex. `<attr />` elements,
+as public API so that developers can set and retrieve the values stored in
+styles and themes. Exposing values -- such as `<dimen />` and `<string />` -- or
+images -- such as drawable XML and PNGs -- locks the current state of those
+elements as public API that cannot be changed without a major version bump. That
+means changing a publicly-visible icon would be considered a breaking change.
+
+#### Documentation
+
+All public resource definitions should be documented, including top-level
+definitions and re-uses inside `<styleable>` elements:
+
+`src/main/res/attrs.xml`
+
+```xml
+<resources>
+  <!-- String specifying the application package for a Font Provider. -->
+  <attr name="fontProviderPackage" format="string" />
+
+  <!-- Attributes that are read when parsing a <fontfamily> tag. -->
+  <declare-styleable name="FontFamily">
+      <!-- The package for the Font Provider to be used for the request. This is
+           used to verify the identity of the provider. -->
+      <attr name="fontProviderPackage" />
+  </declare-styleable>
+</resources>
+```
+
+`src/main/res/colors.xml`
+
+```xml
+<resources>
+  <!-- Color for Material Blue-Grey 900. -->
+  <color name="material_blue_grey_900">#ff263238</color>
+</resources>
+```
+
+#### Public declaration
+
+Resources are declared public by providing a separate `<public />` element with
+a matching type:
+
+`src/main/res/public.xml`
+
+```xml
+<resources>
+  <public name="fontProviderPackage" type="attr" />
+  <public name="material_blue_grey_900" type="color" />
+</resources>
+```
+
+#### More information
+
+See also the official Android Gradle Plugin documentation for
+[Private Resources](https://developer.android.com/studio/projects/android-library#PrivateResources).
+
+### Manifest entries (`AndroidManifest.xml`) {#resources-manifest}
+
+#### Metadata tags (`<meta-data>`) {#resources-manifest-metadata}
+
+Developers **must not** add `<application>`-level `<meta-data>` tags to library
+manifests or advise developers to add such tags to their application manifests.
+Doing so may _inadvertently cause denial-of-service attacks against other apps_.
+
+Assume a library adds a single item of meta-data at the application level. When
+an app uses the library, that meta-data will be merged into the resulting app's
+application entry via manifest merger.
+
+If another app attempts to obtain a list of all activities associated with the
+primary app, that list will contain multiple copies of the `ApplicationInfo`,
+each of which in turn contains a copy of the library's meta-data. As a result,
+one `<metadata>` tag may become hundreds of KB on the binder call to obtain the
+list -- resulting in apps hitting transaction too large exceptions and crashing.
+
+```xml {.bad}
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.librarypackage">
+  <application>
+    <meta-data
+        android:name="keyName"
+        android:value="@string/value" />
+  </application>
+</manifest>
+```
+
+Instead, developers may consider adding `<metadata>` nested inside of
+placeholder `<service>` tags.
+
+```xml {.good}
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.librarypackage">
+  <application>
+    <service
+        android:name="androidx.librarypackage.MetadataHolderService"
+        android:enabled="false"
+        android:exported="false">
+      <meta-data
+          android:name="androidx.librarypackage.MetadataHolderService.KEY_NAME"
+          android:resource="@string/value" />
+    </service>
+  </application>
+```
+
+```java {.good}
+package androidx.libraryname.featurename;
+
+/**
+ * A placeholder service to avoid adding application-level metadata. The service
+ * is only used to expose metadata defined in the library's manifest. It is
+ * never invoked.
+ */
+public final class MetadataHolderService {
+  private MetadataHolderService() {}
+
+  @Override
+  public IBinder onBind(Intent intent) {
+    throw new UnsupportedOperationException();
+  }
+}
+```
+
+## Dependencies {#dependencies}
+
+Generally, Jetpack libraries should avoid dependencies that negatively impact
+developers without providing substantial benefit. This includes large
+dependencies where only a small portion is needed, dependencies that slow down
+build times through annotation processing or compiler overhead, and generally
+any dependency that negatively affects system health.
+
+### Kotlin {#dependencies-kotlin}
+
+Kotlin is _recommended_ for new libraries; however, it's important to consider
+its size impact on clients. Currently, the Kotlin stdlib adds a minimum of 40kB
+post-optimization.
+
+### Kotlin coroutines {#dependencies-coroutines}
+
+Kotlin's coroutine library adds around 100kB post-shrinking. New libraries that
+are written in Kotlin should prefer coroutines over `ListenableFuture`, but
+existing libraries must consider the size impact on their clients. See
+[Asynchronous work with return values](#async-return) for more details on using
+Kotlin coroutines in Jetpack libraries.
+
+### Guava {#dependencies-guava}
+
+The full Guava library is very large and *must not* be used. Libraries that
+would like to depend on Guava's `ListenableFuture` may instead depend on the
+standalone `com.google.guava:listenablefuture` artifact. See
+[Asynchronous work with return values](#async-return) for more details on using
+`ListenableFuture` in Jetpack libraries.
+
+### Java 8 {#dependencies-java8}
+
+Libraries that take a dependency on a library targeting Java 8 must _also_
+target Java 8, which will incur a ~5% build performance (as of 8/2019) hit for
+clients. New libraries targeting Java 8 may use Java 8 dependencies; however,
+existing libraries targeting Java 7 should not.
+
+The default language level for `androidx` libraries is Java 8, and we encourage
+libraries to stay on Java 8. However, if you have a business need to target Java
+7, you can specify Java 7 in your `build.gradle` as follows:
+
+```Groovy
+android {
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_1_7
+        targetCompatibility = JavaVersion.VERSION_1_7
+    }
+}
+```
+
+## More API guidelines {#more-api-guidelines}
+
+### Annotations {#annotation}
+
+#### Annotation processors {#annotation-processor}
+
+Annotation processors should opt-in to incremental annotation processing to
+avoid triggering a full recompilation on every client source code change. See
+Gradle's
+[Incremental annotation processing](https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing)
+documentation for information on how to opt-in.
+
+### Experimental APIs {#experimental-api}
+
+Jetpack libraries may choose to annotate API surfaces as unstable using either
+Kotlin's
+[`@Experimental` annotation](https://kotlinlang.org/docs/reference/experimental.html)
+for APIs written in Kotlin or Jetpack's
+[`@Experimental` annotation](https://developer.android.com/reference/kotlin/androidx/annotation/experimental/Experimental)
+for APIs written in Java.
+
+In both cases, API surfaces marked as experimental are considered alpha and will
+be excluded from API compatibility guarantees. Due to the lack of compatibility
+guarantees, libraries *must never* call experimental APIs exposed by other
+libraries and *may not* use the `@UseExperimental` annotation except in the
+following cases:
+
+*   A library within a same-version group *may* call an experimental API exposed
+    by another library **within its same-version group**. In this case, API
+    compatibility guarantees are covered under the same-version group policies
+    and the library *may* use the `@UsesExperimental` annotation to prevent
+    propagation of the experimental property. **Library owners must exercise
+    care to ensure that post-alpha APIs backed by experimental APIs actually
+    meet the release criteria for post-alpha APIs.**
+
+#### How to mark an API surface as experimental
+
+All libraries using `@Experimental` annotations *must* depend on the
+`androidx.annotation:annotation-experimental` artifact regardless of whether
+they are using the `androidx` or Kotlin annotation. This artifact provides Lint
+enforcement of experimental usage restrictions for Kotlin callers as well as
+Java (which the Kotlin annotation doesn't handle on its own, since it's a Kotlin
+compiler feature). Libraries *may* include the dependency as `api`-type to make
+`@UseExperimental` available to Java clients; however, this will also
+unnecessarily expose the `@Experimental` annotation.
+
+```java
+dependencies {
+    implementation(project(":annotation:annotation-experimental"))
+}
+```
+
+See Kotlin's
+[experimental marker documentation](https://kotlinlang.org/docs/reference/experimental.html)
+for general usage information. If you are writing experimental Java APIs, you
+will use the Jetpack
+[`@Experimental` annotation](https://developer.android.com/reference/kotlin/androidx/annotation/experimental/Experimental)
+rather than the Kotlin compiler's annotation.
+
+#### How to transition an API out of experimental
+
+When an API surface is ready to transition out of experimental, the annotation
+may only be removed during an alpha pre-release stage since removing the
+experimental marker from an API is equivalent to adding the API to the current
+API surface.
+
+When transitioning an entire feature surface out of experimental, you *should*
+remove the associated annotations.
+
+When making any change to the experimental API surface, you *must* run
+`./gradlew updateApi` prior to uploading your change.
+
+### Restricted APIs {#restricted-api}
+
+Jetpack's library tooling supports hiding Java-visible (ex. `public` and
+`protected`) APIs from developers using a combination of the `@hide` docs
+annotation and `@RestrictTo` source annotation. These annotations **must** be
+paired together when used, and are validated as part of presubmit checks for
+Java code (Kotlin not yet supported by Checkstyle).
+
+The effects of hiding an API are as follows:
+
+*   The API will not appear in documentation
+*   Android Studio will warn the developer not to use the API
+
+Hiding an API does *not* provide strong guarantees about usage:
+
+*   There are no runtime restrictions on calling hidden APIs
+*   Android Studio will not warn if hidden APIs are called using reflection
+*   Hidden APIs will still show in Android Studio's auto-complete
+
+#### When to use `@hide` {#restricted-api-usage}
+
+Generally, avoid using `@hide`. The `@hide` annotation indicates that developers
+should not call an API that is _technically_ public from a Java visibility
+perspective. Hiding APIs is often a sign of a poorly-abstracted API surface, and
+priority should be given to creating public, maintainable APIs and using Java
+visibility modifiers.
+
+*Do not* use `@hide` to bypass API tracking and review for production APIs;
+instead, rely on API+1 and API Council review to ensure APIs are reviewed on a
+timely basis.
+
+*Do not* use `@hide` for implementation detail APIs that are used between
+libraries and could reasonably be made public.
+
+*Do* use `@hide` paired with `@RestrictTo(LIBRARY)` for implementation detail
+APIs used within a single library (but prefer Java language `private` or
+`default` visibility).
+
+#### `RestrictTo.Scope` and inter- versus intra-library API surfaces {#private-api-types}
+
+To maintain binary compatibility between different versions of libraries,
+restricted API surfaces that are used between libraries (inter-library APIs)
+must follow the same Semantic Versioning rules as public APIs. Inter-library
+APIs should be annotated with the `@RestrictTo(LIBRARY_GROUP)` source
+annotation.
+
+Restricted API surfaces used within a single library (intra-library APIs), on
+the other hand, may be added or removed without any compatibility
+considerations. It is safe to assume that developers _never_ call these APIs,
+even though it is technically feasible. Intra-library APIs should be annotated
+with the `@RestrictTo(LIBRARY)` source annotation.
+
+The following table shows the visibility of a hypothetical API within Maven
+coordinate `androidx.concurrent:concurrent` when annotated with a variety of
+scopes:
+
+<table>
+    <tr>
+        <td><code>RestrictTo.Scope</code></td>
+        <td>Visibility by Maven coordinate</td>
+    </tr>
+    <tr>
+        <td><code>LIBRARY</code></td>
+        <td><code>androidx.concurrent:concurrent</code></td>
+    </tr>
+    <tr>
+        <td><code>LIBRARY_GROUP</code></td>
+        <td><code>androidx.concurrent:*</code></td>
+    </tr>
+    <tr>
+        <td><code>LIBRARY_GROUP_PREFIX</code></td>
+        <td><code>androidx.*:*</code></td>
+    </tr>
+</table>
+
+### Constructors {#constructors}
+
+#### View constructors {#view-constructors}
+
+The four-arg View constructor -- `View(Context, AttributeSet, int, int)` -- was
+added in SDK 21 and allows a developer to pass in an explicit default style
+resource rather than relying on a theme attribute to resolve the default style
+resource. Because this API was added in SDK 21, care must be taken to ensure
+that it is not called through any < SDK 21 code path.
+
+Views _may_ implement a four-arg constructor in one of the following ways:
+
+1.  Do not implement.
+1.  Implement and annotate with `@RequiresApi(21)`. This means the three-arg
+    constructor **must not** call into the four-arg constructor.
+
+### Asynchronous work {#async}
+
+#### With return values {#async-return}
+
+Traditionally, asynchronous work on Android that results in an output value
+would use a callback; however, better alternatives exist for libraries.
+
+Kotlin libraries should prefer
+[coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) and
+`suspend` functions, but please refer to the guidance on
+[allowable dependencies](#dependencies-coroutines) before adding a new
+dependency on coroutines.
+
+Java libraries should prefer `ListenableFuture` and the
+[`CallbackToFutureAdapter`](https://developer.android.com/reference/androidx/concurrent/futures/CallbackToFutureAdapter)
+implementation provided by the `androidx.concurrent:concurrent-futures` library.
+
+Libraries **must not** use `java.util.concurrent.CompletableFuture`, as it has a
+large API surface that permits arbitrary mutation of the future's value and has
+error-prone defaults.
+
+See the [Dependencies](#dependencies) section for more information on using
+Kotlin coroutines and Guava in your library.
+
+#### Avoid `synchronized` methods
+
+Whenever multiple threads are interacting with shared (mutable) references those
+reads and writes must be synchronized in some way. However synchronized blocks
+make your code thread-safe at the expense of concurrent execution. Any time
+execution enters a synchronized block or method any other thread trying to enter
+a synchronized block on the same object has to wait; even if in practice the
+operations are unrelated (e.g. they interact with different fields). This can
+dramatically reduce the benefit of trying to write multi-threaded code in the
+first place.
+
+Locking with synchronized is a heavyweight form of ensuring ordering between
+threads, and there are a number of common APIs and patterns that you can use
+that are more lightweight, depending on your use case:
+
+*   Compute a value once and make it available to all threads
+*   Update Set and Map data structures across threads
+*   Allow a group of threads to process a stream of data concurrently
+*   Provide instances of a non-thread-safe type to multiple threads
+*   Update a value from multiple threads atomically
+*   Maintain granular control of your concurrency invariants
+
+### Kotlin {#kotlin}
+
+#### Data classes {#kotlin-data}
+
+Kotlin `data` classes provide a convenient way to define simple container
+objects, where Kotlin will generate `equals()` and `hashCode()` for you.
+However, they are not designed to preserve API/binary compatibility when members
+are added. This is due to other methods which are generated for you -
+[destructuring declarations](https://kotlinlang.org/docs/reference/multi-declarations.html),
+and [copying](https://kotlinlang.org/docs/reference/data-classes.html#copying).
+
+Example data class as tracked by metalava:
+
+<pre>
+  public final class TargetAnimation {
+    ctor public TargetAnimation(float target, androidx.animation.AnimationBuilder animation);
+    <b>method public float component1();</b>
+    <b>method public androidx.animation.AnimationBuilder component2();</b>
+    <b>method public androidx.animation.TargetAnimation copy(float target, androidx.animation.AnimationBuilder animation);</b>
+    method public androidx.animation.AnimationBuilder getAnimation();
+    method public float getTarget();
+  }
+</pre>
+
+Because members are exposed as numbered components for destructuring, you can
+only safely add members at the end of the member list. As `copy` is generated
+with every member name in order as well, you'll also have to manually
+re-implement any old `copy` variants as items are added. If these constraints
+are acceptable, data classes may still be useful to you.
+
+As a result, Kotlin `data` classes are _strongly discouraged_ in library APIs.
+Instead, follow best-practices for Java data classes including implementing
+`equals`, `hashCode`, and `toString`.
+
+See Jake Wharton's article on
+[Public API challenges in Kotlin](https://jakewharton.com/public-api-challenges-in-kotlin/)
+for more details.
+
+#### Extension and top-level functions {#kotlin-extension-functions}
+
+If your Kotlin file contains any sybmols outside of class-like types
+(extension/top-level functions, properties, etc), the file must be annotated
+with `@JvmName`. This ensures unanticipated use-cases from Java callers don't
+get stuck using `BlahKt` files.
+
+Example:
+
+```kotlin {.bad}
+package androidx.example
+
+fun String.foo() = // ...
+```
+
+```kotlin {.good}
+@file:JvmName("StringUtils")
+
+package androidx.example
+
+fun String.foo() = // ...
+```
+
+NOTE This guideline may be ignored for libraries that only work in Kotlin (think
+Compose).
+
+## Testing Guidelines
+
+### [Do not Mock, AndroidX](do_not_mock.md)
+
+## Android Lint Guidelines
+
+### Suppression vs Baselines
+
+Lint sometimes flags false positives, even though it is safe to ignore these
+errors (for example WeakerAccess warnings when you are avoiding synthetic
+access). There may also be lint failures when your library is in the middle of a
+beta / rc / stable release, and cannot make the breaking changes needed to fix
+the root cause. There are two ways of ignoring lint errors:
+
+1.  Suppression - using `@SuppressLint` (for Java) or `@Suppress` annotations to
+    ignore the warning per call site, per method, or per file. *Note
+    `@SuppressLint` - Requires Android dependency*.
+2.  Baselines - allowlisting errors in a lint-baseline.xml file at the root of
+    the project directory.
+
+Where possible, you should use a **suppression annotation at the call site**.
+This helps ensure that you are only suppressing the *exact* failure, and this
+also keeps the failure visible so it can be fixed later on. Only use a baseline
+if you are in a Java library without Android dependencies, or when enabling a
+new lint check, and it is prohibitively expensive / not possible to fix the
+errors generated by enabling this lint check.
+
+To update a lint baseline (lint-baseline.xml) after you have fixed issues, add
+`-PupdateLintBaseline` to the end of your lint command. This will delete and
+then regenerate the baseline file.
+
+```shell
+./gradlew core:lintDebug -PupdateLintBaseline
+```
+
+## Metalava API Lint
+
+As well as Android Lint, which runs on all source code, Metalava will also run
+checks on the public API surface of each library. Similar to with Android Lint,
+there can sometimes be false positives / intended deviations from the API
+guidelines that Metalava will lint your API surface against. When this happens,
+you can suppress Metalava API lint issues using `@SuppressLint` (for Java) or
+`@Suppress` annotations. In cases where it is not possible, update Metalava's
+baseline with the `updateApiLintBaseline` task.
+
+```shell
+./gradlew core:updateApiLintBaseline
+```
+
+This will create/amend the `api_lint.ignore` file that lives in a library's
+`api` directory.
+
+## Build Output Guidelines
+
+In order to more easily identify the root cause of build failures, we want to
+keep the amount of output generated by a successful build to a minimum.
+Consequently, we track build output similarly to the way in which we track Lint
+warnings.
+
+### Invoking build output validation
+
+You can add `-Pandroidx.validateNoUnrecognizedMessages` to any other AndroidX
+gradlew command to enable validation of build output. For example:
+
+```shell
+/gradlew -Pandroidx.validateNoUnrecognizedMessages :help
+```
+
+### Exempting new build output messages
+
+Please avoid exempting new build output and instead fix or suppress the warnings
+themselves, because that will take effect not only on the build server but also
+in Android Studio, and will also run more quickly.
+
+If you cannot prevent the message from being generating and must exempt the
+message anyway, follow the instructions in the error:
+
+```shell
+$ ./gradlew -Pandroidx.validateNoUnrecognizedMessages :help
+
+Error: build_log_simplifier.py found 15 new messages found in /usr/local/google/workspace/aosp-androidx-git/out/dist/gradle.log.
+
+Please fix or suppress these new messages in the tool that generates them.
+If you cannot, then you can exempt them by doing:
+
+  1. cp /usr/local/google/workspace/aosp-androidx-git/out/dist/gradle.log.ignore /usr/local/google/workspace/aosp-androidx-git/frameworks/support/development/build_log_simplifier/messages.ignore
+  2. modify the new lines to be appropriately generalized
+```
+
+Each line in this exemptions file is a regular expressing matching one or more
+lines of output to be exempted. You may want to make these expressions as
+specific as possible to ensure that the addition of new, similar messages will
+also be detected (for example, discovering an existing warning in a new source
+file).
+
+## Behavior changes
+
+### Changes that affect API documentation
+
+Do not make behavior changes that require altering API documentation in a way
+that would break existing clients, even if such changes are technically binary
+compatible. For example, changing the meaning of a method's return value to
+return true rather than false in a given state would be considered a breaking
+change. Because this change is binary-compatible, it will not be caught by
+tooling and is effectively invisible to clients.
+
+Instead, add new methods and deprecate the existing ones if necessary, noting
+behavior changes in the deprecation message.
+
+### High-risk behavior changes
+
+Behavior changes that conform to documented API contracts but are highly complex
+and difficult to comprehensively test are considered high-risk and should be
+implemented using behavior flags. These changes may be flagged on initially, but
+the original behaviors must be preserved until the library enters release
+candidate stage and the behavior changes have been appropriately verified by
+integration testing against public pre-release
+revisions.
+
+It may be necessary to soft-revert a high-risk behavior change with only 24-hour
+notice, which should be achievable by flipping the behavior flag to off.
+
+```java
+[example code pending]
+```
+
+Avoid adding multiple high-risk changes during a feature cycle, as verifying the
+interaction of multiple feature flags leads to unnecessary complexity and
+exposes clients to high risk even when a single change is flagged off. Instead,
+wait until one high-risk change has landed in RC before moving on to the next.
+
+#### Testing
+
+Relevant tests should be run for the behavior change in both the on and off
+flagged states to prevent regressions.
+
+## Sample code in Kotlin modules
+
+### Background
+
+Public API can (and should!) have small corresponding code snippets that
+demonstrate functionality and usage of a particular API. These are often exposed
+inline in the documentation for the function / class - this causes consistency
+and correctness issues as this code is not compiled against, and the underlying
+implementation can easily change.
+
+KDoc (JavaDoc for Kotlin) supports a `@sample` tag, which allows referencing the
+body of a function from documentation. This means that code samples can be just
+written as a normal function, compiled and linted against, and reused from other
+modules such as tests! This allows for some guarantees on the correctness of a
+sample, and ensuring that it is always kept up to date.
+
+### Enforcement
+
+There are still some visibility issues here - it can be hard to tell if a
+function is a sample, and is used from public documentation - so as a result we
+have lint checks to ensure sample correctness.
+
+Primarily, there are three requirements when using sample links:
+
+1.  All functions linked to from a `@sample` KDoc tag must be annotated with
+    `@Sampled`
+2.  All sample functions annotated with `@Sampled` must be linked to from a
+    `@sample` KDoc tag
+3.  All sample functions must live inside a separate `samples` library
+    submodule - see the section on module configuration below for more
+    information.
+
+This enforces visibility guarantees, and make it easier to know that a sample is
+a sample. This also prevents orphaned samples that aren't used, and remain
+unmaintained and outdated.
+
+### Sample usage
+
+The follow demonstrates how to reference sample functions from public API. It is
+also recommended to reuse these samples in unit tests / integration tests / test
+apps / library demos where possible.
+
+**Public API:**
+
+```
+/*
+ * Fancy prints the given [string]
+ *
+ * @sample androidx.printer.samples.fancySample
+ */
+fun fancyPrint(str: String) ...
+```
+
+**Sample function:**
+
+```
+package androidx.printer.samples
+
+import androidx.printer.fancyPrint
+
+@Sampled
+fun fancySample() {
+   fancyPrint("Fancy!")
+}
+```
+
+**Generated documentation visible on d.android.com\***
+
+```
+fun fancyPrint(str: String)
+
+Fancy prints the given [string]
+
+<code>
+ import androidx.printer.fancyPrint
+
+ fancyPrint("Fancy!")
+<code>
+```
+
+\**still some improvements to be made to DAC side, such as syntax highlighting*
+
+### Module configuration
+
+The following module setups should be used for sample functions, and are
+enforced by lint:
+
+**Group-level samples**
+
+For library groups with strongly related samples that want to share code.
+
+Gradle project name: `:foo-library:samples`
+
+```
+foo-library/
+  foo-module/
+  bar-module/
+  samples/
+```
+
+**Per-module samples**
+
+For library groups with complex, relatively independent sub-libraries
+
+Gradle project name: `:foo-library:foo-module:samples`
+
+```
+foo-library/
+  foo-module/
+    samples/
+```
diff --git a/docs/benchmarking.md b/docs/benchmarking.md
new file mode 100644
index 0000000..cc38f78
--- /dev/null
+++ b/docs/benchmarking.md
@@ -0,0 +1,273 @@
+# Benchmarking in AndroidX
+
+[TOC]
+
+The public documentation at
+[d.android.com/benchmark](http://d.android.com/benchmark) explains how to use
+the library - this page focuses on specifics to writing libraries in the
+AndroidX repo, and our continuous testing / triage process.
+
+### Writing the benchmark
+
+Benchmarks are just regular instrumentation tests! Just use the
+[`BenchmarkRule`](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/benchmark/junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt)
+provided by the library:
+
+<section class="tabs">
+
+#### Kotlin {.new-tab}
+
+```kotlin
+@RunWith(AndroidJUnit4::class)
+class ViewBenchmark {
+    @get:Rule
+    val benchmarkRule = BenchmarkRule()
+
+    @Test
+    fun simpleViewInflate() {
+        val context = InstrumentationRegistry
+                .getInstrumentation().targetContext
+        val inflater = LayoutInflater.from(context)
+        val root = FrameLayout(context)
+
+        benchmarkRule.measure {
+            inflater.inflate(R.layout.test_simple_view, root, false)
+        }
+    }
+}
+```
+
+#### Java {.new-tab}
+
+```java
+@RunWith(AndroidJUnit4.class)
+public class ViewBenchmark {
+    @Rule
+    public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+
+    @Test
+    public void simpleViewInflate() {
+        Context context = InstrumentationRegistry
+                .getInstrumentation().getTargetContext();
+        final BenchmarkState state = mBenchmarkRule.getState();
+        LayoutInflater inflater = LayoutInflater.from(context);
+        FrameLayout root = new FrameLayout(context);
+
+        while (state.keepRunning()) {
+            inflater.inflate(R.layout.test_simple_view, root, false);
+        }
+    }
+}
+```
+
+</section>
+
+## Project structure
+
+As in the public documentation, benchmarks in the AndroidX repo are test-only
+library modules. Differences for AndroidX repo:
+
+1.  Module name must end with `-benchmark` in `settings.gradle`.
+2.  You do not need to apply the benchmark plugin (it's pulled in automatically
+    from source)
+
+### I'm lazy and want to start quickly
+
+Start by copying one of the following projects:
+
+*   [navigation-benchmark](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/navigation/benchmark/)
+*   [recyclerview-benchmark](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/recyclerview/recyclerview-benchmark/)
+
+### Compose
+
+Compose builds the benchmark from source, so usage matches the rest of the
+AndroidX project. See existing Compose benchmark projects:
+
+*   [Compose UI benchmarks](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/ui/integration-tests/benchmark/)
+*   [Compose Runtime benchmarks](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/compose/compose-runtime/compose-runtime-benchmark/)
+
+## Profiling
+
+### Command Line
+
+The benchmark library supports capturing profiling information - sampled and
+method - from the command line. Here's an example which runs the
+`androidx.ui.benchmark.test.CheckboxesInRowsBenchmark#draw` method with
+`MethodSampling` profiling:
+
+```
+./gradlew compose:integ:bench:cC \
+    -P android.testInstrumentationRunnerArguments.androidx.benchmark.profiling.mode=MethodSampling \
+    -P android.testInstrumentationRunnerArguments.class=androidx.ui.benchmark.test.CheckboxesInRowsBenchmark#draw
+```
+
+The command output will tell you where to look for the file on your host
+machine:
+
+```
+04:33:49 I/Benchmark: Benchmark report files generated at
+/androidx-master-dev/out/ui/ui/integration-tests/benchmark/build/outputs/connected_android_test_additional_output
+```
+
+To inspect the captured trace, open the appropriate `*.trace` file in that
+directory with Android Studio, using `File > Open`.
+
+For more information on the `MethodSampling` and `MethodTracing` profiling
+modes, see the
+[Studio Profiler configuration docs](https://developer.android.com/studio/profile/cpu-profiler#configurations),
+specifically Java Sampled Profiling, and Java Method Tracing.
+
+![Sample flame chart](benchmarking_images/profiling_flame_chart.png "Sample flame chart")
+
+### Advanced: Simpleperf Method Sampling
+
+[Simpleperf](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/)
+offers more accurate profiling for apps than standard method sampling, due to
+lower overhead (as well as C++ profiling support). Simpleperf support will be
+simplified and improved over time.
+
+[Simpleperf app profiling docs](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md).
+
+#### Device
+
+Get an API 28+ device (Or a rooted API 27 device). The rest of this section is
+about *why* those constraints exist, skip if not interested.
+
+Simpleperf has restrictions about where it can be used - Jetpack Benchmark will
+only support API 28+ for now, due to
+[platform/simpleperf constraints](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md#prepare-an-android-application)
+(see last subsection titled "If you want to profile Java code"). Summary is:
+
+-   <=23 (M): Unsupported for Java code.
+
+-   24-25 (N): Requires compiled Java code. We haven't investigated support.
+
+-   26 (O): Requires compiled Java code, and wrapper script. We haven't
+    investigated support.
+
+-   27 (P): Can profile all Java code, but requires `userdebug`/rooted device
+
+-   \>=28 (Q): Can profile all Java code, requires profileable (or
+    `userdebug`/rooted device)
+
+We aren't planning to support profiling debuggable APK builds, since they're
+misleading for profiling.
+
+#### Initial setup
+
+Currently, we rely on Python scripts built by the simpleperf team. We can
+eventually build this into the benchmark library / gradle plugin. Download the
+scripts from AOSP:
+
+```
+# copying to somewhere outside of the androidx repo
+git clone https://android.googlesource.com/platform/system/extras ~/simpleperf
+```
+
+Next configure your path to ensure the ADB that the scripts will use matches the
+androidx tools:
+
+```
+export PATH=$PATH:<path/to/androidx>/prebuilts/fullsdk-<linux or darwin>/platform-tools
+```
+
+Now, setup your device for simpleperf:
+
+```
+~/simpleperf/simpleperf/scripts/api_profiler.py prepare --max-sample-rate 10000000
+```
+
+#### Build and Run, Option 1: Studio (slightly recommended)
+
+Running from Studio is simpler, since you don't have to manually install and run
+the APKs, avoiding Gradle.
+
+Add the following to the benchmark module's build.gradle:
+
+```
+android {
+    defaultConfig {
+        // DO NOT COMMIT!!
+        testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'MethodSamplingSimpleperf'
+        // Optional: Control freq / duration.
+        testInstrumentationRunnerArgument 'androidx.benchmark.profiler.sampleFrequency', '1000000'
+        testInstrumentationRunnerArgument 'androidx.benchmark.profiler.sampleDurationSeconds', '5'
+    }
+}
+```
+
+And run the test or tests you'd like to measure from within Studio.
+
+#### Build and Run, Option 2: Command Line
+
+**Note - this will be significantly simplified in the future**
+
+Since we're not using AGP to pull the files yet, we can't invoke the benchmark
+through Gradle, because Gradle uninstalls after each test run. Instead, let's
+just build and run manually:
+
+```
+./gradlew compose:integration-tests:benchmark:assembleReleaseAndroidTest
+
+adb install -r ../../../out/ui/compose/integration-tests/benchmark/build/outputs/apk/androidTest/release/benchmark-release-androidTest.apk
+
+# run the test (can copy this line from Studio console, when running a benchmark)
+adb shell am instrument -w -m --no-window-animation -e androidx.benchmark.profiling.mode MethodSamplingSimpleperf -e debug false -e class 'androidx.ui.benchmark.test.CheckboxesInRowsBenchmark#toggleCheckbox_draw' androidx.ui.benchmark.test/androidx.benchmark.junit4.AndroidBenchmarkRunner
+```
+
+#### Pull and open the trace
+
+```
+# move the files to host
+# (Note: removes files from device)
+~/simpleperf/simpleperf/scripts/api_profiler.py collect -p androidx.ui.benchmark.test -o ~/simpleperf/results
+
+# create/open the HTML report
+~/simpleperf/simpleperf/scripts/report_html.py -i ~/simpleperf/results/CheckboxesInRowsBenchmark_toggleCheckbox_draw\[1\].data
+```
+
+### Advanced: Studio Profiling
+
+Profiling for allocations and simpleperf profiling requires Studio to capture.
+
+Studio profiling tools require `debuggable=true`. First, temporarily override it
+in your benchmark's `androidTest/AndroidManifest.xml`.
+
+Next choose which profiling you want to do: Allocation, or Sampled (SimplePerf)
+
+`ConnectedAllocation` will help you measure the allocations in a single run of a
+benchmark loop, after warmup.
+
+`ConnectedSampled` will help you capture sampled profiling, but with the more
+detailed / accurate Simpleperf sampling.
+
+Set the profiling type in your benchmark module's `build.gradle`:
+
+```
+android {
+    defaultConfig {
+        // Local only, don't commit this!
+        testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'ConnectedAllocation'
+    }
+}
+```
+
+Run `File > Sync Project with Gradle Files`, or sync if Studio asks you. Now any
+benchmark runs in that project will permit debuggable, and pause before and
+after the test, to allow you to connect a profiler and start recording, and then
+stop recording.
+
+#### Running and Profiling
+
+After the benchmark test starts, you have about 20 seconds to connect the
+profiler:
+
+1.  Click the profiler tab at the bottom
+1.  Click the plus button in the top left, `<device name>`, `<process name>`
+1.  Next step depends on which you intend to capture
+
+#### Allocations
+
+Click the memory section, and right click the window, and select `Record
+allocations`. Approximately 20 seconds later, right click again and select `Stop
+recording`.
diff --git a/docs/benchmarking_images/filter_build.png b/docs/benchmarking_images/filter_build.png
new file mode 100644
index 0000000..f4d15d4
--- /dev/null
+++ b/docs/benchmarking_images/filter_build.png
Binary files differ
diff --git a/docs/benchmarking_images/filter_initial.png b/docs/benchmarking_images/filter_initial.png
new file mode 100644
index 0000000..61bdc30
--- /dev/null
+++ b/docs/benchmarking_images/filter_initial.png
Binary files differ
diff --git a/docs/benchmarking_images/filter_test.png b/docs/benchmarking_images/filter_test.png
new file mode 100644
index 0000000..9a8a0ee
--- /dev/null
+++ b/docs/benchmarking_images/filter_test.png
Binary files differ
diff --git a/docs/benchmarking_images/profiling_flame_chart.png b/docs/benchmarking_images/profiling_flame_chart.png
new file mode 100644
index 0000000..33cd76f
--- /dev/null
+++ b/docs/benchmarking_images/profiling_flame_chart.png
Binary files differ
diff --git a/docs/benchmarking_images/result_plot.png b/docs/benchmarking_images/result_plot.png
new file mode 100644
index 0000000..d0b878e
--- /dev/null
+++ b/docs/benchmarking_images/result_plot.png
Binary files differ
diff --git a/docs/benchmarking_images/triage_bug.png b/docs/benchmarking_images/triage_bug.png
new file mode 100644
index 0000000..816122c
--- /dev/null
+++ b/docs/benchmarking_images/triage_bug.png
Binary files differ
diff --git a/docs/benchmarking_images/triage_cl_list.png b/docs/benchmarking_images/triage_cl_list.png
new file mode 100644
index 0000000..d65a7e2
--- /dev/null
+++ b/docs/benchmarking_images/triage_cl_list.png
Binary files differ
diff --git a/docs/benchmarking_images/triage_complete.png b/docs/benchmarking_images/triage_complete.png
new file mode 100644
index 0000000..3a04763
--- /dev/null
+++ b/docs/benchmarking_images/triage_complete.png
Binary files differ
diff --git a/docs/benchmarking_images/triage_graph.png b/docs/benchmarking_images/triage_graph.png
new file mode 100644
index 0000000..90defbc
--- /dev/null
+++ b/docs/benchmarking_images/triage_graph.png
Binary files differ
diff --git a/docs/benchmarking_images/triage_initial.png b/docs/benchmarking_images/triage_initial.png
new file mode 100644
index 0000000..ea0d033
--- /dev/null
+++ b/docs/benchmarking_images/triage_initial.png
Binary files differ
diff --git a/docs/benchmarking_images/triage_regression.png b/docs/benchmarking_images/triage_regression.png
new file mode 100644
index 0000000..bed4b5f
--- /dev/null
+++ b/docs/benchmarking_images/triage_regression.png
Binary files differ
diff --git a/docs/benchmarking_images/triage_word_cloud.png b/docs/benchmarking_images/triage_word_cloud.png
new file mode 100644
index 0000000..35dbe43
--- /dev/null
+++ b/docs/benchmarking_images/triage_word_cloud.png
Binary files differ
diff --git a/docs/branching.md b/docs/branching.md
new file mode 100644
index 0000000..8b5cd06
--- /dev/null
+++ b/docs/branching.md
@@ -0,0 +1,40 @@
+# AndroidX Branch Workflow
+
+[TOC]
+
+## Single Development Branch [androidx-master-dev]
+
+All feature development occurs in the public AndroidX master dev branch of the
+Android Open Source Project: `androidx-master-dev`. This branch serves as the
+central location and source of truth for all AndroidX library source code. All
+alpha and beta version development, builds, and releases will be done ONLY in
+this branch.
+
+## Release Branches [androidx-\<feature\>-release]
+
+When a library updates to rc (release-candidate) or stable, that library version
+will be snapped over to that library’s release branch. If that release branch
+doesn’t exist, then a release branch will be created for that library, snapped
+from androidx-master-dev at the commit that changed the library to an rc or
+stable version.
+
+Release branches have the following properties:
+
+*   A release branch will contain rc or stable versions of libraries.
+*   Release branches are internal branches.
+*   Release branches can **ONLY** be changed through
+    cherry-picks
+*   Bug-fixes and updates to that rc or stable version will need to be
+    individually cherry-picked
+*   No alpha or beta versions will exist in a release branch.
+*   Toolchain and other library wide changes to androidx-master-dev will be
+    synced to each release branch.
+*   Release branches will have the naming format
+    `androidx-<feature-name>-release`
+*   Release branches will be re-snapped from `androidx-master-dev` for each new
+    minor version release (for example, releasing 2.2.0-rc01 after 2.1.0)
+
+## Platform Developement and AndroidX [androidx-platform-dev]
+
+Platform specific development is done using our INTERNAL platform development
+branch `androidx-platform-dev`.
diff --git a/docs/branching_images/cs_branch_switcher.png b/docs/branching_images/cs_branch_switcher.png
new file mode 100644
index 0000000..660a877
--- /dev/null
+++ b/docs/branching_images/cs_branch_switcher.png
Binary files differ
diff --git a/docs/branching_images/cs_change.png b/docs/branching_images/cs_change.png
new file mode 100644
index 0000000..15c7475
--- /dev/null
+++ b/docs/branching_images/cs_change.png
Binary files differ
diff --git a/docs/branching_images/cs_editor.png b/docs/branching_images/cs_editor.png
new file mode 100644
index 0000000..72ec1ee
--- /dev/null
+++ b/docs/branching_images/cs_editor.png
Binary files differ
diff --git a/docs/branching_images/jetpack_branch_workflow.png b/docs/branching_images/jetpack_branch_workflow.png
new file mode 100644
index 0000000..ca4b094
--- /dev/null
+++ b/docs/branching_images/jetpack_branch_workflow.png
Binary files differ
diff --git a/docs/branching_images/release_branch_diagram.png b/docs/branching_images/release_branch_diagram.png
new file mode 100644
index 0000000..7b54025
--- /dev/null
+++ b/docs/branching_images/release_branch_diagram.png
Binary files differ
diff --git a/docs/do_not_mock.md b/docs/do_not_mock.md
new file mode 100644
index 0000000..d614001
--- /dev/null
+++ b/docs/do_not_mock.md
@@ -0,0 +1,123 @@
+# Do Not Mock, AndroidX
+
+All APIs created in AndroidX **must have a testing story**: how developers
+should write tests for their code that relies on a library, this story should
+not be "use mockito to mock class `Foo`". Your goal as API owner is to **create
+better alternatives** to mocking.
+
+## Why can't I suggest mocks as testing strategy?
+
+Frequently mocks don't follow guarantees outlined in the API they mock. That
+leads to:
+
+*   Significant difference in the behavior that diminishes test value.
+*   Brittle tests, that make hard to evolve both apps and libraries, because new
+    code may start to rely on the guarantees broken in a mock. Let's take a look
+    at a simplified example. So, let's say you mocked a bundle and getString in
+    it:
+
+    ```java
+    Bundle mock = mock(Bundle.class);
+    when(mock.getString("key")).thenReturn("result");
+    ```
+
+    But you don't mock it to simply call `getString()` in your test. A goal is
+    not to test a mock, the goal is always to test your app code, so your app
+    code always interacts with a mock in some way:
+
+    ```java
+    Bundle bundle = mock(Bundle.class);
+    when(mock.getString("key")).thenReturn("result");
+    mycomponent.consume(bundle)
+    ```
+
+    Originally the test worked fine, but over time `component.consume` is
+    evolving, and, for example, it may start to call `containsKey` on the given
+    bundle. But our test passes a mock that don't expect such call and, boom,
+    test is broken. However, component code is completely valid and has nothing
+    to do with the broken test. We observed a lot of issues like that during
+    updates of android SDK and AndroidX libraries to newer versions internally
+    at google. Suggesting to mock our own components is shooting ourselves in
+    the foot, it will make adoption of newer version of libraries even slower.
+
+*   Messy tests. It always starts with simple mock with one method, but then
+    this mock grows with the project, and as a result test code has sub-optimal
+    half-baked class implementation of on top of the mock.
+
+## But it is ok to mock interfaces, right?
+
+It depends. There are interfaces that don't imply any behavior guarantees and
+they are ok to be mocked. However, **not all** interfaces are like that: for
+example, `Map` is an interface but it has a lot of contracts required from
+correct implementation. Examples of interfaces that are ok to mock are callback
+interfaces in general, for example: `View.OnClickListener`, `Runnable`.
+
+## What about spying?
+
+Spying on these classes is banned as well - mockito spies permit stubbing of
+methods just like mocks do, and interaction verification is brittle and
+unnecessary for these classes. Rather than verifying an interaction with a
+class, developers should observe the result of an interaction - the effect of a
+task submitted to an `Executor`, or the presence of a fragment added to your
+layout. If an API in your library misses a way to have such checks, you should
+add methods to do that. If you think it is dangerous to open such methods in the
+main surface of your library, consult with
+[API council](https://sites.google.com/corp/google.com/android-api-council), it
+may have seen similar patterns before. For example, one of the possible ways to
+resolve such issue can be adding test artifact with special capabilities. So
+`fragment-testing` module was created to drive lifecycle of Fragment and ease
+interaction with fragments in tests.
+
+## Avoid mockito in your own tests.
+
+One of the things that would help you to identify if your library is testable
+without mockito is not using mockito yourself. Yes, historically we heavily
+relied on mockito ourselves and old tests are not rewritten, but new tests
+shouldn't follow up that and should take as an example good citizens, for
+example, `-ktx` modules. These modules don't rely on mockito and have concise
+expressive tests.
+
+One of the popular and legit patterns for mockito usage were tests that verify
+that a simple callback-like interface receives correct parameters.
+
+```java
+class MyApi {
+   interface Callback {
+     void onFoo(Value value);
+  }
+  void foo() { … }
+  void registerFooCallback(Callback callback) {...}
+}
+```
+
+In api like the one above, in java 7 tests for value received in `Callback`
+tended to become very wordy without mockito. But now in your tests you can use
+Kotlin and test will be as short as with mockito:
+
+```kotlin
+fun test() {
+    var receivedValue = null
+    myApi.registerCallback { value -> receivedValue = value }
+    myApi.foo()
+   // verify receivedValue
+}
+```
+
+## Don't compromise in API to enable mockito
+
+Mockito on android
+[had an issue](https://github.com/mockito/mockito/issues/1173) with mocking
+final classes. Moreover, internally at google this feature is disabled even for
+non-android code. So you may hear complaints that some of your classes are not
+mockable, however **It is not a reason for open up a class for extension**. What
+you should instead is verify that is possible to write the same test without
+mocking, if not, again you should **provide better alternative in your API**.
+
+## How do I approach testing story for my API?
+
+Best way is to step into developer's shoes and write a sample app that is a
+showcase for your API, then go to the next step - test that code also. If you
+are able to implement tests for your demo app, then users of your API will also
+be able to implement tests for functionalities where your API is also used.
+
+## ~~Use @DoNotMock on most of your APIs ~~(Not available yet)
diff --git a/docs/faq.md b/docs/faq.md
new file mode 100644
index 0000000..786cc23
--- /dev/null
+++ b/docs/faq.md
@@ -0,0 +1,171 @@
+# FAQ
+
+[TOC]
+
+## General FAQ
+
+### What is AndroidX?
+
+The Android Extension (AndroidX) Libraries provide functionality that extends
+the capabilities of the Android platform. These libraries, which ship separately
+from the Android OS, focus on improving the experience of developing apps
+through broad OS- and device-level compatibility, high-level abstractions to
+simplify and unify platform features, and other new features that target
+developer pain points. To find out more about AndroidX, see the public
+documentation on developer.android.com.
+
+### Why did we move to AndroidX?
+
+Please read our
+[blog post](https://android-developers.googleblog.com/2018/05/hello-world-androidx.html)
+about our migration to AndroidX.
+
+### What happened to the Support Library?
+
+As part of the Jetpack effort to improve developer experience on Android, the
+Support Library team undertook a massive refactoring project. Over the course of
+2017 and 2018, we streamlined and enforced consistency in our packaging,
+developed new policies around vesioning and releasing, and developed tools to
+make it easy for developers to migrate.
+
+### Will there be any more updates to Support Library?
+
+No, Revision 28.0.0 of the Support Library, which launched as stable in
+September 2018, was the last feature release in the android.support package.
+There will be no further releases under Support Library packaging.
+
+### How is AndroidX related to Jetpack?
+
+They are the same thing! In a sentence, AndroidX is the packaging and
+internally-facing development project for all components in Jetpack. Jetpack is
+the external branding for libraries within AndroidX.
+
+In more detail, Jetpack is the external branding for the set of components,
+tools, and guidance that improve the developer experience on Android. AndroidX
+is the open-source development project that defines the workflow, versioning,
+and release policies for ALL libraries included in Jetpack. All libraries within
+the androidx Java package follow a consistent set of API design guidelines,
+conform to SemVer and alpha/beta revision cycles, and use the Android issue
+tracker for bugs and feature requests.
+
+### What AndroidX library versions have been officially released?
+
+You can see all publicly released versions on the interactive
+[Google Maven page](https://dl.google.com/dl/android/maven2/index.html).
+
+### How do I jetify something?
+
+The Standalone Jetifier documentation and download link can be found
+[here](https://developer.android.com/studio/command-line/jetifier), under the
+Android Studio DAC.
+
+### How do I update my library version?
+
+See the steps specified on the version page
+[here](versioning.md#how-to-update-your-version).
+
+### How do I test my change in a separate Android Studio project?
+
+If you're working on a new feature or bug fix in AndroidX, you may want to test
+your changes against another project to verify that the change makes sense in a
+real-world context or that a bug's specific repro case has been fixed.
+
+If you need to be absolutely sure that your test will exactly emulate the
+developer's experience, you can repeatedly build the AndroidX archive and
+rebuild your application. In this case, you will need to create a local build of
+AndroidX's local Maven repository artifact and install it in your Android SDK
+path.
+
+First, use the `createArchive` Gradle task to generate the local Maven
+repository artifact:
+
+```shell
+# Creates <path-to-checkout>/out/dist/sdk-repo-linux-m2repository-##.zip
+./gradlew createArchive
+```
+
+Next, take the ZIP output from this task and extract the contents to the Android
+SDK path that you are using for your alternate (non-AndroidX) version of Android
+Studio. For example, you may be using `~/Android/SDK/extras` if you are using
+the default Android Studio SDK for app development or
+`prebuilts/fullsdk-linux/extras` if you are using fullsdk for platform
+development.
+
+```shell
+# Creates or overwrites android/m2repository
+cd <path-to-sdk>/extras
+unzip <path-to-checkout>/out/dist/top-of-tree-m2repository-##.zip
+```
+
+Finally, in the dependencies section of your standalone project's `build.gradle`
+file, add or update the `compile` entries to reflect the AndroidX modules that
+you would like to test:
+
+```
+dependencies {
+    ...
+    compile "com.android.support:appcompat-v7:26.0.0-SNAPSHOT"
+}
+```
+
+## Version FAQ {#version}
+
+### How are changes in dependency versions propagated?
+
+If you declare `api(project(":depGroupId"))` in your `build.gradle`, then the
+version change will occur automatically. While convienent, be intentional when
+doing so because this causes your library to have a direct dependency on the
+version in development.
+
+If you declare `api("androidx.depGroupId:depArtifactId:1.0.0")`, then the
+version change will need to be done manually and intentionally. This is
+considered best practice.
+
+### How does a library begin work on a new Minor version?
+
+Set the version to the next minor version, as an alpha.
+
+### How does a library ship an API reference documentation bugfix?
+
+Developers obtain API reference documentation from two sources -- HTML docs on
+[d.android.com](https://d.android.com), which are generated from library release
+artifacts, and Javadoc from source JARs on Google Maven.
+
+As a result, documentation bug fixes should be held with other fixes until they
+can go through a normal release cycle. Critical (e.g. P0) documentation issues
+**may** result in a [bugfix](loaf.md#bugfix) release independent of other fixes.
+
+### When does an alpha ship?
+
+For public releases, an alpha ships when the library lead believes it is ready.
+Generally, these occur during the batched bi-weekly (every 2 weeks) release
+because all tip-of-tree dependencies will need to be released too.
+
+### Are there restrictions on when or how often an alpha can ship?
+
+Nope.
+
+### Can Alpha work (ex. for the next Minor release) occur in the primary development branch during Beta API lockdown?
+
+No. This is by design. Focus should be spent on improving the Beta version and
+adding documentation/samples/blog posts for usage!
+
+### Is there an API freeze window between Alpha and Beta while API surface is reviewed and tests are added, but before the Beta is released?
+
+Yes. If any new APIs are added in this window, the beta release will be blocked
+until API review is complete and addressed.
+
+### How often can a Beta release?
+
+As often as needed, however, releases outside of the bi-weekly (every 2 weeks)
+release will need to get approval from the TPM (nickanthony@).
+
+### What are the requirements for moving from Alpha to Beta?
+
+See the [Beta section of Versioning guidelines](versioning.md?#beta) for
+pre-release cycle transition requirements.
+
+### What are the requirements for a Beta launch?
+
+See the [Beta section of Versioning guidelines](versioning.md?#beta) for
+pre-release cycle transition requirements.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..4a29386
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,47 @@
+# What is Jetpack?
+
+## Jetpack Ethos
+
+To create recommended components, tools, and guidance that makes it quick and
+easy to build great Android apps, including pieces both from Google and from
+trusted OSS sources.
+
+## Team Mission
+
+To improve the Android developer experience by providing architectural guidance,
+addressing common pain points, and simplifying the app development process
+through broad compatibility across Android versions and elimination of
+boilerplate code so developers can focus on what makes their app special.
+
+## What is `androidx`?
+
+Artifacts within the `androidx` package comprise the libraries of
+[Android Jetpack](https://developer.android.com/jetpack).
+
+Libraries in the `androidx` package provide functionality that extends the
+capabilities of the Android platform. These libraries, which ship separately
+from the Android OS, focus on improving the experience of developing apps
+through broad OS- and device-level compatibility, high-level abstractions to
+simplify and unify platform features, and other new features that target
+developer pain points.
+
+## What happened to the Support Library?
+
+As part of the Jetpack project to improve developer experience on Android, the
+Support Library team undertook a massive refactoring project. Over the course of
+2017 and 2018, we streamlined and enforced consistency in our packaging,
+developed new policies around versioning and release, and developed tools to
+make it easy for developers to migrate.
+
+Revision 28.0.0 of the Support Library, which launched as stable in September
+2018, was the last feature release in the `android.support` package. There will
+be no further releases under Support Library packaging.
+
+## Quick links
+
+### Filing an issue
+
+Have a bug or feature request? Please check our
+[public Issue Tracker component](http://issuetracker.google.com/issues/new?component=192731&template=842428)
+for duplicates first, then file against the appropriate sub-component according
+to the library package or infrastructure system.
diff --git a/docs/issue_tracking.md b/docs/issue_tracking.md
new file mode 100644
index 0000000..3413ab2
--- /dev/null
+++ b/docs/issue_tracking.md
@@ -0,0 +1,101 @@
+# Issue Lifecycle and Reporting Guidelines
+
+[TOC]
+
+## Issue tracker
+
+The public-facing issue tracker URL is
+[issuetracker.google.com](https://issuetracker.google.com). If you visit this
+URL from a corp account, it will immediately redirect you to the internal-facing
+issue tracker URL. Make sure that any links you paste publicly have the correct
+public-facing URL.
+
+The top-level Jetpack component is
+[`Android Public Tracker > App Development > Jetpack (androidx)`](https://issuetracker.google.com/components/192731/manage#basic).
+
+## Reporting guidelines
+
+Issue Tracker isn't a developer support forum. For support information, consider
+[StackOverflow](http://stackoverflow.com).
+
+Support for Google apps is through
+[Google's support site](http://support.google.com/). Support for third-party
+apps is provided by the app's developer, for example through the contact
+information provided on Google Play.
+
+1.  Search for your bug to see if anyone has already reported it. Don't forget
+    to search for all issues, not just open ones, as your issue might already
+    have been reported and closed. To help you find the most popular results,
+    sort the result by number of stars.
+
+1.  If you find your issue and it's important to you, star it! The number of
+    stars on a bug helps us know which bugs are most important to fix.
+
+1.  If no one has reported your bug, file the bug. First, browse for the correct
+    component -- typically this has a 1:1 correspondence with Maven group ID --
+    and fill out the provided template.
+
+1.  Include as much information in the bug as you can, following the
+    instructions for the bug queue that you're targeting. A bug that simply says
+    something isn't working doesn't help much, and will probably be closed
+    without any action. The amount of detail that you provide, such as a minimal
+    sample project, log files, repro steps, and even a patch set, helps us
+    address your issue.
+
+## Status definitions
+
+| Status   | Description                                                       |
+| -------- | ----------------------------------------------------------------- |
+| New      | The default for public bugs. Waiting for someone to validate,     |
+:          : reproduce, or otherwise confirm that this is actionable.          :
+| Assigned | Pending action from the assignee. May be reassigned.              |
+| Accepted | Actively being worked on by the assignee. Do not reassign.        |
+| Fixed    | Fixed in the development branch. Do not re-open unless the fix is |
+:          : reverted.                                                         :
+| WontFix  | Covers all the reasons we chose to close the issue without taking |
+:          : action (can't repro, working as intended, obsolete).              :
+
+## Priority criteria and SLOs
+
+| Priority | Criteria                       | Resolution time                |
+| -------- | ------------------------------ | ------------------------------ |
+| P0       | This priority is limited to    | Less than 1 day. Don't go home |
+:          : service outages, blocking      : until this is fixed.           :
+:          : issues, or other types of work :                                :
+:          : stoppage such as issues on the :                                :
+:          : Platform chase list requiring  :                                :
+:          : immediate attention.           :                                :
+| P1       | This priority is limited to    | Within the next 7 days         |
+:          : work that requires rapid       :                                :
+:          : resolution, but can be dealt   :                                :
+:          : with in a slightly longer time :                                :
+:          : window than P0.                :                                :
+| P2       | Won't ship without this.       | Within the current release     |
+| P3       | Would rather not ship without  | Less than 365 days             |
+:          : this, but would decide case by :                                :
+:          : case.                          :                                :
+| P4       | Issue has not yet been         | N/A (must triage in under 14   |
+:          : prioritized (default as of Feb : days)                          :
+:          : 2013).                         :                                :
+
+## Issue lifecycle
+
+1.  When an issue is reported, it is set to **Assigned** status for default
+    assignee (typically the [library owner](owners.md)) with a priority of
+    **P4**.
+    *   Some components have an empty default assignee and will be manually
+        assigned by the [triage cop](triage_cop.md)
+1.  Once an issue has been triaged by the assignee, its priority will be raised
+    from **P4** according to severity.
+1.  The issue may still be reassigned at this point.
+    [Bug bounty](onboarding.md#bug-bounty) issues are likely to change
+    assignees.
+1.  A status of **Accepted** means the assignee is actively working on the
+    issue.
+1.  A status of **Fixed** means that the issue has been resolved in the
+    development branch. Please note that it may take some time for the fix to
+    propagate into various release channels (internal repositories, Google
+    Maven, etc.). **Do not** re-open an issue because the fix has not yet
+    propagated into a specific release channel. **Do not** re-open an issue that
+    has been fixed unless the fix was reverted or the exact reported issue is
+    still occurring.
diff --git a/docs/LINT.md b/docs/lint_guide.md
similarity index 86%
rename from docs/LINT.md
rename to docs/lint_guide.md
index 5916b6d..5e40600 100644
--- a/docs/LINT.md
+++ b/docs/lint_guide.md
@@ -1,5 +1,7 @@
 # Adding custom Lint checks
 
+[TOC]
+
 ## Getting started
 
 Lint is a static analysis tool that checks Android project source files. Lint
@@ -47,8 +49,8 @@
 }
 
 dependencies {
-    // compileOnly because we use lintChecks and it doesn't allow other types of deps
-    // this ugly hack exists because of b/63873667
+    // compileOnly because lint runtime is provided when checks are run
+    // Use latest lint for running from IDE to make sure checks always run
     if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
         compileOnly LINT_API_LATEST
     } else {
@@ -81,7 +83,7 @@
 
 Your new module will need to have a registry that contains a list of all of the
 checks to be performed on the library. There is an
-[`IssueRegistry`](https://cs.android.com/android/platform/superproject/+/master:tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java)
+[`IssueRegistry`](https://cs.android.com/android/platform/superproject/+/master:tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java;l=47)
 class provided by the tools team. Extend this class into your own
 `IssueRegistry` class, and provide it with the issues in the module.
 
@@ -103,7 +105,7 @@
 `CURRENT_API` is defined by the Lint API version against which your project is
 compiled, as defined in the module's `build.gradle` file. Jetpack Lint modules
 should compile using Lint API version 3.3 defined in
-[Dependencies.kt](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt;l=84).
+[Dependencies.kt](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt;l=176).
 
 We guarantee that our Lint checks work with versions 3.3-3.6 by running our
 tests with both versions 3.3 and 3.6. For newer versions of Android Studio (and
@@ -268,7 +270,7 @@
 These are Lint checks that will apply to source code files -- primarily Java and
 Kotlin, but can also be used for other similar file types. All code detectors
 that analyze Java or Kotlin files should implement the
-[SourceCodeScanner](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/SourceCodeScanner.kt).
+[SourceCodeScanner](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/SourceCodeScanner.kt).
 
 ### API surface
 
@@ -401,7 +403,7 @@
 These are Lint rules that will apply to resource files including `anim`,
 `layout`, `values`, etc. Lint rules being applied to resource files should
 extend
-[`ResourceXmlDetector`](https://cs.android.com/android/platform/superproject/+/master:tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java).
+[`ResourceXmlDetector`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java).
 The `Detector` must define the issue it is going to detect, most commonly as a
 static variable of the class.
 
@@ -436,7 +438,7 @@
 #### appliesTo
 
 This determines the
-[ResourceFolderType](https://cs.android.com/android/platform/superproject/+/master:tools/base/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java)
+[ResourceFolderType](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java)
 that the check will run against.
 
 ```kotlin
@@ -503,7 +505,7 @@
 ```
 
 Next, you must test the `Detector` class. The Tools team provides a
-[`LintDetectorTest`](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java)
+[`LintDetectorTest`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java)
 class that should be extended. Override `getDetector()` to return an instance of
 the `Detector` class:
 
@@ -517,13 +519,13 @@
 getIssues(): MutableList<Issue> = mutableListOf(MyLibraryDetector.ISSUE)
 ```
 
-[`LintDetectorTest`](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java)
+[`LintDetectorTest`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java)
 provides a `lint()` method that returns a
-[`TestLintTask`](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintTask.java).
+[`TestLintTask`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintTask.java).
 `TestLintTask` is a builder class for setting up lint tests. Call the `files()`
 method and provide an `.xml` test file, along with a file stub. After completing
 the set up, call `run()` which returns a
-[`TestLintResult`](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintResult.kt).
+[`TestLintResult`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintResult.kt).
 `TestLintResult` provides methods for checking the outcome of the provided
 `TestLintTask`. `ExpectClean()` means the output is expected to be clean because
 the lint rule was followed. `Expect()` takes a string literal of the expected
@@ -536,13 +538,13 @@
 ## Android manifest detector
 
 Lint checks targeting `AndroidManifest.xml` files should implement the
-[XmlScanner](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlScanner.kt)
+[XmlScanner](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlScanner.kt)
 and define target scope in issues as `Scope.MANIFEST`
 
 ## Gradle detector
 
 Lint checks targeting Gradle configuration files should implement the
-[GradleScanner](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/GradleScanner.kt)
+[GradleScanner](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/GradleScanner.kt)
 and define target scope in issues as `Scope.GRADLE_SCOPE`
 
 ### API surface
@@ -599,7 +601,7 @@
 
 Sometimes it is necessary to implement multiple different scanners in a Lint
 detector. For example, the
-[Unused Resource](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java)
+[Unused Resource](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java)
 Lint check implements an XML and SourceCode Scanner in order to determine if
 resources defined in XML files are ever references in the Java/Kotlin source
 code.
@@ -621,16 +623,16 @@
 
 ## Useful classes/packages
 
-### [`SdkConstants`](https://cs.android.com/android/platform/superproject/+/master:tools/base/common/src/main/java/com/android/SdkConstants.java;l=38?q=SdkCon)
+### [`SdkConstants`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:common/src/main/java/com/android/SdkConstants.java)
 
 Contains most of the canonical names for android core library classes, as well
 as XML tag names.
 
 ## Helpful links
 
-[Studio Lint Rules](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks)
+[Studio Lint Rules](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/)
 
-[Lint Detectors and Scanners Source Code](https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api)
+[Lint Detectors and Scanners Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/)
 
 [Creating Custom Link Checks (external)](https://twitter.com/alexjlockwood/status/1176675045281693696)
 
@@ -644,4 +646,4 @@
 
 [ADS 19 Presentation by Alan & Rahul](https://www.youtube.com/watch?v=jCmJWOkjbM0)
 
-[META-INF vs Manifest](https://groups.google.com/forum/#!msg/lint-dev/z3NYazgEIFQ/hbXDMYp5AwAJ)
\ No newline at end of file
+[META-INF vs Manifest](https://groups.google.com/forum/#!msg/lint-dev/z3NYazgEIFQ/hbXDMYp5AwAJ)
diff --git a/docs/manual_prebuilts_dance.md b/docs/manual_prebuilts_dance.md
new file mode 100644
index 0000000..d06e86f
--- /dev/null
+++ b/docs/manual_prebuilts_dance.md
@@ -0,0 +1,22 @@
+# The Manual Prebuilts Dance™
+
+NOTE There is also a [script](releasing.md#the-prebuilts-dance™) that automates
+this step.
+
+Public-facing Jetpack library docs are built from prebuilts to reconcile our
+monolithic docs update process with our independently-versioned library release
+process.
+
+Submit the following changes in the same Gerrit topic so that they merge in the
+same build ID:
+
+1.  Commit your release artifact to the AndroidX AOSP checkout's local Maven
+    repository under `prebuilts/androidx/internal`.
+
+2.  Update the version for your library in the public docs configuration
+    ([docs-public/build.gradle](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:docs-public/build.gradle)).
+    If this is the first time that your library is being published, you will
+    need to add a new entry.
+
+Once both changes are, make sure to note the build ID where they landed. You
+will need to put this in your release request bug for Docs team.
diff --git a/docs/onboarding.md b/docs/onboarding.md
new file mode 100644
index 0000000..5bd331b
--- /dev/null
+++ b/docs/onboarding.md
@@ -0,0 +1,790 @@
+# Getting started
+
+[TOC]
+
+This page describes how to set up your workstation to check out source code,
+make simple changes in Android Studio, and upload commits to Gerrit for review.
+
+This page does **not** cover best practices for the content of changes. Please
+see [Life of a Jetpack Feature](loaf.md) for details on developing and releasing
+a library, [API Guidelines](api_guidelines.md) for best practices regarding
+public APIs, or [Policies and Processes](policies.md) for an overview of the
+constraints placed on changes.
+
+## Workstation setup {#setup}
+
+You will need to install the `repo` tool, which is used for Git branch and
+commit management. If you want to learn more about `repo`, see the
+[Repo Command Reference](https://source.android.com/setup/develop/repo).
+
+### Linux and MacOS {#setup-linux-mac}
+
+First, download `repo` using `curl`.
+
+```shell
+test -d ~/bin || mkdir ~/bin
+curl https://storage.googleapis.com/git-repo-downloads/repo \
+    > ~/bin/repo && chmod 700 ~/bin/repo
+```
+
+Then, modify `~/.bash_profile` (if using `bash`) to ensure you can find local
+binaries from the command line.
+
+```shell
+export PATH=~/bin:$PATH
+```
+
+You will need to either start a new terminal session or run `source
+~/.bash_profile` to pick up the new path.
+
+If you encounter an SSL `CERTIFICATE_VERIFY_FAILED` error or warning about
+Python 2 being no longer supported, you will need to install Python 3 and alias
+your `repo` command to run with `python3`.
+
+```shell {.bad}
+repo: warning: Python 2 is no longer supported; Please upgrade to Python 3.6+.
+```
+
+```shell {.bad}
+Downloading Repo source from https://gerrit.googlesource.com/git-repo
+fatal: Cannot get https://gerrit.googlesource.com/git-repo/clone.bundle
+fatal: error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)
+```
+
+First, install Python 3 from the [official website](https://www.python.org).
+Please read the "Important Information" displayed during installation for
+information about SSL/TLS certificate validation and the running the "Install
+Certificates.command".
+
+Next, open your `~/.bash_profile` and add the following lines to wrap the `repo`
+command:
+
+```shell
+# Force repo to run with Python3
+function repo() {
+  command python3 "$(which repo)" $@
+}
+```
+
+### Windows {#setup-win}
+
+Sorry, Windows is not a supported platform for AndroidX development.
+
+## Set up access control {#access}
+
+### Authenticate to AOSP Gerrit {#access-gerrit}
+
+Before you can upload changes, you will need to associate your Google
+credentials with the AOSP Gerrit code review system by signing in to
+[android-review.googlesource.com](https://android-review.googlesource.com) at
+least once using the account you will use to submit patches.
+
+Next, you will need to
+[set up authentication](https://android-review.googlesource.com/new-password).
+This will give you a shell command to update your local Git cookies, which will
+allow you to upload changes.
+
+Finally, you will need to accept the
+[CLA for new contributors](https://android-review.googlesource.com/settings/new-agreement).
+
+## Check out the source {#source}
+
+Like ChromeOS, Chromium, and the Android build system, we develop in the open as
+much as possible. All feature development occurs in the public
+[androidx-master-dev](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev)
+branch of the Android Open Source Project.
+
+As of 2020/03/20, you will need about 38 GB for a fully-built checkout.
+
+### Synchronize the branch {#source-checkout}
+
+Use the following `repo` commands to check out your branch.
+
+#### Public master development branch {#source-checkout-master}
+
+All development should occur in this branch unless otherwise specified by the
+AndroidX Core team.
+
+The following command will check out the public master development branch:
+
+```shell
+mkdir androidx-master-dev && cd androidx-master-dev
+repo init -u https://android.googlesource.com/platform/manifest \
+    -b androidx-master-dev --partial-clone --clone-filter=blob:limit=10M
+repo sync -c -j8
+```
+
+NOTE On MacOS, if you receive an SSL error like `SSL: CERTIFICATE_VERIFY_FAILED`
+you may need to install Python3 and boot strap the SSL certificates in the
+included version of pip. You can execute `Install Certificates.command` under
+`/Applications/Python 3.6/` to do so.
+
+### Increase Git rename limit {#source-config}
+
+To ensure `git` can detect diffs and renames across significant changes (namely,
+the `androidx.*` package rename), we recommend that you set the following `git
+config` properties:
+
+```shell
+git config --global merge.renameLimit 999999
+git config --global diff.renameLimit 999999
+```
+
+## Explore source code from a browser {#code-search}
+
+`androidx-master-dev` has a publicly-accessible
+[code search](https://cs.android.com/androidx/platform/frameworks/support) that
+allows you to explore all of the source code in the repository. Links to this
+URL may be shared on public Buganizer and other external sites.
+
+We recommend setting up a custom search engine in Chrome as a faster (and
+publicly-accessible) alternative to `cs/`.
+
+### Custom search engine for `androidx-master-dev` {#custom-search-engine}
+
+1.  Open `chrome://settings/searchEngines`
+1.  Click the `Add` button
+1.  Enter a name for your search engine, ex. "AndroidX Code Search"
+1.  Enter a keyword, ex. "csa"
+1.  Enter the following URL:
+    `https://cs.android.com/search?q=%s&ss=androidx%2Fplatform%2Fframeworks%2Fsupport`
+1.  Click the `Add` button
+
+Now you can select the Chrome omnibox, type in `csa` and press tab, then enter a
+query to search for, e.g. `AppCompatButton file:appcompat`, and press the
+`Enter` key to get to the search result page.
+
+## Develop in Android Studio {#studio}
+
+Library development uses a curated version of Android Studio to ensure
+compatibility between various components of the development workflow.
+
+From the `frameworks/support` directory, you can use `ANDROIDX_PROJECTS=MAIN
+./gradlew studio` to automatically download and run the correct version of
+Studio to work on main set of androidx projects. `ANDROIDX_PROJECTS` has several
+other options like `ANDROIDX_PROJECTS=ALL` to open other subsets of the
+projects.
+[settings.gradle](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:settings.gradle)
+file in the repository has these options listed.
+
+```shell
+ANDROIDX_PROJECTS=MAIN ./gradlew studio
+```
+
+Next, open the `framework/support` project root from your checkout. If Studio
+asks you which SDK you would like to use, select `Use project SDK`. Importing
+projects may take a while, but once that finishes you can use Studio as you
+normally would for application or library development -- right-click on a test
+or sample to run or debug it, search through classes, and so on.
+
+If you see any errors (red underlines), click Gradle's elephant button in the
+toolbar ("Sync Project with Gradle Files") and they should resolve once the
+build completes.
+
+> NOTE: You should choose "Use project SDK" when prompted by Studio. If you
+> picked "Android Studio SDK" by mistake, don't panic! You can fix this by
+> opening `File > Project Structure > Platform Settings > SDKs` and manually
+> setting the Android SDK home path to
+> `<project-root>/prebuilts/fullsdk-<platform>`.
+
+> NOTE: If Android Studio's UI looks scaled up, ex. twice the size it should be,
+> you may need to add the following line to your `studio64.vmoptions` file using
+> `Help -> Edit Custom VM Options`:
+>
+> ```
+> -Dsun.java2d.uiScale.enabled=false
+> ```
+
+## Making changes {#changes}
+
+Similar to Android framework development, library developmnent should occur in
+CL-specific working branches. Use `repo` to create, upload, and abandon local
+branches. Use `git` to manage changes within a local branch.
+
+```shell
+cd path/to/checkout/frameworks/support/
+repo start my_branch_name .
+# make necessary code changes
+# use git to commit changes
+repo upload --cbr -t .
+```
+
+The `--cbr` switch automatically picks the current repo branch for upload. The
+`-t` switch sets the Gerrit topic to the branch name, e.g. `my-branch-name`.
+
+## Building {#building}
+
+### Modules and Maven artifacts {#modules-and-maven-artifacts}
+
+To build a specific module, use the module's `assemble` Gradle task. For
+example, if you are working on `core` module use:
+
+```shell
+./gradlew core:core:assemble
+```
+
+Use the `-Pandroidx.allWarningsAsErrors` to make warnings fail your build (same
+as presubmits):
+
+```shell
+./gradlew core:core:assemble -Pandroidx.allWarningsAsErrors
+```
+
+To build every module, run the Lint verifier, verify the public API surface, and
+generate the local Maven repository artifact, use the `createArchive` Gradle
+task:
+
+```shell
+./gradlew createArchive
+```
+
+To run the complete build task that our build servers use, use the
+`buildOnServer` Gradle task:
+
+```shell
+./gradlew buildOnServer
+```
+
+### Attaching a debugger to the build
+
+Gradle tasks, including building a module, may be run or debugged from Android
+Studio's `Gradle` pane by finding the task to be debugged -- for example,
+`androidx > androidx > appcompat > appcompat > build > assemble` --
+right-clicking on it, and then selecting `Debug...`.
+
+Note that debugging will not be available until Gradle sync has completed.
+
+## From the command line
+
+Tasks may also be debugged from the command line, which may be useful if
+`./gradlew studio` cannot run due to a Gradle task configuration issue.
+
+1.  From the configurations dropdown in Studio, select "Edit Configurations".
+1.  Click the plus in the top left to create a new "Remote" configuration. Give
+    it a name and hit "Ok".
+1.  Set breakpoints.
+1.  Run your task with added flags: `./gradlew <your_task_here>
+    -Dorg.gradle.debug=true --no-daemon`
+1.  Hit the "Debug" button to the right of the configuration dropdown to attach
+    to the process.
+
+#### Troubleshooting the debugger
+
+If you get a "Connection refused" error, it's likely because a gradle daemon is
+still running on the port specified in the config, and you can fix this by
+killing the running gradle daemons:
+
+```shell
+./gradlew --stop
+```
+
+Note: This is described in more detail in this
+[Medium article](https://medium.com/grandcentrix/how-to-debug-gradle-plugins-with-intellij-eef2ef681a7b).
+
+#### Attaching to an annotation processor
+
+Annotation processors run as part of the build, to debug them is similar to
+debugging the build.
+
+For a Java project:
+
+```shell
+./gradlew <your_project>:compileDebugJava --no-daemon --rerun-tasks -Dorg.gradle.debug=true
+```
+
+For a Kotlin project:
+
+```shell
+./gradlew <your_project>:compileDebugKotlin --no-daemon --rerun-tasks -Dorg.gradle.debug=true -Dkotlin.compiler.execution.strategy="in-process" -Dkotlin.daemon.jvm.options="-Xdebug,-Xrunjdwp:transport=dt_socket\,address=5005\,server=y\,suspend=n"
+```
+
+### Optional: Enabling internal menu in IntelliJ/Studio
+
+To enable tools such as `PSI tree` inside of IntelliJ/Studio to help debug
+Android Lint checks and Metalava, you can enable the
+[internal menu](https://www.jetbrains.org/intellij/sdk/docs/reference_guide/internal_actions/enabling_internal.html)
+which is typically used for plugin and IDE development.
+
+### Reference documentation {#docs}
+
+Our reference docs (Javadocs and KotlinDocs) are published to
+https://developer.android.com/reference/androidx/packages and may be built
+locally.
+
+NOTE `./gradlew tasks` always has the canonical task information! When in doubt,
+run `./gradlew tasks`
+
+#### Javadocs
+
+To build API reference docs for tip-of-tree Java source code, run the Gradle
+task:
+
+```
+./gradlew disttipOfTreeDocs
+```
+
+This will output docs in the zip file:
+`{androidx-master-dev}/out/dist/android-support-tipOfTree-docs-0.zip`, as well
+as in local html files that you can check from your browser:
+`{androidx-master-dev}/out/androidx/build/javadoc/tipOfTree/offline/reference/packages.html`
+
+#### KotlinDocs
+
+To build API reference docs for tip-of-tree Kotlin source code, run the Gradle
+task:
+
+```
+./gradlew distTipOfTreeDokkaDocs
+```
+
+This will output docs in the zip file:
+`{androidx-master-dev}/out/dist/dokkaTipOfTreeDocs-0.zip`
+
+#### Release docs
+
+To build API reference docs for published artifacts formatted for use on
+[d.android.com](http://d.android.com), run the Gradle command:
+
+```
+./gradlew distpublicDocs
+```
+
+This will create the artifact
+`{androidx-master-dev}/out/dist/android-support-public-docs-0.zip`. This command
+builds docs based on the version specified in
+`{androidx-master-dev-checkout}/frameworks/support/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt`
+and uses the prebuilt checked into
+`{androidx-master-dev-checkout}/prebuilts/androidx/internal/androidx/`. We
+colloquially refer to this two step process of (1) updating PublishDocsRules.kt
+and (2) checking in a prebuilt artifact into the prebuilts directory as
+[The Prebuilts Dance](releasing.md#the-prebuilts-dance™). So, to build javadocs
+that will be published to
+https://developer.android.com/reference/androidx/packages, both of these steps
+need to be completed.
+
+Once you done the above steps, Kotlin docs will also be generated, with the only
+difference being that we use the Gradle command:
+
+```
+./gradlew distPublicDokkaDocs
+```
+
+which generates the kotlin docs artifact
+`{androidx-master-dev}/out/dist/dokkaPublicDocs-0.zip`
+
+### Updating public APIs {#updating-public-apis}
+
+Public API tasks -- including tracking, linting, and verifying compatibility --
+are run under the following conditions based on the `androidx` configuration
+block, evaluated in order:
+
+*   `runApiTasks=Yes` => yes
+*   `runApiTasks=No` => no
+*   `toolingProject=true` => no
+*   `mavenVersion` or group version not set => no
+*   Has an existing `api/` directory => yes
+*   `publish=SNAPSHOT_AND_RELEASE` => yes
+*   Otherwise, no
+
+If you make changes to tracked public APIs, you will need to acknowledge the
+changes by updating the `<component>/api/current.txt` and associated API files.
+This is handled automatically by the `updateApi` Gradle task:
+
+```shell
+# Run updateApi for all modules.
+./gradlew updateApi
+
+# Run updateApi for a single module, ex. appcompat-resources in group appcompat.
+./gradlew :appcompat:appcompat-resources:updateApi
+```
+
+If you change the public APIs without updating the API file, your module will
+still build **but** your CL will fail Treehugger presubmit checks.
+
+### Release notes & the `Relnote:` tag {#relnote}
+
+Prior to releasing, release notes are pre-populated using a script and placed
+into a Google Doc. The Google Doc is manually double checked by library owners
+before the release goes live. To auto-populate your release notes, you can use
+the semi-optional commit tag `Relnote:` in your commit, which will automatically
+include that message the commit in the pre-populated release notes.
+
+The presence of a `Relnote:` tag is required for API changes in
+`androidx-master-dev`.
+
+#### How to use it?
+
+One-line release note:
+
+``` {.good}
+Relnote: Fixed a critical bug
+```
+
+``` {.good}
+Relnote: "Fixed a critical bug"
+```
+
+``` {.good}
+Relnote: Added the following string function: `myFoo(\"bar\")`
+```
+
+Multi-line release note:
+
+Note: If the following lines do not contain an indent, you may hit b/165570183.
+
+``` {.good}
+Relnote: "We're launching this awesome new feature!  It solves a whole list of
+    problems that require a lot of explaining! "
+```
+
+``` {.good}
+Relnote: """Added the following string function: `myFoo("bar")`
+    It will fix cases where you have to call `myFoo("baz").myBar("bar")`
+    """
+```
+
+Opt out of the Relnote tag:
+
+``` {.good}
+Relnote: N/A
+```
+
+``` {.good}
+Relnote: NA
+```
+
+NOT VALID:
+
+``` {.bad}
+Relnote: This is an INVALID multi-line release note.  Our current scripts won't
+include anything beyond the first line.  The script has no way of knowing when
+the release note actually stops.
+```
+
+``` {.bad}
+Relnote: This is an INVALID multi-line release note.  "Quotes" need to be
+  escaped in order for them to be parsed properly.
+```
+
+### Common build errors
+
+#### Diagnosing build failures
+
+If you've encountered a build failure and you're not sure what is triggering it,
+then please run
+`./development/diagnose-build-failure/diagnose-build-failure.sh`.
+
+This script can categorize your build failure into one of the following
+categories:
+
+*   The Gradle Daemon is saving state in memory and triggering a failure
+*   Your source files have been changed and/or incompatible git commits have
+    been checked out
+*   Some file in the out/ dir is triggering an error
+    *   If this happens, diagnose-build-failure.sh should also identify which
+        file(s) specifically
+*   The build is nondeterministic and/or affected by timestamps
+*   The build via gradlew actually passes and this build failure is specific to
+    Android Studio
+
+Some more-specific build failures are listed below in this page.
+
+#### Out-of-date platform prebuilts
+
+Like a normal Android library developed in Android Studio, libraries within
+`androidx` are built against prebuilts of the platform SDK. These are checked in
+to the `prebuilts/fullsdk-darwin/platforms/<android-version>` directory.
+
+If you are developing against pre-release platform APIs in the internal
+`androidx-platform-dev` branch, you may need to update these prebuilts to obtain
+the latest API changes.
+
+### Missing external dependency
+
+If Gradle cannot resolve a dependency listed in your `build.gradle`, you may
+need to import the corresponding artifact into `prebuilts/androidx/external`.
+Our workflow does not automatically download artifacts from the internet to
+facilitate reproducible builds even if remote artifacts are changed.
+
+You can download a dependency by running:
+
+```shell
+cd frameworks/support && ./development/importMaven/import_maven_artifacts.py -n 'someGroupId:someArtifactId:someVersion'
+```
+
+This will create a change within the `prebuilts/androidx/external` directory.
+Make sure to upload this change before or concurrently (ex. in the same Gerrit
+topic) with the dependent library code.
+
+Libraries typically reference dependencies using constants defined in
+[`Dependencies.kt`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt),
+so please update this file to include a constant for the version of the library
+that you have checked in. You will reference this constant in your library's
+`build.gradle` dependencies.
+
+#### Updating an existing dependency
+
+If an older version of a dependency prebuilt was already checked in, please
+manually remove it within the same CL that adds the new prebuilt. You will also
+need to update `Dependencies.kt` to reflect the version change.
+
+#### My gradle build fails with "Cannot invoke method getURLs() on null object"
+
+You're using Java 9's javac, possibly because you ran envsetup.sh from the
+platform build or specified Java 9 as the global default Java compiler. For the
+former, you can simply open a new shell and avoid running envsetup.sh. For the
+latter, we recommend you set Java 8 as the default compiler using sudo
+update-java-alternatives; however, if you must use Java 9 as the default then
+you may alternatively set JAVA_HOME to the location of the Java 8 SDK.
+
+#### My gradle build fails with "error: cannot find symbol" after making framework-dependent changes.
+
+You probably need to update the prebuilt SDK used by the gradle build. If you
+are referencing new framework APIs, you will need to wait for the framework
+changes to land in an SDK build (or build it yourself) and then land in both
+prebuilts/fullsdk and prebuilts/sdk. See
+[Updating SDK prebuilts](playbook.md#prebuilts-fullsdk) for more information.
+
+#### How do I handle refactoring a framework API referenced from a library?
+
+Because AndroidX must compile against both the current framework and the latest
+SDK prebuilt, and because compiling the SDK prebuilt depends on AndroidX, you
+will need to refactor in stages: Remove references to the target APIs from
+AndroidX Perform the refactoring in the framework Update the framework prebuilt
+SDK to incorporate changes in (2) Add references to the refactored APIs in
+AndroidX Update AndroidX prebuilts to incorporate changes in (4)
+
+## Testing {#testing}
+
+AndroidX libraries are expected to include unit or integration test coverage for
+100% of their public API surface. Additionally, all CLs must include a `Test:`
+stanza indicating which tests were used to verify correctness. Any CLs
+implementing bug fixes are expected to include new regression tests specific to
+the issue being fixed
+
+See the [Testing](testing.md) page for more resources on writing, running, and
+monitoring tests.
+
+### AVD Manager
+
+The Android Studio instance started by `./gradlew studio` uses a custom SDK
+directory, which means any virtual devices created by a "standard" non-AndroidX
+instance of Android Studio will be _visible_ from the `./gradlew studio`
+instance but will be unable to locate the SDK artifacts -- they will display a
+`Download` button.
+
+You can either use the `Download` button to download an extra copy of the SDK
+artifacts _or_ you can set up a symlink to your "standard" non-AndroidX SDK
+directory to expose your existing artifacts to the `./gradlew studio` instance:
+
+```shell
+# Using the default MacOS Android SDK directory...
+ln -s /Users/$(whoami)/Library/Android/sdk/system-images \
+      ../../prebuilts/fullsdk-darwin/system-images
+```
+
+### Benchmarking {#testing-benchmarking}
+
+Libraries are encouraged to write and monitor performance benchmarks. See the
+[Benchmarking](benchmarking.md) page for more details.
+
+## Library snapshots {#snapshots}
+
+### Quick how to
+
+Add the following snippet to your build.gradle file, replacing `buildId` with a
+snapshot build Id.
+
+```groovy {highlight=context:[buildId]}
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        maven { url 'https://androidx.dev/snapshots/builds/[buildId]/artifacts/repository' }
+    }
+}
+```
+
+You must define dependencies on artifacts using the SNAPSHOT version suffix, for
+example:
+
+```groovy {highlight=context:SNAPSHOT}
+dependencies {
+    implementation "androidx.core:core:1.2.0-SNAPSHOT"
+}
+```
+
+### Where to find snapshots
+
+If you want to use unreleased `SNAPSHOT` versions of `androidx` artifacts, you
+can find them on either our public-facing build server:
+
+`https://ci.android.com/builds/submitted/<build_id>/androidx_snapshot/latest`
+
+or on our slightly-more-convenient [androidx.dev](https://androidx.dev) site:
+
+`https://androidx.dev/snapshots/builds/<build-id>/artifacts/repository` for a
+specific build ID
+
+`https://androidx.dev/snapshots/builds/latest/artifacts/repository` for
+tip-of-tree snapshots
+
+### Obtaining a build ID
+
+To browse build IDs, you can visit either
+[androidx-master-dev](https://ci.android.com/builds/branches/aosp-androidx-master-dev/grid?)
+on ci.android.com or [Snapshots](https://androidx.dev/snapshots/builds) on the
+androidx.dev site.
+
+Note that if you are using androidx.dev, you may substitute `latest` for a build
+ID to use the last known good build.
+
+To manually find the last known good `build-id`, you have several options.
+
+#### Snapshots on androidx.dev
+
+[Snapshots](https://androidx.dev/snapshots/builds) on androidx.dev only lists
+usable builds.
+
+#### Programmatically via `jq`
+
+Install `jq`:
+
+```shell
+sudo apt-get install jq
+```
+
+```shell
+ID=`curl -s "https://ci.android.com/builds/branches/aosp-androidx-master-dev/status.json" | jq ".targets[] | select(.ID==\"aosp-androidx-master-dev.androidx_snapshot\") | .last_known_good_build"` \
+  && echo https://ci.android.com/builds/submitted/"${ID:1:-1}"/androidx_snapshot/latest/raw/repository/
+```
+
+#### Android build server
+
+Go to
+[androidx-master-dev](https://ci.android.com/builds/branches/aosp-androidx-master-dev/grid?)
+on ci.android.com.
+
+For `androidx-snapshot` target, wait for the green "last known good build"
+button to load and then click it to follow it to the build artifact URL.
+
+### Using in a Gradle build
+
+To make these artifacts visible to Gradle, you need to add it as a respository:
+
+```groovy
+allprojects {
+    repositories {
+        google()
+        maven {
+          // For all Jetpack libraries (including Compose)
+          url 'https://androidx.dev/snapshots/builds/<build-id>/artifacts/repository'
+        }
+    }
+}
+```
+
+Note that the above requires you to know the `build-id` of the snapshots you
+want.
+
+#### Specifying dependencies
+
+All artifacts in the snapshot repository are versioned as `x.y.z-SNAPSHOT`. So
+to use a snapshot artifact, the version in your `build.gradle` will need to be
+updated to `androidx.<groupId>:<artifactId>:X.Y.Z-SNAPSHOT`
+
+For example, to use the `core:core:1.2.0-SHAPSHOT` snapshot, you would add the
+following to your `build.gradle`:
+
+```
+dependencies {
+    ...
+    implementation("androidx.core:core:1.2.0-SNAPSHOT")
+    ...
+}
+```
+
+## FAQ {#faq}
+
+### How do I test my change in a separate Android Studio project? {#faq-test-change-studio}
+
+If you're working on a new feature or bug fix in AndroidX, you may want to test
+your changes against another project to verify that the change makes sense in a
+real-world context or that a bug's specific repro case has been fixed.
+
+If you need to be absolutely sure that your test will exactly emulate the
+developer's experience, you can repeatedly build the AndroidX archive and
+rebuild your application. In this case, you will need to create a local build of
+AndroidX's local Maven repository artifact and install it in your Android SDK
+path.
+
+First, use the `createArchive` Gradle task to generate the local Maven
+repository artifact:
+
+```shell
+# Creates <path-to-checkout>/out/dist/sdk-repo-linux-m2repository-##.zip
+./gradlew createArchive
+```
+
+Next, take the ZIP output from this task and extract the contents to the Android
+SDK path that you are using for your alternate (non-AndroidX) version of Android
+Studio. For example, you may be using `~/Android/SDK/extras` if you are using
+the default Android Studio SDK for app development or
+`prebuilts/fullsdk-linux/extras` if you are using fullsdk for platform
+development.
+
+```shell
+# Creates or overwrites android/m2repository
+cd <path-to-sdk>/extras
+unzip <path-to-checkout>/out/dist/top-of-tree-m2repository-##.zip
+```
+
+In the project's 'build.gradle' within 'repositories' notify studio of the
+location of m2repository:
+
+```groovy
+allprojects {
+    repositories {
+        ...
+        maven {
+            url "<path-to-sdk>/extras/m2repository"
+        }
+    }
+}
+```
+
+NOTE Gradle resolves dependencies in the order that the repositories are defined
+(if 2 repositories can resolve the same dependency, the first listed will do so
+and the second will not). Therefore, if the library you are testing has the same
+group, artifact, and version as one already published, you will want to list
+your custom maven repo first.
+
+Finally, in the dependencies section of your standalone project's `build.gradle`
+file, add or update the `implementation` entries to reflect the AndroidX modules
+that you would like to test. Example:
+
+```
+dependencies {
+    ...
+    implementation "androidx.appcompat:appcompat::1.0.0-alpha02"
+}
+```
+
+If you are testing your changes in the Android Platform code, you can replace
+the module you are testing
+`YOUR_ANDROID_PATH/prebuilts/sdk/current/androidx/m2repository` with your own
+module. We recommend only replacing the module you are modifying instead of the
+full m2repository to avoid version issues of other modules. You can either take
+the unzipped directory from
+`<path-to-checkout>/out/dist/top-of-tree-m2repository-##.zip`, or from
+`<path-to-checkout>/out/androidx/build/support_repo/` after buiding `androidx`.
+Here is an example of replacing the RecyclerView module:
+
+```shell
+$TARGET=YOUR_ANDROID_PATH/prebuilts/sdk/current/androidx/m2repository/androidx/recyclerview/recyclerview/1.1.0-alpha07;
+rm -rf $TARGET;
+cp -a <path-to-sdk>/extras/m2repository/androidx/recyclerview/recyclerview/1.1.0-alpha07 $TARGET
+```
+
+Make sure the library versions are the same before and after replacement. Then
+you can build the Android platform code with the new `androidx` code.
diff --git a/docs/onboarding_images/image1.png b/docs/onboarding_images/image1.png
new file mode 100644
index 0000000..9a32d42
--- /dev/null
+++ b/docs/onboarding_images/image1.png
Binary files differ
diff --git a/docs/onboarding_images/image2.png b/docs/onboarding_images/image2.png
new file mode 100644
index 0000000..9c215f1
--- /dev/null
+++ b/docs/onboarding_images/image2.png
Binary files differ
diff --git a/docs/onboarding_images/image3.png b/docs/onboarding_images/image3.png
new file mode 100644
index 0000000..e672255
--- /dev/null
+++ b/docs/onboarding_images/image3.png
Binary files differ
diff --git a/docs/policies.md b/docs/policies.md
new file mode 100644
index 0000000..b099299
--- /dev/null
+++ b/docs/policies.md
@@ -0,0 +1,190 @@
+## AndroidX policies and processes
+
+This document is intended to describe release policies that affect the workflow
+of an engineer developing within the AndroidX libraries. It also describes the
+process followed by a release engineer or TPM to take a development branch and
+publish it as a release on Google Maven.
+
+Policies and processes automated via tooling are noted in
+<span style="color:#bf9000;">yellow</span>.
+
+[TOC]
+
+## Project directory structure {#directory-structure}
+
+Libraries developed in AndroidX follow a consistent project naming and directory
+structure.
+
+Library groups should organize their modules into directories and module names
+(in brackets) as:
+
+```
+<feature-name>/
+  <feature-name>-<sub-feature>/ [<feature-name>:<feature-name>-<sub-feature>]
+  integration-tests/
+    testapp/ [<feature-name>:testapp]
+    testlib/ [<feature-name>:testlib]
+    samples/ [<feature-name>:samples]
+```
+
+For example, the `room` library group's directory structure is:
+
+```
+room/
+  common/ [room:room-common]
+  ...
+  rxjava2/ [room:room-rxjava2]
+  testing/ [room:room-testing]
+  integration-tests/
+    testapp/ [room:testapp]
+    testapp-kotlin/ [room:testapp-kotlin]
+```
+
+## Terminology {#terminology}
+
+**Artifact**
+:   Previously referred to as "a Support Library library." A library --
+    typically Java or Android -- that maps to a single Maven artifact, ex.
+    `androidx.recyclerview:recyclerview`. An artifact is associated with a
+    single Android Studio module and a directory containing a `build.gradle`
+    configuration, resources, and source code.
+
+**API Council**
+:   A committee that reviews Android APIs, both platform and library, to ensure
+    they are consistent and follow the best-practices defined in our API
+    guidelines.
+
+**Semantic Versioning (SemVer)**
+:   A versioning standard developed by one of the co-founders of GitHub that is
+    understood by common dependency management systems, including Maven. In this
+    document, we are referring specifically to
+    [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
+
+## Managing versions {#managing-versions}
+
+This section outlines the steps for a variety of common versioning-related
+tasks. Artifact versions should **only** be modified by their owners as
+specified in the artifact directory's `OWNERS` file.
+
+Artifact versions are specified in
+[`LibraryVersions.kt`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt).
+Versions are bound to your artifact in the `supportLibrary` block in your
+artifact's `build.gradle` file. The `Version` class validates the version string
+at build time.
+
+In the
+[`LibraryVersions.kt`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt)
+file:
+
+```
+object LibraryVersions {
+    val SNAZZY_ARTIFACT = Version("1.1.0-alpha03")
+}
+```
+
+In your artifact's `build.gradle` file:
+
+```
+import androidx.build.LibraryVersions
+
+supportLibrary {
+   mavenVersion = LibraryVersions.SNAZZY_ARTIFACT
+}
+```
+
+#### Finalizing for release {#finalizing-for-release}
+
+When the artifact is ready for release, its versioned API file should be
+finalized to ensure that the subsequent versioned release conforms to the
+versioning policies.
+
+```
+./gradlew <module>:finalizeApi
+```
+
+This will prevent any subsequent changes to the API surface until the artifact
+version is updated. To update the artifact version and allow changes within the
+semantic versioning contract, simply update the version string in the artifact's
+`build.gradle` (see [Workflow](#workflow) introduction).
+
+To avoid breaking the development workflow, we recommend that API finalization
+and version string updates be submitted as a single CL.
+
+## Dependencies {#dependencies}
+
+Artifacts may depend on other artifacts within AndroidX as well as sanctioned
+third-party libraries.
+
+### Versioned artifacts {#versioned-artifacts}
+
+One of the most difficult aspects of independently-versioned releases is
+maintaining compatibility with public artifacts. In a mono repo such as Google's
+repository or Android Git at master revision, it's easy for an artifact to
+accidentally gain a dependency on a feature that may not be released on the same
+schedule.
+
+#### Pre-release dependencies {#pre-release-dependencies}
+
+Pre-release suffixes **must** propagate up the dependency tree. For example, if
+your artifact has API-type dependencies on pre-release artifacts, ex.
+`1.1.0-alpha01`, then your artifact must also carry the `alpha` suffix. If you
+only have implementation-type dependencies, your artifact may carry either the
+`alpha` or `beta` suffix.
+
+#### Pinned versions {#pinned-versions}
+
+To avoid issues with dependency versioning, consider pinning your artifact's
+dependencies to the oldest version (available via local `maven_repo` or Google
+Maven) that satisfies the artifact's API requirements. This will ensure that the
+artifact's release schedule is not accidentally tied to that of another artifact
+and will allow developers to use older libraries if desired.
+
+```
+dependencies {
+   api("androidx.collection:collection:1.0.0")
+   ...
+}
+```
+
+Artifacts should be built and tested against both pinned and tip-of-tree
+versions of their dependencies to ensure behavioral compatibility.
+
+#### Non-Pinned versions {#nonpinned-versions}
+
+Below is an example of a non-pinned dependency. It ties the artifact's release
+schedule to that of the dependency artifact, because the dependency will need to
+be released at the same time.
+
+```
+dependencies {
+   api(project(":collection"))
+   ...
+}
+```
+
+### Non-public APIs {#non-public-apis}
+
+Artifacts may depend on non-public (e.g. `@hide`) APIs exposed within their own
+artifact or another artifact in the same `groupId`; however, cross-artifact
+usages are subject to binary compatibility guarantees and
+`@RestrictTo(Scope.LIBRARY_GROUP)` APIs must be tracked like public APIs.
+
+```
+Dependency versioning policies are enforced at build time in the createArchive task. This task will ensure that pre-release version suffixes are propagated appropriately.
+
+Cross-artifact API usage policies are enforced by the checkApi and checkApiRelease tasks (see Life of a release).
+```
+
+### Third-party libraries {#third-party-libraries}
+
+Artifacts may depend on libraries developed outside of AndroidX; however, they
+must conform to the following guidelines:
+
+*   Prebuilt **must** be checked into Android Git with both Maven and Make
+    artifacts
+    *   `prebuilts/maven_repo` is recommended if this dependency is only
+        intended for use with AndroidX artifacts, otherwise please use
+        `external`
+*   Prebuilt directory **must** contains an OWNERS file identifying one or more
+    individual owners (e.g. NOT a group alias)
+*   Library **must** be approved by legal
diff --git a/docs/principles.md b/docs/principles.md
new file mode 100644
index 0000000..6dba721
--- /dev/null
+++ b/docs/principles.md
@@ -0,0 +1,135 @@
+# Jetpack Principles
+
+[TOC]
+
+## Ethos of Jetpack
+
+To create components, tools, and guidance that makes it quick and easy to build
+great Android apps, including contributions from Google and the open-source
+community.
+
+## Core Principles of a Jetpack Library
+
+Jetpack libraries provide the following guarantees to Android Developers:
+
+_formatted as “Jetpack libraries are…” with sub-points “Libraries should…”_
+
+### 1. Optimized for external client adoption
+
+-   Libraries should work for first-party clients and may even have optional
+    modules tailored specifically to first-party needs, but primary
+    functionality should target external developers.
+-   Measure success by 3p client adoption, followed by 1p client adoption.
+
+### 2. Designed to satisfy real-world use cases
+
+-   Meet developers where they are and solve the problems that they have
+    building apps -- not designed to just provide parity with existing platform
+    APIs and features
+-   Expose modules that are tightly-scoped to **developer pain points**
+    -   Smaller building blocks for external developers by scoping disjoint use
+        cases that are likely not to co-exist in a single app to individual
+        modules.
+-   Implement layered complexity, with **simple top-level APIs**
+    -   Complicated use case support must not be at the expense of increasing
+        API complexity for the most common simpler use cases.
+-   Have **backing data or a researched hypothesis** (research, demand etc) to
+    prove the library is necessary and sufficient.
+
+### 3. Aware of the existing developer ecosystem
+
+-   Avoid reinventing the wheel -- do not create a new library where one already
+    exists that is accepted by the community as a best practice
+
+### 4. Consistent with the rest of Jetpack
+
+-   Ensure that concepts learned in one component can be seen and understood in
+    other components
+-   Leverage Jetpack and community standards, for example:
+    -   For async work, uses Kotlin coroutines and/or Kotlin flow
+    -   For data persistence, uses Jetpack DataStore for simple and small data
+        and uses Room for more complicated Data
+
+### 5. Developed as open-source and compatible with AOSP Android
+
+-   Expose a unified developer-facing API surface across the Android ecosystem
+-   Avoid proprietary services or closed-source libraries for core
+    functionality, and instead provide integration points that allow a developer
+    to choose a proprietary service as the backing implementation
+-   Develop in AOSP to provide visibility into new features and bug fixes and
+    encourage external participation
+
+### 6. Written using language-idiomatic APIs
+
+-   Write APIs that feel natural for clients using both
+    [Kotlin](https://developer.android.com/kotlin/interop) and Java
+
+### 7. Compatible with a broad range of API levels
+
+-   Support older platforms and API levels according to client needs
+-   Provide continued maintenance to ensure compatibility with newer platforms
+-   Design with the expectation that every Jetpack API is **write-once,
+    run-everywhere** for Android with graceful degradation where necessary
+
+### 8. Integrated with best practices
+
+-   Guide developers toward using existing Jetpack best-practice libraries,
+    including Architecture Components
+
+### 9. Designed for tooling and testability
+
+-   Write adequate unit and integration tests for the library itself
+-   Provide developers with an accompanying testing story for integration
+    testing their own applications (ex. -testing artifacts that some libraries
+    expose)
+    -   Robolectric shouldn’t need to shadow the library classes
+    -   Ex. Room has in-memory testing support
+-   Build tooling concurrent with the library when possible, and with tooling in
+    mind otherwise
+
+### 10. Released using a clearly-defined process
+
+-   Follow Semantic Versioning and pre-release revision guidelines where each
+    library moves through alpha, beta, and rc revisions to gain feedback and
+    ensure stability
+
+### 11. Well-documented
+
+-   Provide developers with getting started and use case documentation on
+    d.android.com in addition to clear API reference documentation
+
+### 12. Supported for long-term use
+
+-   Plan for long-term support and maintenance
+
+### 13. Examples of modern development
+
+-   Where possible, targeting the latest languages, OS features, and tools. All
+    new libraries should be written in Kotlin first. Existing libraries
+    implemented in Java should add Kotlin extension libraries to improve the
+    interoperability of the Java APIs from Kotlin. New libraries written in Java
+    require a significant business reason on why a dependency in Kotlin cannot
+    be taken. The following is the order of preference, with each lower tier
+    requiring a business reason:
+    1.  Implemented in Kotlin that compiles to Java 8 bytecode
+    2.  Implemented in Java 8, with `-ktx` extensions for Kotlin
+        interoperability
+    3.  Implemented in Java 7, with `-ktx` extensions for Kotlin
+        interoperability
+
+### 14. High quality APIs and ownership
+
+-   All Jetpack libraries are expected to abide by Android and Jetpack API
+    Guidelines
+
+## Target Audience
+
+Jetpack libraries are used by a wide variety of external developers, from
+individuals working on their first Android app to huge corporations developing
+large-scale production apps. Generally, however, Jetpack libraries are designed
+to focus on small- to medium-sized app development teams.
+
+-   Note: If the library targets a niche set of apps, the developer pain
+    point(s) addressed by the Jetpack library must be significant enough to
+    justify its need.
+    -   Example : Jetpack Enterprise library
diff --git a/docs/testing.md b/docs/testing.md
new file mode 100644
index 0000000..b937bb9
--- /dev/null
+++ b/docs/testing.md
@@ -0,0 +1,246 @@
+# Testing
+
+[TOC]
+
+AndroidX contains unit and integration tests that are run automatically when a
+change is uploaded. It also contains a number of sample applications that are
+useful for demonstrating how to use features as well as performing manual
+testing.
+
+## Adding tests {#adding}
+
+For an example of how to set up simple unit and integration tests in a new
+module, see
+[aosp/1189799](https://android-review.googlesource.com/c/platform/frameworks/support/+/1189799).
+For an example of how to set up Espresso-powered integration tests, see the
+`preference` library's
+[`build.gradle`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:preference/preference/build.gradle)
+and
+[`EditTextPreferenceTest.java`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:preference/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java)
+files.
+
+The currently allowed test runners for on-device tests are
+[`AndroidJUnitRunner`](https://developer.android.com/training/testing/junit-runner)
+and
+[`Parameterized`](https://junit.org/junit4/javadoc/4.12/org/junit/runners/Parameterized.html).
+
+### What gets tested, and when
+
+We use the
+[AffectedModuleDetector](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt)
+to determine what projects have changed since the last merge.
+
+In presubmit, "affected" modules will run all host and device tests regardless
+of size. Modules that _depend_ on affected modules will run all host tests, but
+will only run device tests annotated with `@SmallTest` or `@MediumTest`.
+
+When changes are made that can't be associated with a module, are in the root of
+the checkout, or are within `buildSrc`, then all host tests and all device tests
+annotated with `@SmallTest` or `@MediumTest` will be run for all modules.
+
+Presubmit tests represent only a subset of the devices on which our tests run.
+The remaining devices are tested only in postsubmit. In postsubmit, all host and
+device tests are run for all modules.
+
+### Test annotations
+
+#### Test size
+
+All device tests *should* be given a size annotation, which is one of:
+
+*   [`@SmallTest`](https://developer.android.com/reference/androidx/test/filters/SmallTest)
+*   [`@MediumTest`](https://developer.android.com/reference/androidx/test/filters/MediumTest)
+*   [`@LargeTest`](https://developer.android.com/reference/androidx/test/filters/LargeTest)
+
+If a device test is _not_ annotated with its size, it will be considered a
+`@LargeTest` by default. Host tests do not need to be annotated with their size,
+as all host tests are run regardless of size.
+
+This annotation can occur at either the class level or individual test level.
+After API level 27, timeouts are enforced based on this annotation.
+
+Annotation    | Max duration | Timeout after
+------------- | ------------ | -------------
+`@SmallTest`  | 200ms        | 300ms
+`@MediumTest` | 1000ms       | 1500ms
+`@LargeTest`  | 100000ms     | 120000ms
+
+Small tests should be less than 200ms, and the timeout is set to 300ms. Medium
+tests should be less than 1000ms, and the timeout is set to 1500ms. Large tests
+have a timeout of 120000ms, which should cover any remaining tests.
+
+The exception to this rule is when using a runner other than
+[`AndroidJUnitRunner`](https://developer.android.com/training/testing/junit-runner).
+Since these runners do not enforce timeouts, tests that use them must not use a
+size annotation. They will run whenever large tests would run.
+
+Currently the only allowed alternative is the
+[`Parameterized`](https://junit.org/junit4/javadoc/4.12/org/junit/runners/Parameterized.html)
+runner. If you need to use a different runner for some reason, please reach out
+to the team and we can review/approve the use.
+
+#### Disabling tests
+
+To disable a device-side test in presubmit testing only -- but still have it run
+in postsubmit -- use the
+[`@FlakyTest`](https://developer.android.com/reference/androidx/test/filters/FlakyTest)
+annotation. There is currently no support for presubmit-only disabling of
+host-side tests.
+
+If you need to stop a host- or device-side test from running entirely, use
+JUnit's [`@Ignore`](http://junit.sourceforge.net/javadoc/org/junit/Ignore.html)
+annotation. Do *not* use Android's `@Suppress` annotation, which only works with
+Android test runners and will *not* work for host-side tests.
+
+#### Filtering devices
+
+To restrict a test to a range of SDKs, use
+[`@SdkSuppress`](https://developer.android.com/reference/androidx/test/filters/SdkSuppress)
+which allows specifying a range with `minSdkVersion` and `maxSdkVersion`. This
+annotation also supports targeting a specific pre-release SDK with the
+`codeName` parameter.
+
+```java
+// Target SDKs 17 through 19, inclusive
+@SdkSuppress(minSdkVersion = 17, maxSdkVersion = 19)
+
+// Target pre-release SDK R only
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, isCodeName = "R")
+```
+
+You may also gate portions of test implementation code using `SDK_INT` or
+[`BuildCompat.isAtLeast`](https://developer.android.com/reference/androidx/core/os/BuildCompat)
+methods.
+
+To restrict to only phsyical devices, use
+[`@RequiresDevice`](https://developer.android.com/reference/androidx/test/filters/RequiresDevice).
+
+### Animations in tests
+
+Animations are disabled for tests by default. This helps avoid flakes due to
+timing and also makes tests faster.
+
+In rare cases, like testing the animations themselves, you may want to enable
+animations for a particular test or test class. For those cases, you can use the
+[`AnimationDurationScaleRule`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:testutils/testutils-runtime/src/main/java/androidx/testutils/AnimationDurationScaleRule.kt).
+
+## Using the emulator {#emulator}
+
+You can use the emulator or a real device to run tests. If you wish to use the
+emulator, you will need to access the AVD Manager (and your downloaded emulator
+images) using a separate "normal" instance of Android Studio. "Normal" means a
+non-Canary build of Studio that you would use for regular app development -- the
+important part being that it points to the Android SDK where your downloaded
+emulator images reside. You will need to open a project to get the Tools menu --
+do NOT open the AndroidX project in the "normal" instance of Android Studio;
+instead, open a normal app or create a blank project using the app wizard.
+
+## Debugging with platform SDK sources {#sources}
+
+The platform SDK sources that are checked into the development branch may not
+match up with the build of Android present on the emulator or your physical
+device. As a result, the line numbers reported by the debugger may not match up
+the actual code being run.
+
+If you have a copy of the sources for the build against which you are debugging,
+you can manually specify your platform SDK source path:
+
+1.  Click on a module (e.g. `appcompat`) in the `Project` view
+1.  Press `Ctrl-Shift-A` and type "Module Settings", then run the action
+1.  In the `Project Structure` dialog, navigate to `SDKs > Android API 29
+    Platform > Sourcepath`
+1.  Use the `-` button to remove any paths that are present, then use the `+`
+    button to add the desired source path, ex. `<android checkout
+    root>/frameworks/base` if you are debugging against a locally-built system
+    image
+
+NOTE The `Project Structure` dialog reachable via `File > Project Structure` is
+**not** the same as the `Project Structure` dialog that will allow you to
+specify the SDK source path. You must use the "Module Settings" action as
+directed above.
+
+## Running unit and integration tests {#running}
+
+From Android Studio, right-click can be used to run most test targets, including
+source files, classes within a file, or individual test methods but **not**
+entire modules. To run a supported test target, right-click on the test target
+and then click `Run <name of test target>`.
+
+To run tests for an entire module such as `appcompat`, use `Run -> Edit
+configurations...` and use the `+` button to create a new `Android Instrumented
+Tests` configuration. Specify the module to be tested, give it a reasonable name
+(not "All Tests") and click `OK`, then use the `Run` menu to run the
+configuration.
+
+![alt_text](onboarding_images/image2.png "screenshot of run menu")
+
+NOTE If you receive the error `JUnit version 3.8 or later expected` this means
+that Android Studio generated an Android JUnit configuration when you actually
+needed an Android Instrumented Tests configuration. Open the `Run -> Edit
+configurations...` dialog and delete the configuration from Android JUnit, then
+manually add a configuration in Android Instrumented Tests.
+
+### From the command line {#running-from-shell}
+
+Following a successful build, tests may be run against a particular AndroidX
+module using `gradlew`.
+
+To run all integration tests in a specific project, run the following from
+`framework/support`:
+
+```shell
+./gradlew <project-name>:connectedCheck --info --daemon
+```
+
+substituting the Gradle project name (ex. `core`).
+
+To run all integration tests in the specific project and test class you're
+working on, run
+
+```shell
+./gradlew <project-name>:connectedCheck --info --daemon \
+    -Pandroid.testInstrumentationRunnerArguments.class=<fully-qualified-class>[\#testName]
+```
+
+substituting the Gradle project name (ex. `viewpager`) and fully-qualified class
+name (ex. `androidx.viewpager.widget.ViewPagerTest`) of your test file,
+optionally followed by `\#testName` if you want to execute a single test in that
+file.
+
+If you see some weird compilation errors such as below, run `./gradlew clean`
+first:
+
+```
+Unknown source file : UNEXPECTED TOP-LEVEL EXCEPTION:
+Unknown source file : com.android.dex.DexException: Multiple dex files define Landroid/content/pm/ParceledListSlice$1;
+```
+
+## Test apps {#testapps}
+
+Library developers are strongly encouraged to write test apps that exercise
+their library's public API surface. Test apps serve multiple purposes:
+
+*   Integration testing and validation of API testability, when paired with
+    tests
+*   Validation of API usability and developer experience, when paired with a use
+    case or critical user journey
+*   Sample documentation, when embedded into API reference docs using the
+    [`@sample` and `@Sampled` annotations](api_guidelines.md#sample-usage)
+
+### Legacy test apps {#testapps-legacy}
+
+We have a set of legacy sample Android applications in projects suffixed with
+`-demos`. These applications do not have tests and should not be used as test
+apps for new APIs, but they may be useful for manual regression testing.
+
+1.  Click `Run/Debug Configuration` on the top of the window.
+1.  Select the app you want to run.
+1.  Click 'Run' button.
+
+![alt_text](onboarding_images/image3.png "screenshot of Run/Debug menu")
+
+## Benchmarking {#benchmarking}
+
+AndroidX supports benchmarking - locally with Studio/Gradle, and continuously in
+post-submit. For more information on how to create and run benchmarks, see
+[Benchmarking](benchmarking.md).
diff --git a/docs/truth_guide.md b/docs/truth_guide.md
new file mode 100644
index 0000000..fd9efc4
--- /dev/null
+++ b/docs/truth_guide.md
@@ -0,0 +1,147 @@
+# Adding custom Truth subjects
+
+[TOC]
+
+## Custom truth subjects
+
+Every subject class should extend
+[Subject](https://truth.dev/api/latest/com/google/common/truth/Subject.html) and
+follow the naming schema `[ClassUnderTest]Subject`. The Subject must also have a
+constructor that accepts
+[FailureMetadata](https://truth.dev/api/latest/com/google/common/truth/FailureMetadata.html)
+and a reference to the object under test, which are both passed to the
+superclass.
+
+```kotlin
+class NavControllerSubject private constructor(
+    metadata: FailureMetadata,
+    private val actual: NavController
+) : Subject(metadata, actual) { }
+```
+
+### Subject factory
+
+The Subject class should also contain two static fields; a
+[Subject Factory](https://truth.dev/api/latest/com/google/common/truth/Subject.Factory.html)
+and an`assertThat()` shortcut method.
+
+A subject Factory provides most of the functionality of the Subject by allowing
+users to perform all operations provided in the Subject class by passing this
+factory to `about()` methods. E.g:
+
+`assertAbout(navControllers()).that(navController).isGraph(x)` where `isGraph()`
+is a method defined in the Subject class.
+
+The assertThat() shortcut method simply provides a shorthand method for making
+assertions about the Subject without needing a reference to the subject factory.
+i.e., rather than using
+`assertAbout(navControllers()).that(navController).isGraph(x)` users can simply
+use`assertThat(navController).isGraph(x)`.
+
+```kotlin
+companion object {
+    fun navControllers(): Factory<NavControllerSubject, NavController> =
+        Factory<NavControllerSubject, NavController> { metadata, actual ->
+            NavControllerSubject(metadata, actual)
+        }
+
+    @JvmStatic
+    fun assertThat(actual: NavController): NavControllerSubject {
+        return assertAbout(navControllers()).that(actual)
+    }
+}
+```
+
+### Assertion methods
+
+When creating assertion methods for your custom Subject the names of these
+methods should follow the
+[Truth naming convention](https://truth.dev/faq#assertion-naming).
+
+To create assertion methods it is necessary to either delegate to an existing
+assertion method by using `Subject.check()` or to provide your own failure
+strategy by using`failWithActual()` or `failWithoutActual()`.
+
+When using `failWithActual()` the error message will display the`toString()`
+value of the Subject object. Additional information can be added to these error
+messages by using `fact(key, value)` or `simpleFact(value)` where `fact()` will
+be output as a colon separated key, value pair.
+
+```kotlin
+fun isGraph(@IdRes navGraph: Int) {
+    check("graph()").that(actual.graph.id).isEqualTo(navGraph)
+}
+
+// Sample Failure Message
+value of          : navController.graph()
+expected          : 29340
+but was           : 10394
+navController was : {actual.toString() value}
+```
+
+```kotlin
+fun isGraph(@IdRes navGraph: Int) {
+    val actualGraph = actual.graph.id
+    if (actualGraph != navGraph) {
+        failWithoutActual(
+            fact("expected id", navGraph.toString()),
+            fact("but was", actualGraph.toString()),
+            fact("current graph is", actual.graph)
+        )
+    }
+}
+
+// Sample Failure Message
+expected id      : 29340
+but was          : 10394
+current graph is : {actual.graph.toString() value}
+```
+
+## Testing
+
+When testing expected successful assertions it is enough to simply call the
+assertion with verified successful actual and expected values.
+
+```kotlin
+private lateinit var navController: NavController
+@Before
+fun setUp() {
+    navController = NavController(
+        ApplicationProvider.getApplicationContext()
+    ).apply {
+        navigationProvider += TestNavigator()
+        // R.navigation.testGraph == R.id.test_graph
+        setGraph(R.navigation.test_graph)
+    }
+}
+
+@Test
+fun testGraph() {
+    assertThat(navController).isGraph(R.id.test_graph)
+}
+```
+
+To test that expected failure cases you should use the
+[assertThrows](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:testutils/testutils-truth/src/main/java/androidx/testutils/assertions.kt)
+function from the AndroidX testutils module. The assertions.kt file contains two
+assertThrows functions. The second method, which specifically returns a
+TruthFailureSubject, should be used since it allows for validating additional
+information about the FailureSubject, particularly that it contains specific
+fact messages.
+
+```kotlin
+@Test
+fun testGraphFailure() {
+    with(assertThrows {
+        assertThat(navController).isGraph(R.id.second_test_graph)
+    }) {
+        factValue("expected id").isEqualTo(R.id.second_test_graph.toString())
+        factValue("but was").isEqualTo(navController.graph.id.toString())
+        factValue("current graph is").isEqualTo(navController.graph.toString())
+    }
+}
+```
+
+## Helpful resources
+
+[Truth extension points](https://truth.dev/extension.html)
diff --git a/docs/versioning.md b/docs/versioning.md
new file mode 100644
index 0000000..f1212a3
--- /dev/null
+++ b/docs/versioning.md
@@ -0,0 +1,319 @@
+# Versioning
+
+[TOC]
+
+## Semantic Versioning
+
+Artifacts follow strict semantic versioning. The version for a finalized release
+will follow the format `<major>.<minor>.<bugfix>` with an optional
+`-<alpha|beta><n>` suffix. Internal or nightly releases should use the
+`-SNAPSHOT` suffix to indicate that the release bits are subject to change.
+
+Also check out the [Versioning FAQ](faq.md#version).
+
+### Major (`x.0.0`) {#major}
+
+An artifact's major version indicates a guaranteed forward-compatibility window.
+For example, a developer could update an artifact versioned `2.0.0` to `2.7.3`
+without taking any additional action.
+
+#### When to increment
+
+An artifact *must* increment its major version number in response to breaking
+changes in binary or behavioral compatibility within the library itself _or_ in
+response to breaking changes within a dependency.
+
+For example, if an artifact updates a SemVer-type dependency from `1.0.0` to
+`2.0.0` then the artifact must also bump its own major version number.
+
+An artifact *may in rare cases* increment its major version number to indicate
+an important but non-breaking change in the library. Note, however, that the
+SemVer implications of incrementing the major version are the same as a breaking
+change -- dependent projects _must_ assume the major version change is breaking
+and update their dependency specifications.
+
+#### Ecosystem implications
+
+When an artifact increases its major version, _all_ artifacts that depended on
+the previous major version are no longer considered compatible and must
+explicitly migrate to depend on the new major version.
+
+As a result, if the library ecosystem is slow to adopt a new major version of an
+artifact then developers may end up in a situation where they cannot update an
+artifact because they depend on a library that has not yet adopted the new major
+version.
+
+For this reason, we *strongly* recommend against increasing the major version of
+a “core” artifact that is depended upon by other libraries. “Leaf” artifacts --
+those that apps depend upon directly and are not used by other libraries -- have
+a much easier time increasing their major version.
+
+#### Process requirements
+
+If the artifact has dependencies within Jetpack, owners *must* complete the
+assessment before implementing any breaking changes to binary or behavioral
+compatibility.
+
+Otherwise, owners are *strongly recommended* to complete the assessment before
+implementing any breaking changes to binary or behavioral compatibility, as such
+changes may negatively impact downstream clients in Android git or Google's
+repository. These clients are not part of our pre-submit workflow, but filling
+out the assessment will provide insight into how they will be affected by a
+major version change.
+
+### Minor (`1.x.0`) {#minor}
+
+Minor indicates compatible public API changes. This number is incremented when
+APIs are added, including the addition of `@Deprecated` annotations. Binary
+compatibility must be preserved between minor version changes.
+
+#### Moving between minor versions:
+
+*   A change in the minor revision indicates the addition of binary-compatible
+    APIs. Libraries **must** increment their minor revision when adding APIs.
+    Dependent libraries are not required to update their minimum required
+    version unless they depend on newly-added APIs.
+
+### Bugfix (`1.0.x`) {#bugfix}
+
+Bugfix indicates internal changes to address broken behavior. Care should be
+taken to ensure that existing clients are not broken, including clients that may
+have been working around long-standing broken behavior.
+
+#### Moving between bugfix versions:
+
+*   A change in the bugfix revision indicates changes in behavior to fix bugs.
+    The API surface does not change. Changes to the bugfix version may *only*
+    occur in a release branch. The bugfix revision must always be `.0` in a
+    development branch.
+
+### Pre-release suffixes {#pre-release-suffix}
+
+The pre-release suffix indicates stability and feature completeness of a
+release. A typical release will begin as alpha, move to beta after acting on
+feedback from internal and external clients, move to release candidate for final
+verification, and ultimately move to a finalized build.
+
+Alpha, beta, and release candidate releases are versioned sequentially using a
+leading zero (ex. alpha01, beta11, rc01) for compatibility with the
+lexicographic ordering of versions used by SemVer.
+
+### Snapshot {#snapshot}
+
+Snapshot releases are whatever exists at tip-of-tree. They are only subject to
+the constraints placed on the average commit. Depending on when it's cut, a
+snapshot may even be binary-identical to an alpha, beta, or stable release.
+
+Versioning policies are enforced by the following Gradle tasks:
+
+`checkApi`: ensures that changes to public API are intentional and tracked,
+asking the developer to explicitly run updateApi (see below) if any changes are
+detected
+
+`checkApiRelease`: verifies that API changes between previously released and
+currently under-development versions conform to semantic versioning guarantees
+
+`updateApi`: commits API changes to source control in such a way that they can
+be reviewed in pre-submit via Gerrit API+1 and reviewed in post-submit by API
+Council
+
+`SNAPSHOT`: is automatically added to the version string for all builds that
+occur outside the build server for release branches (ex. ub-androidx-release).
+Local release builds may be forced by passing -Prelease to the Gradle command
+line.
+
+## Picking the right version {#picking-the-right-version}
+
+AndroidX follows [Strict Semantic Versioning](https://semver.org), which means
+that the version code is strongly tied to the API surface. A full version
+consists of revision numbers for major, minor, and bugfix as well as a
+pre-release stage and revision. Correct versioning is, for the most part,
+automatically enforced; however, please check for the following:
+
+### Initial version {#initial-version}
+
+If your library is brand new, your version should start at 1.0.0, e.g.
+`1.0.0-alpha01`.
+
+The initial release within a new version always starts at `alpha01`. Note the
+two-digit revision code, which allows us to do up to 99 revisions within a
+pre-release stage.
+
+### Pre-release stages
+
+A single version will typically move through several revisions within each of
+the pre-release stages: alpha, beta, rc, and stable. Subsequent revisions within
+a stage (ex. alpha, beta) are incremented by 1, ex. `alpha01` is followed by
+`alpha02` with no gaps.
+
+### Moving between pre-release stages and revisions
+
+Libraries are expected to go through a number of pre-release stages within a
+version prior to release, with stricter requirements at each stage to ensure a
+high-quality stable release. The owner for a library should typically submit a
+CL to update the stage or revision when they are ready to perform a public
+release.
+
+Libraries are expected to allow >= 2 weeks per pre-release stage. This 'soaking
+period' gives developers time to try/use each version, find bugs, and ensure a
+quality stable release. Therefore, at minimum:
+
+-   An `alpha` version must be publically available for 2 weeks before releasing
+    a public `beta`
+-   A `beta` version must be publically available for 2 weeks before releasing
+    an public `rc`
+-   A `rc` version must be publically available fore 2 weeks before releasing a
+    public stable version
+
+Your library must meet the following criteria to move your public release to
+each stage:
+
+### Alpha {#alpha}
+
+Alpha releases are expected to be functionally stable, but may have unstable API
+surface or incomplete features. Typically, alphas have not gone through API
+Council review but are expected to have performed a minimum level of validation.
+
+#### Within the `alphaXX` cycle
+
+*   API surface
+    *   Prior to `alpha01` release, API tracking **must** be enabled (either
+        `publish=true` or create an `api` directory) and remain enabled
+    *   May add/remove APIs within `alpha` cycle, but deprecate/remove cycle is
+        strongly recommended.
+*   Testing
+    *   All changes **should** be accompanied by a `Test:` stanza
+    *   All pre-submit and post-submit tests are passing
+    *   Flaky or failing tests **must** be suppressed or fixed within one day
+        (if affecting pre-submit) or three days (if affecting post-submit)
+
+### Beta {#beta}
+
+Beta releases are ready for production use but may contain bugs. They are
+expected to be functionally stable and have highly-stable, feature-complete API
+surface. APIs should have been reviewed by API Council at this stage, and new
+APIs may only be added with approval by API Council. Tests must have 100%
+coverage of public API surface and translations must be 100% complete.
+
+#### Checklist for moving to `beta01`
+
+*   API surface
+    *   Entire API surface has been reviewed by API Council
+    *   All APIs from alpha undergoing deprecate/remove cycle must be removed
+*   Testing
+    *   All public APIs are tested
+    *   All pre-submit and post-submit tests are enabled (e.g. all suppressions
+        are removed) and passing
+    *   Your library passes `./gradlew library:checkReleaseReady`
+*   No experimental features (e.g. `@UseExperimental`) may be used
+*   All dependencies are `beta`, `rc`, or stable
+*   Be able to answer the question "How will developers test their apps against
+    your library?"
+    *   Ideally, this is an integration app with automated tests that cover the
+        main features of the library and/or a `-testing` artifact as seen in
+        other Jetpack libraries
+
+#### Within the `betaXX` cycle
+
+*   API surface
+    *   New APIs discouraged unless P0 or P1 (ship-blocking)
+    *   May not remove `@Experimental` from experimental APIs, see previous item
+        regarding new APIs
+    *   No API removals allowed
+
+### RC {#rc}
+
+Release candidates are expected to be nearly-identical to the final release, but
+may contain critical last-minute fixes for issues found during integration
+testing.
+
+#### Checklist for moving to `rc01`
+
+*   All previous checklists still apply
+*   Release branch, e.g. `androidx-<group_id>-release`, is created
+*   API surface
+    *   Any API changes from `beta` cycle are reviewed by API Council
+*   No **known** P0 or P1 (ship-blocking) issues
+*   All dependencies are `rc` or stable
+
+#### Within the `rcXX` cycle
+
+*   Ship-blocking bug fixes only
+*   All changes must have corresponding tests
+*   No API changes allowed
+
+### Stable {#stable}
+
+Final releases are well-tested, both by internal tests and external clients, and
+their API surface is reviewed and finalized. While APIs may be deprecated and
+removed in future versions, any APIs added at this stage must remain for at
+least a year.
+
+#### Checklist for moving to stable
+
+*   Identical to a previously released `rcXX` (note that this means any bugs
+    that result in a new release candidate will necessarily delay your stable
+    release by a minimum of two weeks)
+*   No changes of any kind allowed
+*   All dependencies are stable
+
+## Updating your version {#update}
+
+A few notes about version updates:
+
+-   The version of your library listed in `androidx-master-dev` should *always*
+    be higher than the version publically available on Google Maven. This allows
+    us to do proper version tracking and API tracking.
+
+-   Version increments must be done before the CL cutoff date (aka the build cut
+    date).
+
+-   **Increments to the next stability suffix** (like `alpha` to `beta`) should
+    be handled by the library owner, with the Jetpack TPM (nickanthony@) CC'd
+    for API+1.
+
+-   Version increments in release branches will need to follow the guide
+    [How to update your version on a release branch](release_branches.md#update-your-version)
+
+-   When you're ready for `rc01`, the increment to `rc01` should be done in
+    `androidx-master-dev` and then your release branch should be snapped to that
+    build. See the guide [Snap your release branch](release_branches.md#snap) on
+    how to do this. After the release branch is snapped to that build, you will
+    need to update your version in `androidx-master-dev` to `alpha01` of the
+    next minor (or major) version.
+
+### Bi-weekly batched releases (every 2 weeks)
+
+If you participate in a bi-weekly (every 2 weeks) batched release, the Jetpack
+TPM will increment versions for you the day after the build cut deadline. The
+increments are defaulted to increments within the same pre-release suffix.
+
+For example, if you are releasing `1.1.0-alpha04`, the day after the build cut,
+the TPM will increment the version to `1.1.0-alpha05` for the next release.
+
+### How to update your version
+
+1.  Update the version listed in
+    `frameworks/support/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt`
+1.  Run `./gradlew <your-lib>:updateApi`. This will create an API txt file for
+    the new version of your library.
+1.  Verify changes with `./gradlew checkApi verifyDependencyVersions`.
+1.  Commit these change as one commit.
+1.  Upload these changes to Gerrit for review.
+
+An example of a version bump can be found here:
+[aosp/833800](https://android-review.googlesource.com/c/platform/frameworks/support/+/833800)
+
+## `-ktx` Modules {#ktx}
+
+Kotlin Extension modules (`-ktx`) for regular Java modules follow the same
+requirements, but with one exception. They must match the version of the Java
+module that they extend.
+
+For example, let's say you are developing a java library
+`androidx.foo:foo-bar:1.1.0-alpha01` and you want to add a kotlin extension
+module `androidx.foo:foo-bar-ktx` module. Your new `androidx.foo:foo-bar-ktx`
+module will start at version `1.1.0-alpha01` instead of `1.0.0-alpha01`.
+
+If your `androidx.foo:foo-bar` module was in version `1.0.0-alpha06`, then the
+kotlin extension module would start in version `1.0.0-alpha06`.
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 77b8b66..7e1ec2e 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -684,6 +684,22 @@
         exif.saveAttributes();
         exif = new ExifInterface(imageFile.getAbsolutePath());
         assertEquals(currentTimeStamp - expectedDatetimeOffsetLongValue, (long) exif.getDateTime());
+
+        // Test that setting null throws NPE
+        try {
+            exif.setDateTime(null);
+            fail();
+        } catch (NullPointerException e) {
+            // Expected
+        }
+
+        // Test that setting negative value throws IAE
+        try {
+            exif.setDateTime(-1L);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
     }
 
     /**
@@ -757,29 +773,80 @@
         assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
     }
 
-    // TODO: Add tests for other variations (e.g. single/double digit number strings)
     @Test
     @LargeTest
-    public void testParsingSubsec() throws IOException {
+    public void testSubsec() throws IOException {
         File imageFile = getFileFromExternalDir(JPEG_WITH_DATETIME_TAG_PRIMARY_FORMAT);
         ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
-        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, /* 0ms */ "000000");
+
+        // Set initial value to 0
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, /* 0ms */ "000");
         exif.saveAttributes();
+        assertEquals("000", exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
         long currentDateTimeValue = exif.getDateTime();
 
-        // Check that TAG_SUBSEC_TIME values starting with zero are supported.
-        // Note: getDateTime() supports only up to 1/1000th of a second.
-        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, /* 1ms */ "001000");
-        exif.saveAttributes();
-        assertEquals(currentDateTimeValue + 1, (long) exif.getDateTime());
-
-        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, /* 10ms */ "010000");
-        exif.saveAttributes();
-        assertEquals(currentDateTimeValue + 10, (long) exif.getDateTime());
-
-        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, /* 100ms */ "100000");
+        // Test that single and double-digit values are set properly.
+        // Note that since SubSecTime tag records fractions of a second, a single-digit value
+        // should be counted as the first decimal value, which is why "1" becomes 100ms and "11"
+        // becomes 110ms.
+        String oneDigitSubSec = "1";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, oneDigitSubSec);
         exif.saveAttributes();
         assertEquals(currentDateTimeValue + 100, (long) exif.getDateTime());
+        assertEquals(oneDigitSubSec, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        String twoDigitSubSec1 = "01";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, twoDigitSubSec1);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 10, (long) exif.getDateTime());
+        assertEquals(twoDigitSubSec1, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        String twoDigitSubSec2 = "11";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, twoDigitSubSec2);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 110, (long) exif.getDateTime());
+        assertEquals(twoDigitSubSec2, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        // Test that 3-digit values are set properly.
+        String hundredMs = "100";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, hundredMs);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 100, (long) exif.getDateTime());
+        assertEquals(hundredMs, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        // Test that values starting with zero are also supported.
+        String oneMsStartingWithZeroes = "001";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, oneMsStartingWithZeroes);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 1, (long) exif.getDateTime());
+        assertEquals(oneMsStartingWithZeroes, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        String tenMsStartingWithZero = "010";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, tenMsStartingWithZero);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 10, (long) exif.getDateTime());
+        assertEquals(tenMsStartingWithZero, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        // Test that values with more than three digits are set properly. getAttribute() should
+        // return the whole string, but getDateTime() should only add the first three digits
+        // because it supports only up to 1/1000th of a second.
+        String fourDigitSubSec = "1234";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, fourDigitSubSec);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 123, (long) exif.getDateTime());
+        assertEquals(fourDigitSubSec, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        String fiveDigitSubSec = "23456";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, fiveDigitSubSec);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 234, (long) exif.getDateTime());
+        assertEquals(fiveDigitSubSec, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
+
+        String sixDigitSubSec = "345678";
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME, sixDigitSubSec);
+        exif.saveAttributes();
+        assertEquals(currentDateTimeValue + 345, (long) exif.getDateTime());
+        assertEquals(sixDigitSubSec, exif.getAttribute(ExifInterface.TAG_SUBSEC_TIME));
     }
 
     @Test
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 76d6f81..4099177 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -5151,9 +5151,21 @@
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void setDateTime(@NonNull Long timeStamp) {
-        long sub = timeStamp % 1000;
+        if (timeStamp == null) {
+            throw new NullPointerException("Timestamp should not be null.");
+        }
+
+        if (timeStamp < 0) {
+            throw new IllegalArgumentException("Timestamp should a positive value.");
+        }
+
+        long subsec = timeStamp % 1000;
+        String subsecString = Long.toString(subsec);
+        for (int i = subsecString.length(); i < 3; i++) {
+            subsecString = "0" + subsecString;
+        }
         setAttribute(TAG_DATETIME, sFormatterPrimary.format(new Date(timeStamp)));
-        setAttribute(TAG_SUBSEC_TIME, Long.toString(sub));
+        setAttribute(TAG_SUBSEC_TIME, subsecString);
     }
 
     /**
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index 6cfa8e7..88435eb 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -36,11 +36,11 @@
     api("androidx.collection:collection-ktx:1.1.0") {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
-    api("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.0-beta01") {
+    api(projectOrArtifact(":lifecycle:lifecycle-livedata-core-ktx")) {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-beta01")
-    api("androidx.savedstate:savedstate-ktx:1.1.0-beta01") {
+    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-ktx"))
+    api(projectOrArtifact(":savedstate:savedstate-ktx")) {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
     api(KOTLIN_STDLIB)
diff --git a/fragment/fragment-testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt b/fragment/fragment-testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt
index 0674e06..a2735f9 100644
--- a/fragment/fragment-testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt
+++ b/fragment/fragment-testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt
@@ -228,7 +228,7 @@
  * If your testing Fragment has a dependency to specific theme such as `Theme.AppCompat`,
  * use the theme ID parameter in [launch] method.
  *
- * @param <F> The Fragment class being tested
+ * @param F The Fragment class being tested
  *
  * @see ActivityScenario a scenario API for Activity
  */
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index 961ddb2..a6c2996 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -38,10 +38,10 @@
     api("androidx.viewpager:viewpager:1.0.0")
     api("androidx.loader:loader:1.0.0")
     api(projectOrArtifact(":activity:activity"))
-    api("androidx.lifecycle:lifecycle-livedata-core:2.3.0-beta01")
-    api("androidx.lifecycle:lifecycle-viewmodel:2.3.0-beta01")
-    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-beta01")
-    api("androidx.savedstate:savedstate:1.1.0-beta01")
+    api(projectOrArtifact(":lifecycle:lifecycle-livedata-core"))
+    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel"))
+    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-savedstate"))
+    api(projectOrArtifact(":savedstate:savedstate"))
     api("androidx.annotation:annotation-experimental:1.0.0")
 
     androidTestImplementation("androidx.appcompat:appcompat:1.1.0", {
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 c10b6d9..c5706ec 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -755,6 +755,50 @@
         }
     }
 
+    @Test
+    fun removingFragmentAnimationChange() {
+        waitForAnimationReady()
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = AnimationFragment()
+        fm.beginTransaction()
+            .setReorderingAllowed(true)
+            .add(R.id.fragmentContainer, fragment1, "fragment1")
+            .addToBackStack("fragment1")
+            .commit()
+        activityRule.waitForExecution()
+
+        val fragment2 = AnimationFragment()
+
+        fm.beginTransaction()
+            .setReorderingAllowed(true)
+            .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
+            .replace(R.id.fragmentContainer, fragment2, "fragment2")
+            .addToBackStack("fragment2")
+            .commit()
+
+        activityRule.waitForExecution()
+
+        activityRule.runOnUiThread {
+            fm.popBackStack("fragment1", 0)
+
+            val fragment3 = AnimationFragment()
+
+            fm.beginTransaction()
+                .setReorderingAllowed(true)
+                .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
+                .replace(R.id.fragmentContainer, fragment3, "fragment3")
+                .addToBackStack("fragment3")
+                .commit()
+        }
+
+        activityRule.waitForExecution()
+
+        activityRule.runOnUiThread {
+            assertThat(fragment2.loadedAnimation).isEqualTo(EXIT)
+        }
+    }
+
     private fun assertEnterPopExit(fragment: AnimationFragment) {
         assertFragmentAnimation(fragment, 1, true, ENTER)
 
@@ -842,6 +886,7 @@
         var animation: Animation? = null
         var enter: Boolean = false
         var resourceId: Int = 0
+        var loadedAnimation = 0
 
         override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
             if (nextAnim == 0 ||
@@ -849,6 +894,7 @@
             ) {
                 return null
             }
+            loadedAnimation = nextAnim
             numAnimators++
             animation = TranslateAnimation(-10f, 0f, 0f, 0f)
             (animation as TranslateAnimation).duration = 1
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt
index 1775a46..1776faf 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt
@@ -42,7 +42,7 @@
     @Test
     fun fragmentWithOptionsMenu() {
         activityRule.setContentView(R.layout.simple_container)
-        val fragment = OptionsMenuFragment()
+        val fragment = MenuFragment()
         val fm = activityRule.activity.supportFragmentManager
         fm.beginTransaction()
             .add(R.id.fragmentContainer, fragment)
@@ -112,6 +112,30 @@
     }
 
     @Test
+    fun childFragmentWithPrepareOptionsMenuParentMenuVisibilityFalse() {
+        activityRule.setContentView(R.layout.simple_container)
+        val parent = ParentOptionsMenuFragment()
+        val fm = activityRule.activity.supportFragmentManager
+
+        parent.setMenuVisibility(false)
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, parent, "parent")
+            .commit()
+        activityRule.executePendingTransactions()
+
+        assertWithMessage("Fragment should not have an options menu")
+            .that(parent.hasOptionsMenu()).isFalse()
+        assertWithMessage("Child fragment should have an options menu")
+            .that(parent.childFragmentManager.checkForMenus()).isTrue()
+
+        activityRule.runOnUiThread {
+            assertWithMessage("child fragment onCreateOptions menu was not called")
+                .that(parent.childFragment.onPrepareOptionsMenuCountDownLatch.count)
+                .isEqualTo(1)
+        }
+    }
+
+    @Test
     fun parentAndChildFragmentWithOptionsMenu() {
         activityRule.setContentView(R.layout.simple_container)
         val parent = ParentOptionsMenuFragment(true)
@@ -165,7 +189,7 @@
         activityRule.executePendingTransactions()
 
         parent.childFragmentManager.beginTransaction()
-            .add(R.id.fragmentContainer2, OptionsMenuFragment())
+            .add(R.id.fragmentContainer2, MenuFragment())
             .commit()
         activityRule.executePendingTransactions()
 
@@ -175,8 +199,9 @@
             .that(parent.mChildFragmentManager.checkForMenus()).isTrue()
     }
 
-    class OptionsMenuFragment : StrictViewFragment(R.layout.fragment_a) {
+    class MenuFragment : StrictViewFragment(R.layout.fragment_a) {
         val onCreateOptionsMenuCountDownLatch = CountDownLatch(1)
+        val onPrepareOptionsMenuCountDownLatch = CountDownLatch(1)
 
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
@@ -188,12 +213,17 @@
             onCreateOptionsMenuCountDownLatch.countDown()
             inflater.inflate(R.menu.example_menu, menu)
         }
+
+        override fun onPrepareOptionsMenu(menu: Menu) {
+            super.onPrepareOptionsMenu(menu)
+            onPrepareOptionsMenuCountDownLatch.countDown()
+        }
     }
 
     class ParentOptionsMenuFragment(
         val createMenu: Boolean = false
     ) : StrictViewFragment(R.layout.double_container) {
-        val childFragment = OptionsMenuFragment()
+        val childFragment = MenuFragment()
 
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
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 6445a872..f3e9d37 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -2402,7 +2402,7 @@
      */
     private void setVisibleRemovingFragment(@NonNull Fragment f) {
         ViewGroup container = getFragmentContainer(f);
-        if (container != null && f.mView != null) {
+        if (container != null && f.getNextAnim() > 0) {
             if (container.getTag(R.id.visible_removing_fragment_view_tag) == null) {
                 container.setTag(R.id.visible_removing_fragment_view_tag, f);
             }
@@ -3193,7 +3193,7 @@
         boolean show = false;
         for (Fragment f : mFragmentStore.getFragments()) {
             if (f != null) {
-                if (f.performPrepareOptionsMenu(menu)) {
+                if (isParentMenuVisible(f) && f.performPrepareOptionsMenu(menu)) {
                     show = true;
                 }
             }
diff --git a/inspection/inspection-gradle-plugin/build.gradle b/inspection/inspection-gradle-plugin/build.gradle
index aff9915..b09f862 100644
--- a/inspection/inspection-gradle-plugin/build.gradle
+++ b/inspection/inspection-gradle-plugin/build.gradle
@@ -36,7 +36,7 @@
     implementation(AGP_STABLE)
     implementation(KOTLIN_STDLIB)
     implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.13")
-    implementation("org.anarres.jarjar:jarjar-gradle:1.0.1")
+    implementation("com.github.jengelman.gradle.plugins:shadow:5.2.0")
 
     testImplementation(project(":internal-testutils-gradle-plugin"))
     testImplementation gradleTestKit()
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
index e8370d5..b10b84b 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
@@ -19,7 +19,6 @@
 import com.android.build.gradle.BaseExtension
 import com.android.build.gradle.api.BaseVariant
 import com.android.build.gradle.api.LibraryVariant
-import org.anarres.gradle.plugin.jarjar.JarjarTask
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.file.ConfigurableFileCollection
@@ -30,6 +29,7 @@
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.TaskAction
 import org.gradle.api.tasks.TaskProvider
+import org.gradle.api.tasks.bundling.Jar
 import java.io.File
 
 abstract class DexInspectorTask : DefaultTask() {
@@ -73,13 +73,13 @@
 fun Project.registerDexInspectorTask(
     variant: BaseVariant,
     extension: BaseExtension,
-    jarJar: TaskProvider<JarjarTask>
+    jar: TaskProvider<out Jar>
 ): TaskProvider<DexInspectorTask> {
     return tasks.register(variant.taskName("dexInspector"), DexInspectorTask::class.java) {
         it.setDx(extension.sdkDirectory, extension.buildToolsVersion)
-        it.jars.from(jarJar.get().destinationPath)
+        it.jars.from(jar.get().destinationDirectory)
         val out = File(taskWorkingDir(variant, "dexedInspector"), "${project.name}.jar")
         it.outputFile.set(out)
-        it.dependsOn(jarJar)
+        it.dependsOn(jar)
     }
 }
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
index 7751681..e5a1eba 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/InspectionPlugin.kt
@@ -52,8 +52,8 @@
                 if (variant.name == "release") {
                     foundReleaseVariant = true
                     val unzip = project.registerUnzipTask(variant)
-                    val jarJar = project.registerJarJarDependenciesTask(variant, unzip)
-                    dexTask = project.registerDexInspectorTask(variant, libExtension, jarJar)
+                    val shadowJar = project.registerShadowDependenciesTask(variant, unzip)
+                    dexTask = project.registerDexInspectorTask(variant, libExtension, shadowJar)
                 }
             }
             libExtension.sourceSets.findByName("main")!!.resources.srcDirs(
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/JarJarDependenciesTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
similarity index 84%
rename from inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/JarJarDependenciesTask.kt
rename to inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
index 33b6ca2b..14893cb 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/JarJarDependenciesTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
@@ -17,7 +17,7 @@
 package androidx.inspection.gradle
 
 import com.android.build.gradle.api.BaseVariant
-import org.anarres.gradle.plugin.jarjar.JarjarTask
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
 import org.gradle.api.Project
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.TaskProvider
@@ -27,29 +27,29 @@
 
 // variant.taskName relies on @ExperimentalStdlibApi api
 @ExperimentalStdlibApi
-fun Project.registerJarJarDependenciesTask(
+fun Project.registerShadowDependenciesTask(
     variant: BaseVariant,
     zipTask: TaskProvider<Copy>
-): TaskProvider<JarjarTask> {
+): TaskProvider<ShadowJar> {
     val uberJar = registerUberJarTask(variant)
     return tasks.register(
-        variant.taskName("jarJarDependencies"),
-        JarjarTask::class.java
+        variant.taskName("shadowDependencies"),
+        ShadowJar::class.java
     ) {
         it.dependsOn(uberJar)
         val fileTree = project.fileTree(zipTask.get().destinationDir)
         fileTree.include("**/*.jar")
         it.from(fileTree)
-        it.destinationDir = taskWorkingDir(variant, "jarJar")
-        it.destinationName = "${project.name}-shadowed.jar"
+        it.destinationDirectory.set(taskWorkingDir(variant, "shadowedJar"))
+        it.archiveBaseName.set("${project.name}-shadowed")
         it.dependsOn(zipTask)
         val prefix = "deps.${project.name.replace('-', '.')}"
         it.doFirst {
-            val task = it as JarjarTask
+            val task = it as ShadowJar
             val input = uberJar.get().outputs.files
             task.from(input)
             input.extractPackageNames().forEach { packageName ->
-                task.classRename("$packageName.**", "$prefix.$packageName.@1")
+                task.relocate(packageName, "$prefix.$packageName")
             }
         }
     }
diff --git a/jetifier/jetifier/preprocessor/src/main/kotlin/com/android/tools/build/jetifier/preprocessor/Main.kt b/jetifier/jetifier/preprocessor/src/main/kotlin/com/android/tools/build/jetifier/preprocessor/Main.kt
index 167669e..ab97f41 100644
--- a/jetifier/jetifier/preprocessor/src/main/kotlin/com/android/tools/build/jetifier/preprocessor/Main.kt
+++ b/jetifier/jetifier/preprocessor/src/main/kotlin/com/android/tools/build/jetifier/preprocessor/Main.kt
@@ -42,7 +42,7 @@
             isRequired = false
         )
 
-        private fun createOption(
+        internal fun createOption(
             argName: String,
             desc: String,
             isRequired: Boolean = true,
@@ -94,4 +94,4 @@
 
 fun main(args: Array<String>) {
     Main().run(args)
-}
\ No newline at end of file
+}
diff --git a/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt b/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt
index df0666f..71dafe2 100644
--- a/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt
+++ b/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt
@@ -64,9 +64,9 @@
 
         val inputFile = File(javaClass.getResource(inputZipPath).file)
 
-        @Suppress("DEPRECATION")
+        @Suppress("DEPRECATION") // b/174695914
         val tempDir = createTempDir()
-        @Suppress("DEPRECATION")
+        @Suppress("DEPRECATION") // b/174695914
         val expectedFile = File(createTempDir(), "test.zip")
 
         @Suppress("deprecation")
@@ -103,9 +103,9 @@
 
         val inputFile = File(javaClass.getResource(inputZipPath).file)
 
-        @Suppress("DEPRECATION")
+        @Suppress("DEPRECATION") // b/174695914
         val tempDir = createTempDir()
-        @Suppress("DEPRECATION")
+        @Suppress("DEPRECATION") // b/174695914
         val expectedFile = File(createTempDir(), "test.zip")
 
         @Suppress("deprecation")
diff --git a/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/Main.kt b/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/Main.kt
index 587c1ce..af71151 100644
--- a/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/Main.kt
+++ b/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/Main.kt
@@ -102,7 +102,7 @@
             isRequired = false
         )
 
-        private fun createOption(
+        internal fun createOption(
             argName: String,
             argNameLong: String,
             desc: String,
@@ -166,7 +166,7 @@
 
         val fileMappings = mutableSetOf<FileMapping>()
         if (rebuildTopOfTree) {
-            @Suppress("DEPRECATION")
+            @Suppress("DEPRECATION") // b/174695914
             val tempFile = createTempFile(suffix = "zip")
             fileMappings.add(FileMapping(input, tempFile))
         } else {
diff --git a/leanback/leanback-paging/build.gradle b/leanback/leanback-paging/build.gradle
index 7a7e046..63486ac 100644
--- a/leanback/leanback-paging/build.gradle
+++ b/leanback/leanback-paging/build.gradle
@@ -12,8 +12,8 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
-    api(project(":leanback:leanback"))
-    api("androidx.paging:paging-runtime:3.0.0-alpha09")
+    api("androidx.leanback:leanback:1.1.0-beta01")
+    api(project(":paging:paging-runtime"))
 
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/leanback/leanback-paging/src/androidTest/java/androidx/leanback/paging/PagingDataAdapterTest.kt b/leanback/leanback-paging/src/androidTest/java/androidx/leanback/paging/PagingDataAdapterTest.kt
index 85e2137..7e4b192 100644
--- a/leanback/leanback-paging/src/androidTest/java/androidx/leanback/paging/PagingDataAdapterTest.kt
+++ b/leanback/leanback-paging/src/androidTest/java/androidx/leanback/paging/PagingDataAdapterTest.kt
@@ -19,14 +19,12 @@
 import androidx.paging.CombinedLoadStates
 import androidx.paging.ExperimentalPagingApi
 import androidx.paging.LoadState
-import androidx.paging.LoadType
 import androidx.paging.Pager
 import androidx.paging.PagingConfig
 import androidx.paging.PagingData
 import androidx.paging.TestPagingSource
 import androidx.paging.assertEvents
 import androidx.paging.localLoadStatesOf
-import androidx.paging.toCombinedLoadStatesLocal
 import androidx.recyclerview.widget.DiffUtil
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -139,15 +137,18 @@
         // empty previous list.
         assertEvents(
             listOf(
-                LoadType.REFRESH to LoadState.Loading,
-                LoadType.REFRESH to LoadState.NotLoading(endOfPaginationReached = false)
-            ).toCombinedLoadStatesLocal(),
+                localLoadStatesOf(),
+                localLoadStatesOf(refreshLocal = LoadState.Loading),
+                localLoadStatesOf(
+                    refreshLocal = LoadState.NotLoading(endOfPaginationReached = false)
+                ),
+            ),
             loadEvents
         )
         loadEvents.clear()
         job.cancel()
 
-        pagingDataAdapter.submitData(TestLifecycleOwner().lifecycle, PagingData.empty<Int>())
+        pagingDataAdapter.submitData(TestLifecycleOwner().lifecycle, PagingData.empty())
         advanceUntilIdle()
         // Assert that all load state updates are sent, even when differ enters fast path for
         // empty next list.
diff --git a/leanback/leanback-preference/build.gradle b/leanback/leanback-preference/build.gradle
index caedc44..81faf4c 100644
--- a/leanback/leanback-preference/build.gradle
+++ b/leanback/leanback-preference/build.gradle
@@ -13,7 +13,7 @@
     api("androidx.appcompat:appcompat:1.0.0")
     api("androidx.recyclerview:recyclerview:1.0.0")
     api("androidx.preference:preference:1.1.0")
-    api(project(":leanback:leanback"))
+    api("androidx.leanback:leanback:1.1.0-beta01")
 }
 
 android {
diff --git a/leanback/leanback-tab/build.gradle b/leanback/leanback-tab/build.gradle
index d58b65a..4481a37 100644
--- a/leanback/leanback-tab/build.gradle
+++ b/leanback/leanback-tab/build.gradle
@@ -46,7 +46,7 @@
 androidx {
     name = "AndroidX Leanback Tab"
     publish = Publish.SNAPSHOT_AND_RELEASE
-    mavenVersion = LibraryVersions.LEANBACK
+    mavenVersion = LibraryVersions.LEANBACK_TAB
     mavenGroup = LibraryGroups.LEANBACK
     inceptionYear = "2020"
     description = "This library adds top tab navigation component to be used in TV"
diff --git a/leanback/leanback/api/1.1.0-beta01.txt b/leanback/leanback/api/1.1.0-beta01.txt
index 808452b..d92b77a 100644
--- a/leanback/leanback/api/1.1.0-beta01.txt
+++ b/leanback/leanback/api/1.1.0-beta01.txt
@@ -883,6 +883,7 @@
     method @Deprecated public void onCreate(android.os.Bundle!);
     method @Deprecated public android.view.View! onCreateView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
     method @Deprecated public void onDestroy();
+    method @Deprecated public void onDestroyView();
     method @Deprecated public void onPause();
     method @Deprecated public void onRequestPermissionsResult(int, String![]!, int[]!);
     method @Deprecated public void onResume();
diff --git a/leanback/leanback/api/current.txt b/leanback/leanback/api/current.txt
index 808452b..d92b77a 100644
--- a/leanback/leanback/api/current.txt
+++ b/leanback/leanback/api/current.txt
@@ -883,6 +883,7 @@
     method @Deprecated public void onCreate(android.os.Bundle!);
     method @Deprecated public android.view.View! onCreateView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
     method @Deprecated public void onDestroy();
+    method @Deprecated public void onDestroyView();
     method @Deprecated public void onPause();
     method @Deprecated public void onRequestPermissionsResult(int, String![]!, int[]!);
     method @Deprecated public void onResume();
diff --git a/leanback/leanback/api/public_plus_experimental_1.1.0-beta01.txt b/leanback/leanback/api/public_plus_experimental_1.1.0-beta01.txt
index 808452b..d92b77a 100644
--- a/leanback/leanback/api/public_plus_experimental_1.1.0-beta01.txt
+++ b/leanback/leanback/api/public_plus_experimental_1.1.0-beta01.txt
@@ -883,6 +883,7 @@
     method @Deprecated public void onCreate(android.os.Bundle!);
     method @Deprecated public android.view.View! onCreateView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
     method @Deprecated public void onDestroy();
+    method @Deprecated public void onDestroyView();
     method @Deprecated public void onPause();
     method @Deprecated public void onRequestPermissionsResult(int, String![]!, int[]!);
     method @Deprecated public void onResume();
diff --git a/leanback/leanback/api/public_plus_experimental_current.txt b/leanback/leanback/api/public_plus_experimental_current.txt
index 808452b..d92b77a 100644
--- a/leanback/leanback/api/public_plus_experimental_current.txt
+++ b/leanback/leanback/api/public_plus_experimental_current.txt
@@ -883,6 +883,7 @@
     method @Deprecated public void onCreate(android.os.Bundle!);
     method @Deprecated public android.view.View! onCreateView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
     method @Deprecated public void onDestroy();
+    method @Deprecated public void onDestroyView();
     method @Deprecated public void onPause();
     method @Deprecated public void onRequestPermissionsResult(int, String![]!, int[]!);
     method @Deprecated public void onResume();
diff --git a/leanback/leanback/api/restricted_1.1.0-beta01.txt b/leanback/leanback/api/restricted_1.1.0-beta01.txt
index 981b38c..65d5941 100644
--- a/leanback/leanback/api/restricted_1.1.0-beta01.txt
+++ b/leanback/leanback/api/restricted_1.1.0-beta01.txt
@@ -924,6 +924,7 @@
     method @Deprecated public void onCreate(android.os.Bundle!);
     method @Deprecated public android.view.View! onCreateView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
     method @Deprecated public void onDestroy();
+    method @Deprecated public void onDestroyView();
     method @Deprecated public void onPause();
     method @Deprecated public void onRequestPermissionsResult(int, String![]!, int[]!);
     method @Deprecated public void onResume();
diff --git a/leanback/leanback/api/restricted_current.txt b/leanback/leanback/api/restricted_current.txt
index 981b38c..65d5941 100644
--- a/leanback/leanback/api/restricted_current.txt
+++ b/leanback/leanback/api/restricted_current.txt
@@ -924,6 +924,7 @@
     method @Deprecated public void onCreate(android.os.Bundle!);
     method @Deprecated public android.view.View! onCreateView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
     method @Deprecated public void onDestroy();
+    method @Deprecated public void onDestroyView();
     method @Deprecated public void onPause();
     method @Deprecated public void onRequestPermissionsResult(int, String![]!, int[]!);
     method @Deprecated public void onResume();
diff --git a/leanback/leanback/build.gradle b/leanback/leanback/build.gradle
index df6b059f..8e03560 100644
--- a/leanback/leanback/build.gradle
+++ b/leanback/leanback/build.gradle
@@ -15,7 +15,7 @@
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.media:media:1.0.0")
     api("androidx.fragment:fragment:1.0.0")
-    api project(":recyclerview:recyclerview")
+    api("androidx.recyclerview:recyclerview:1.2.0-beta01")
     api("androidx.appcompat:appcompat:1.0.0")
 
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/leanback/leanback/src/androidTest/generatev4.py b/leanback/leanback/src/androidTest/generatev4.py
index c540b2a..d05788b 100755
--- a/leanback/leanback/src/androidTest/generatev4.py
+++ b/leanback/leanback/src/androidTest/generatev4.py
@@ -75,7 +75,8 @@
 
 ####### generate XXXFragmentTest classes #######
 
-testcls = ['Browse', 'GuidedStep', 'VerticalGrid', 'Playback', 'Video', 'Details', 'Rows', 'Headers']
+testcls = ['Browse', 'GuidedStep', 'VerticalGrid', 'Playback', 'Video', 'Details', 'Rows',
+'Headers', 'Search']
 
 for w in testcls:
     print "copy {}SupporFragmentTest to {}FragmentTest".format(w, w)
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchFragmentTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchFragmentTest.java
new file mode 100644
index 0000000..0565f5f
--- /dev/null
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchFragmentTest.java
@@ -0,0 +1,157 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from SearchSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * 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.leanback.app;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import android.app.Fragment;
+import androidx.leanback.test.R;
+import androidx.leanback.testutils.LeakDetector;
+import androidx.leanback.testutils.PollingCheck;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.HeaderItem;
+import androidx.leanback.widget.ListRow;
+import androidx.leanback.widget.ListRowPresenter;
+import androidx.leanback.widget.ObjectAdapter;
+import androidx.leanback.widget.VerticalGridView;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.testutils.AnimationTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@AnimationTest
+@RunWith(AndroidJUnit4.class)
+public class SearchFragmentTest extends SingleFragmentTestBase {
+
+    static final StringPresenter CARD_PRESENTER = new StringPresenter();
+
+    static void loadData(ArrayObjectAdapter adapter, int numRows, int repeatPerRow) {
+        for (int i = 0; i < numRows; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(CARD_PRESENTER);
+            int index = 0;
+            for (int j = 0; j < repeatPerRow; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public static final class F_LeakFragment extends SearchFragment
+            implements SearchFragment.SearchResultProvider {
+        ArrayObjectAdapter mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            loadData(mRowsAdapter, 10, 1);
+        }
+
+        @Override
+        public ObjectAdapter getResultsAdapter() {
+            return mRowsAdapter;
+        }
+
+        @Override
+        public boolean onQueryTextChange(String newQuery) {
+            return true;
+        }
+
+        @Override
+        public boolean onQueryTextSubmit(String query) {
+            return true;
+        }
+    }
+
+    public static final class EmptyFragment extends Fragment {
+        EditText mEditText;
+
+        @Override
+        public View onCreateView(
+                final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            return mEditText = new EditText(container.getContext());
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            // focus IME on the new fragment because there is a memory leak that IME remembers
+            // last editable view, which will cause a false reporting of leaking View.
+            InputMethodManager imm =
+                    (InputMethodManager) getActivity()
+                            .getSystemService(Context.INPUT_METHOD_SERVICE);
+            mEditText.requestFocus();
+            imm.showSoftInput(mEditText, 0);
+        }
+
+        @Override
+        public void onDestroyView() {
+            mEditText = null;
+            super.onDestroyView();
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) // API 17 retains local Variable
+    @Test
+    public void viewLeakTest() throws Throwable {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(F_LeakFragment.class,
+                1000);
+
+        VerticalGridView gridView = ((SearchFragment) activity.getTestFragment())
+                .getRowsFragment().getVerticalGridView();
+        LeakDetector leakDetector = new LeakDetector();
+        leakDetector.observeObject(gridView);
+        leakDetector.observeObject(gridView.getRecycledViewPool());
+        for (int i = 0; i < gridView.getChildCount(); i++) {
+            leakDetector.observeObject(gridView.getChildAt(i));
+        }
+        gridView = null;
+        EmptyFragment emptyFragment = new EmptyFragment();
+        activity.getFragmentManager().beginTransaction()
+                .replace(R.id.main_frame, emptyFragment)
+                .addToBackStack("BK")
+                .commit();
+
+        PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return emptyFragment.isResumed();
+            }
+        });
+        leakDetector.assertNoLeak();
+    }
+}
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchSupportFragmentTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchSupportFragmentTest.java
new file mode 100644
index 0000000..446400b
--- /dev/null
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SearchSupportFragmentTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.leanback.app;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.fragment.app.Fragment;
+import androidx.leanback.test.R;
+import androidx.leanback.testutils.LeakDetector;
+import androidx.leanback.testutils.PollingCheck;
+import androidx.leanback.widget.ArrayObjectAdapter;
+import androidx.leanback.widget.HeaderItem;
+import androidx.leanback.widget.ListRow;
+import androidx.leanback.widget.ListRowPresenter;
+import androidx.leanback.widget.ObjectAdapter;
+import androidx.leanback.widget.VerticalGridView;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.testutils.AnimationTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@AnimationTest
+@RunWith(AndroidJUnit4.class)
+public class SearchSupportFragmentTest extends SingleSupportFragmentTestBase {
+
+    static final StringPresenter CARD_PRESENTER = new StringPresenter();
+
+    static void loadData(ArrayObjectAdapter adapter, int numRows, int repeatPerRow) {
+        for (int i = 0; i < numRows; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(CARD_PRESENTER);
+            int index = 0;
+            for (int j = 0; j < repeatPerRow; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepSupportFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public static final class F_LeakFragment extends SearchSupportFragment
+            implements SearchSupportFragment.SearchResultProvider {
+        ArrayObjectAdapter mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            loadData(mRowsAdapter, 10, 1);
+        }
+
+        @Override
+        public ObjectAdapter getResultsAdapter() {
+            return mRowsAdapter;
+        }
+
+        @Override
+        public boolean onQueryTextChange(String newQuery) {
+            return true;
+        }
+
+        @Override
+        public boolean onQueryTextSubmit(String query) {
+            return true;
+        }
+    }
+
+    public static final class EmptyFragment extends Fragment {
+        EditText mEditText;
+
+        @Override
+        public View onCreateView(
+                final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            return mEditText = new EditText(container.getContext());
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            // focus IME on the new fragment because there is a memory leak that IME remembers
+            // last editable view, which will cause a false reporting of leaking View.
+            InputMethodManager imm =
+                    (InputMethodManager) getActivity()
+                            .getSystemService(Context.INPUT_METHOD_SERVICE);
+            mEditText.requestFocus();
+            imm.showSoftInput(mEditText, 0);
+        }
+
+        @Override
+        public void onDestroyView() {
+            mEditText = null;
+            super.onDestroyView();
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) // API 17 retains local Variable
+    @Test
+    public void viewLeakTest() throws Throwable {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(F_LeakFragment.class,
+                1000);
+
+        VerticalGridView gridView = ((SearchSupportFragment) activity.getTestFragment())
+                .getRowsSupportFragment().getVerticalGridView();
+        LeakDetector leakDetector = new LeakDetector();
+        leakDetector.observeObject(gridView);
+        leakDetector.observeObject(gridView.getRecycledViewPool());
+        for (int i = 0; i < gridView.getChildCount(); i++) {
+            leakDetector.observeObject(gridView.getChildAt(i));
+        }
+        gridView = null;
+        EmptyFragment emptyFragment = new EmptyFragment();
+        activity.getSupportFragmentManager().beginTransaction()
+                .replace(R.id.main_frame, emptyFragment)
+                .addToBackStack("BK")
+                .commit();
+
+        PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return emptyFragment.isResumed();
+            }
+        });
+        leakDetector.assertNoLeak();
+    }
+}
diff --git a/leanback/leanback/src/main/java/androidx/leanback/app/SearchFragment.java b/leanback/leanback/src/main/java/androidx/leanback/app/SearchFragment.java
index 21cc610..644405a 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/app/SearchFragment.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/app/SearchFragment.java
@@ -424,6 +424,13 @@
     }
 
     @Override
+    public void onDestroyView() {
+        mSearchBar = null;
+        mRowsFragment = null;
+        super.onDestroyView();
+    }
+
+    @Override
     public void onDestroy() {
         releaseAdapter();
         super.onDestroy();
diff --git a/leanback/leanback/src/main/java/androidx/leanback/app/SearchSupportFragment.java b/leanback/leanback/src/main/java/androidx/leanback/app/SearchSupportFragment.java
index b28c4ec..124da0b 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/app/SearchSupportFragment.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/app/SearchSupportFragment.java
@@ -419,6 +419,13 @@
     }
 
     @Override
+    public void onDestroyView() {
+        mSearchBar = null;
+        mRowsSupportFragment = null;
+        super.onDestroyView();
+    }
+
+    @Override
     public void onDestroy() {
         releaseAdapter();
         super.onDestroy();
diff --git a/lifecycle/lifecycle-service/build.gradle b/lifecycle/lifecycle-service/build.gradle
index 1f4f5dff..2b81a01 100644
--- a/lifecycle/lifecycle-service/build.gradle
+++ b/lifecycle/lifecycle-service/build.gradle
@@ -31,7 +31,7 @@
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
-    androidTestImplementation("androidx.legacy:legacy-support-core-utils:1.0.0")
+    androidTestImplementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
 }
 
 androidx {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
index 314443b..8e3a3fb 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.lifecycle.viewmodel.savedstate
 
+import android.os.Bundle
 import androidx.annotation.MainThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.SavedStateHandle
@@ -169,9 +170,11 @@
     fun testKeySet() {
         val accessor = SavedStateHandle()
         accessor.set("s", "pb")
+        accessor.getLiveData<String>("no value ld")
         accessor.getLiveData<String>("ld").value = "a"
-        assertThat(accessor.keys().size).isEqualTo(2)
-        assertThat(accessor.keys()).containsExactly("s", "ld")
+        accessor.setSavedStateProvider("provider") { Bundle() }
+        assertThat(accessor.keys().size).isEqualTo(4)
+        assertThat(accessor.keys()).containsExactly("s", "ld", "provider", "no value ld")
     }
 
     @MainThread
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
index 146eb52..af17aa1 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
@@ -32,8 +32,8 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -217,11 +217,17 @@
 
     /**
      * Returns all keys contained in this {@link SavedStateHandle}
+     * <p>
+     * Returned set contains all keys: keys used to get LiveData-s, to set SavedStateProviders and
+     * keys used in regular {@link #set(String, Object)}.
      */
     @MainThread
     @NonNull
     public Set<String> keys() {
-        return Collections.unmodifiableSet(mRegular.keySet());
+        HashSet<String> allKeys = new HashSet<>(mRegular.keySet());
+        allKeys.addAll(mSavedStateProviders.keySet());
+        allKeys.addAll(mLiveDatas.keySet());
+        return allKeys;
     }
 
     /**
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaNotificationHandler.java b/media2/session/src/main/java/androidx/media2/session/MediaNotificationHandler.java
index de2971e..07df689 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaNotificationHandler.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaNotificationHandler.java
@@ -122,6 +122,34 @@
         mServiceInstance.startForeground(id, notification);
     }
 
+    /**
+     * Updates the notification when needed.
+     * This will be called when the current media item is changed.
+     */
+    @Override
+    public void onNotificationUpdateNeeded(MediaSession session) {
+        MediaSessionService.MediaNotification mediaNotification =
+                mServiceInstance.onUpdateNotification(session);
+        if (mediaNotification == null) {
+            // The service implementation doesn't want to use the automatic start/stopForeground
+            // feature.
+            return;
+        }
+
+        int id = mediaNotification.getNotificationId();
+        Notification notification = mediaNotification.getNotification();
+
+        if (Build.VERSION.SDK_INT >= 21) {
+            // Call Notification.MediaStyle#setMediaSession() indirectly.
+            android.media.session.MediaSession.Token fwkToken =
+                    (android.media.session.MediaSession.Token)
+                            session.getSessionCompat().getSessionToken().getToken();
+            notification.extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, fwkToken);
+        }
+
+        mNotificationManager.notify(id, notification);
+    }
+
     @Override
     public void onSessionClosed(MediaSession session) {
         mServiceInstance.removeSession(session);
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSession.java b/media2/session/src/main/java/androidx/media2/session/MediaSession.java
index f6447861..5ba8b5d 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSession.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSession.java
@@ -787,6 +787,12 @@
             }
         }
 
+        final void onCurrentMediaItemChanged(MediaSession session) {
+            if (mForegroundServiceEventCallback != null) {
+                mForegroundServiceEventCallback.onNotificationUpdateNeeded(session);
+            }
+        }
+
         final void onSessionClosed(MediaSession session) {
             if (mForegroundServiceEventCallback != null) {
                 mForegroundServiceEventCallback.onSessionClosed(session);
@@ -799,6 +805,7 @@
 
         abstract static class ForegroundServiceEventCallback {
             public void onPlayerStateChanged(MediaSession session, @PlayerState int state) {}
+            public void onNotificationUpdateNeeded(MediaSession session) {}
             public void onSessionClosed(MediaSession session) {}
         }
     }
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
index d1db6dd..51caf49 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
@@ -1389,6 +1389,7 @@
                 item.addOnMetadataChangedListener(session.mCallbackExecutor, this);
             }
             mMediaItem = item;
+            session.getCallback().onCurrentMediaItemChanged(session.getInstance());
 
             boolean notifyingPended = false;
             if (item != null) {
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionService.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionService.java
index cfed886..d8a459e 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionService.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionService.java
@@ -222,7 +222,7 @@
      * notification UI.
      * <p>
      * This would be called on {@link MediaSession}'s callback executor when player state is
-     * changed.
+     * changed, or when the current media item of the session is changed.
      * <p>
      * With the notification returned here, the service becomes foreground service when the playback
      * is started. Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java
index e0fb863..b9520b8 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionServiceNotificationTest.java
@@ -101,11 +101,10 @@
                 new SessionToken(mContext, MOCK_MEDIA2_SESSION_SERVICE), true, null);
 
         // Set current media item.
-        final String mediaId = "testMediaId";
         Bitmap albumArt = BitmapFactory.decodeResource(mContext.getResources(),
                 androidx.media2.test.service.R.drawable.big_buck_bunny);
         MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(METADATA_KEY_MEDIA_ID, mediaId)
+                .putText(METADATA_KEY_MEDIA_ID, "testMediaId")
                 .putText(METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
                 .putText(METADATA_KEY_ARTIST, "Test Artist Name")
                 .putBitmap(METADATA_KEY_ALBUM_ART, albumArt)
@@ -122,4 +121,63 @@
         mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
         Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
     }
+
+    @Test
+    @Ignore("Comment out this line and manually run the test.")
+    public void notificationUpdatedWhenCurrentMediaItemChanged() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
+            @Override
+            public SessionCommandGroup onConnect(@NonNull MediaSession session,
+                    @NonNull ControllerInfo controller) {
+                if (CLIENT_PACKAGE_NAME.equals(controller.getPackageName())) {
+                    mSession = session;
+                    // Change the player and playlist agent with ours.
+                    session.updatePlayer(mPlayer);
+                    latch.countDown();
+                }
+                return super.onConnect(session, controller);
+            }
+        };
+        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
+
+        // Create a controller to start the service.
+        RemoteMediaController controller = createRemoteController(
+                new SessionToken(mContext, MOCK_MEDIA2_SESSION_SERVICE), true, null);
+
+        // Set current media item.
+        Bitmap albumArt = BitmapFactory.decodeResource(mContext.getResources(),
+                androidx.media2.test.service.R.drawable.big_buck_bunny);
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putText(METADATA_KEY_MEDIA_ID, "testMediaId")
+                .putText(METADATA_KEY_DISPLAY_TITLE, "Test Song Name")
+                .putText(METADATA_KEY_ARTIST, "Test Artist Name")
+                .putBitmap(METADATA_KEY_ALBUM_ART, albumArt)
+                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
+                .putLong(METADATA_KEY_PLAYABLE, 1)
+                .build();
+        mPlayer.mCurrentMediaItem = new MediaItem.Builder()
+                .setMetadata(metadata)
+                .build();
+
+        mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
+        // At this point, the notification should be shown.
+        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
+
+        // Set a new media item. (current media item is changed)
+        MediaMetadata newMetadata = new MediaMetadata.Builder()
+                .putText(METADATA_KEY_MEDIA_ID, "New media ID")
+                .putText(METADATA_KEY_DISPLAY_TITLE, "New Song Name")
+                .putText(METADATA_KEY_ARTIST, "New Artist Name")
+                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE)
+                .putLong(METADATA_KEY_PLAYABLE, 1)
+                .build();
+
+        MediaItem newItem = new MediaItem.Builder().setMetadata(newMetadata).build();
+        mPlayer.mCurrentMediaItem = newItem;
+
+        // Calling this should update the notification with the new metadata.
+        mPlayer.notifyCurrentMediaItemChanged(newItem);
+        Thread.sleep(NOTIFICATION_SHOW_TIME_MS);
+    }
 }
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt
index 6a64d84..e0469ed 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt
@@ -23,7 +23,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Divider
 import androidx.compose.material.Text
 import androidx.compose.material.TextField
@@ -74,7 +74,7 @@
         Divider(color = Color.Black)
         Button(
             onClick = { navController.navigate(Uri.parse(uri + state.value)) },
-            colors = ButtonConstants.defaultButtonColors(backgroundColor = Color.LightGray),
+            colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
             modifier = Modifier.fillMaxWidth()
         ) {
             Text(text = "Navigate By DeepLink")
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
index fd00b02..a195480a 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
@@ -21,7 +21,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -53,7 +53,7 @@
         if (number < 5) {
             Button(
                 onClick = { navController.navigate("$next") },
-                colors = ButtonConstants.defaultButtonColors(backgroundColor = Color.LightGray),
+                colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
                 modifier = Modifier.fillMaxWidth()
             ) {
                 Text(text = "Navigate to Screen $next")
@@ -63,7 +63,7 @@
         if (navController.previousBackStackEntry != null) {
             Button(
                 onClick = { navController.navigate("1") { popUpTo("1") { inclusive = true } } },
-                colors = ButtonConstants.defaultButtonColors(backgroundColor = Color.LightGray),
+                colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
                 modifier = Modifier.fillMaxWidth()
             ) {
                 Text(text = "PopUpTo Screen 1")
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
index 4da4784..89c6817 100644
--- a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
@@ -25,7 +25,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonConstants
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Divider
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
@@ -133,7 +133,7 @@
 ) {
     Button(
         onClick = listener,
-        colors = ButtonConstants.defaultButtonColors(backgroundColor = LightGray),
+        colors = ButtonDefaults.buttonColors(backgroundColor = LightGray),
         modifier = Modifier.fillMaxWidth()
     ) {
         Text(text = "Navigate to $text")
@@ -145,7 +145,7 @@
     if (navController.previousBackStackEntry != null) {
         Button(
             onClick = { navController.popBackStack() },
-            colors = ButtonConstants.defaultButtonColors(backgroundColor = LightGray),
+            colors = ButtonDefaults.buttonColors(backgroundColor = LightGray),
             modifier = Modifier.fillMaxWidth()
         ) {
             Text(text = "Go to Previous screen")
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index dfb2f53..c0867a3 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -9,10 +9,7 @@
   }
 
   public final class CombinedLoadStates {
-    ctor public CombinedLoadStates(androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
-    method public androidx.paging.LoadStates component1();
-    method public androidx.paging.LoadStates? component2();
-    method public androidx.paging.CombinedLoadStates copy(androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
     method public androidx.paging.LoadState getAppend();
     method public androidx.paging.LoadStates? getMediator();
     method public androidx.paging.LoadState getPrepend();
@@ -265,7 +262,7 @@
   }
 
   public final class Pager<Key, Value> {
-    ctor public Pager(androidx.paging.PagingConfig config, Key? initialKey, androidx.paging.RemoteMediator<Key,Value>? remoteMediator, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor @androidx.paging.ExperimentalPagingApi public Pager(androidx.paging.PagingConfig config, 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, 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();
@@ -318,8 +315,8 @@
     method public final boolean getInvalid();
     method public boolean getJumpingSupported();
     method public boolean getKeyReuseSupported();
-    method @androidx.paging.ExperimentalPagingApi public Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
-    method public void invalidate();
+    method public 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);
@@ -331,30 +328,25 @@
   public abstract static sealed class PagingSource.LoadParams<Key> {
     method public abstract Key? getKey();
     method public final int getLoadSize();
-    method @Deprecated public final int getPageSize();
     method public final boolean getPlaceholdersEnabled();
     property public abstract Key? key;
     property public final int loadSize;
-    property @Deprecated public final int pageSize;
     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, int pageSize);
     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, int pageSize);
     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, int pageSize);
     ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
     method public Key? getKey();
     property public Key? key;
diff --git a/paging/common/api/public_plus_experimental_current.txt b/paging/common/api/public_plus_experimental_current.txt
index c2059933..9641951 100644
--- a/paging/common/api/public_plus_experimental_current.txt
+++ b/paging/common/api/public_plus_experimental_current.txt
@@ -9,11 +9,7 @@
   }
 
   public final class CombinedLoadStates {
-    ctor public CombinedLoadStates(androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
-    method public androidx.paging.LoadStates component1();
-    method public androidx.paging.LoadStates? component2();
-    method public androidx.paging.CombinedLoadStates copy(androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public inline void forEach(kotlin.jvm.functions.Function3<? super androidx.paging.LoadType,? super java.lang.Boolean,? super androidx.paging.LoadState,kotlin.Unit> op);
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
     method public androidx.paging.LoadState getAppend();
     method public androidx.paging.LoadStates? getMediator();
     method public androidx.paging.LoadState getPrepend();
@@ -267,7 +263,7 @@
   }
 
   public final class Pager<Key, Value> {
-    ctor public Pager(androidx.paging.PagingConfig config, Key? initialKey, androidx.paging.RemoteMediator<Key,Value>? remoteMediator, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor @androidx.paging.ExperimentalPagingApi public Pager(androidx.paging.PagingConfig config, 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, 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();
@@ -320,8 +316,8 @@
     method public final boolean getInvalid();
     method public boolean getJumpingSupported();
     method public boolean getKeyReuseSupported();
-    method @androidx.paging.ExperimentalPagingApi public Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
-    method public void invalidate();
+    method public 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);
@@ -333,30 +329,25 @@
   public abstract static sealed class PagingSource.LoadParams<Key> {
     method public abstract Key? getKey();
     method public final int getLoadSize();
-    method @Deprecated public final int getPageSize();
     method public final boolean getPlaceholdersEnabled();
     property public abstract Key? key;
     property public final int loadSize;
-    property @Deprecated public final int pageSize;
     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, int pageSize);
     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, int pageSize);
     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, int pageSize);
     ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
     method public Key? getKey();
     property public Key? key;
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index dfb2f53..c0867a3 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -9,10 +9,7 @@
   }
 
   public final class CombinedLoadStates {
-    ctor public CombinedLoadStates(androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
-    method public androidx.paging.LoadStates component1();
-    method public androidx.paging.LoadStates? component2();
-    method public androidx.paging.CombinedLoadStates copy(androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, androidx.paging.LoadStates? mediator);
     method public androidx.paging.LoadState getAppend();
     method public androidx.paging.LoadStates? getMediator();
     method public androidx.paging.LoadState getPrepend();
@@ -265,7 +262,7 @@
   }
 
   public final class Pager<Key, Value> {
-    ctor public Pager(androidx.paging.PagingConfig config, Key? initialKey, androidx.paging.RemoteMediator<Key,Value>? remoteMediator, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor @androidx.paging.ExperimentalPagingApi public Pager(androidx.paging.PagingConfig config, 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, 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();
@@ -318,8 +315,8 @@
     method public final boolean getInvalid();
     method public boolean getJumpingSupported();
     method public boolean getKeyReuseSupported();
-    method @androidx.paging.ExperimentalPagingApi public Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
-    method public void invalidate();
+    method public 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);
@@ -331,30 +328,25 @@
   public abstract static sealed class PagingSource.LoadParams<Key> {
     method public abstract Key? getKey();
     method public final int getLoadSize();
-    method @Deprecated public final int getPageSize();
     method public final boolean getPlaceholdersEnabled();
     property public abstract Key? key;
     property public final int loadSize;
-    property @Deprecated public final int pageSize;
     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, int pageSize);
     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, int pageSize);
     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, int pageSize);
     ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
     method public Key? getKey();
     property public Key? key;
diff --git a/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt b/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
index 386b4ba..19efffd 100644
--- a/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/CombinedLoadStates.kt
@@ -16,12 +16,43 @@
 
 package androidx.paging
 
-import androidx.annotation.RestrictTo
-
 /**
  * Collection of pagination [LoadState]s for both a [PagingSource], and [RemoteMediator].
  */
-data class CombinedLoadStates(
+class CombinedLoadStates(
+    /**
+     * Convenience for combined behavior of [REFRESH][LoadType.REFRESH] [LoadState], which
+     * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
+     * awaits for both [source] and [mediator] to become [LoadState.NotLoading] to ensure the
+     * remote load was applied.
+     *
+     * For use cases that require reacting to [LoadState] of [source] and [mediator]
+     * specifically, e.g., showing cached data when network loads via [mediator] fail,
+     * [LoadStates] exposed via [source] and [mediator] should be used directly.
+     */
+    val refresh: LoadState,
+    /**
+     * Convenience for combined behavior of [PREPEND][LoadType.REFRESH] [LoadState], which
+     * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
+     * awaits for both [source] and [mediator] to become [LoadState.NotLoading] to ensure the
+     * remote load was applied.
+     *
+     * For use cases that require reacting to [LoadState] of [source] and [mediator]
+     * specifically, e.g., showing cached data when network loads via [mediator] fail,
+     * [LoadStates] exposed via [source] and [mediator] should be used directly.
+     */
+    val prepend: LoadState,
+    /**
+     * Convenience for combined behavior of [APPEND][LoadType.REFRESH] [LoadState], which
+     * generally defers to [mediator] if it exists, but if previously was [LoadState.Loading],
+     * awaits for both [source] and [mediator] to become [LoadState.NotLoading] to ensure the
+     * remote load was applied.
+     *
+     * For use cases that require reacting to [LoadState] of [source] and [mediator]
+     * specifically, e.g., showing cached data when network loads via [mediator] fail,
+     * [LoadStates] exposed via [source] and [mediator] should be used directly.
+     */
+    val append: LoadState,
     /**
      * [LoadStates] corresponding to loads from a [PagingSource].
      */
@@ -31,40 +62,39 @@
      * [LoadStates] corresponding to loads from a [RemoteMediator], or `null` if [RemoteMediator]
      * not present.
      */
-    val mediator: LoadStates? = null
+    val mediator: LoadStates? = null,
 ) {
-    /**
-     * Convenience for accessing [REFRESH][LoadType.REFRESH] [LoadState], which always defers to
-     * [LoadState] of [mediator] if it exists, otherwise equivalent to [LoadState] of [source].
-     *
-     * For use cases that require reacting to [LoadState] of [source] and [mediator]
-     * specifically, e.g., showing cached data when network loads via [mediator] fail,
-     * [LoadStates] exposed via [source] and [mediator] should be used directly.
-     */
-    val refresh: LoadState = (mediator ?: source).refresh
 
-    /**
-     * Convenience for accessing [PREPEND][LoadType.PREPEND] [LoadState], which always defers to
-     * [LoadState] of [mediator] if it exists, otherwise equivalent to [LoadState] of [source].
-     *
-     * For use cases that require reacting to [LoadState] of [source] and [mediator]
-     * specifically, e.g., showing cached data when network loads via [mediator] fail,
-     * [LoadStates] exposed via [source] and [mediator] should be used directly.
-     */
-    val prepend: LoadState = (mediator ?: source).prepend
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
 
-    /**
-     * Convenience for accessing [APPEND][LoadType.APPEND] [LoadState], which always defers to
-     * [LoadState] of [mediator] if it exists, otherwise equivalent to [LoadState] of [source].
-     *
-     * For use cases that require reacting to [LoadState] of [source] and [mediator]
-     * specifically, e.g., showing cached data when network loads via [mediator] fail,
-     * [LoadStates] exposed via [source] and [mediator] should be used directly.
-     */
-    val append: LoadState = (mediator ?: source).append
+        other as CombinedLoadStates
 
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    inline fun forEach(op: (LoadType, Boolean, LoadState) -> Unit) {
+        if (refresh != other.refresh) return false
+        if (prepend != other.prepend) return false
+        if (append != other.append) return false
+        if (source != other.source) return false
+        if (mediator != other.mediator) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = refresh.hashCode()
+        result = 31 * result + prepend.hashCode()
+        result = 31 * result + append.hashCode()
+        result = 31 * result + source.hashCode()
+        result = 31 * result + (mediator?.hashCode() ?: 0)
+        return result
+    }
+
+    override fun toString(): String {
+        return "CombinedLoadStates(refresh=$refresh, prepend=$prepend, append=$append, " +
+            "source=$source, mediator=$mediator)"
+    }
+
+    internal fun forEach(op: (LoadType, Boolean, LoadState) -> Unit) {
         source.forEach { type, state ->
             op(type, false, state)
         }
@@ -75,11 +105,10 @@
 
     internal companion object {
         val IDLE_SOURCE = CombinedLoadStates(
+            refresh = LoadState.NotLoading.Incomplete,
+            prepend = LoadState.NotLoading.Incomplete,
+            append = LoadState.NotLoading.Incomplete,
             source = LoadStates.IDLE
         )
-        val IDLE_MEDIATOR = CombinedLoadStates(
-            source = LoadStates.IDLE,
-            mediator = LoadStates.IDLE
-        )
     }
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
index 055f941..e7d9382 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -97,7 +97,6 @@
     @Suppress("UNCHECKED_CAST")
     override val lastKey: K?
         get() {
-            @OptIn(ExperimentalPagingApi::class)
             return (storage.getRefreshKeyInfo(config) as PagingState<K, V>?)
                 ?.let { pagingSource.getRefreshKey(it) }
                 ?: initialLastKey
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
index 26e0270..cbceeae 100644
--- a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -233,9 +233,12 @@
         @JvmOverloads
         fun asPagingSourceFactory(
             fetchDispatcher: CoroutineDispatcher = Dispatchers.IO
-        ): () -> PagingSource<Key, Value> = {
-            LegacyPagingSource(fetchDispatcher) { create() }
-        }
+        ): () -> PagingSource<Key, Value> = SuspendingPagingSourceFactory(
+            delegate = {
+                LegacyPagingSource(fetchDispatcher, create())
+            },
+            dispatcher = fetchDispatcher
+        )
     }
 
     /**
diff --git a/paging/common/src/main/kotlin/androidx/paging/DirectDispatcher.kt b/paging/common/src/main/kotlin/androidx/paging/DirectDispatcher.kt
deleted file mode 100644
index 5d65446..0000000
--- a/paging/common/src/main/kotlin/androidx/paging/DirectDispatcher.kt
+++ /dev/null
@@ -1,29 +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.paging
-
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlin.coroutines.CoroutineContext
-
-/**
- * [CoroutineDispatcher] which immediately runs new jobs on the current thread.
- */
-internal object DirectDispatcher : CoroutineDispatcher() {
-    override fun dispatch(context: CoroutineContext, block: Runnable) {
-        block.run()
-    }
-}
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
index 5d24516..7014826 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -17,6 +17,7 @@
 package androidx.paging
 
 import androidx.annotation.RestrictTo
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 
 /**
@@ -31,15 +32,17 @@
 class InitialPagedList<K : Any, V : Any>(
     pagingSource: PagingSource<K, V>,
     coroutineScope: CoroutineScope,
+    notifyDispatcher: CoroutineDispatcher,
+    backgroundDispatcher: CoroutineDispatcher,
     config: Config,
     initialLastKey: K?
 ) : ContiguousPagedList<K, V>(
-    pagingSource,
-    coroutineScope,
-    DirectDispatcher,
-    DirectDispatcher,
-    null,
-    config,
-    PagingSource.LoadResult.Page.empty(),
-    initialLastKey
+    pagingSource = pagingSource,
+    coroutineScope = coroutineScope,
+    notifyDispatcher = notifyDispatcher,
+    backgroundDispatcher = backgroundDispatcher,
+    boundaryCallback = null,
+    config = config,
+    initialPage = PagingSource.LoadResult.Page.empty(),
+    initialLastKey = initialLastKey
 )
diff --git a/paging/common/src/main/kotlin/androidx/paging/LegacyPageFetcher.kt b/paging/common/src/main/kotlin/androidx/paging/LegacyPageFetcher.kt
index c053edd..1bc1f44 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LegacyPageFetcher.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LegacyPageFetcher.kt
@@ -119,7 +119,6 @@
             key,
             config.pageSize,
             config.enablePlaceholders,
-            config.pageSize
         )
         scheduleLoad(LoadType.PREPEND, loadParams)
     }
@@ -136,7 +135,6 @@
             key,
             config.pageSize,
             config.enablePlaceholders,
-            config.pageSize
         )
         scheduleLoad(LoadType.APPEND, loadParams)
     }
diff --git a/paging/common/src/main/kotlin/androidx/paging/LegacyPagingSource.kt b/paging/common/src/main/kotlin/androidx/paging/LegacyPagingSource.kt
index d3b44a6..c5d7845 100644
--- a/paging/common/src/main/kotlin/androidx/paging/LegacyPagingSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/LegacyPagingSource.kt
@@ -30,24 +30,46 @@
  * A wrapper around [DataSource] which adapts it to the [PagingSource] API.
  */
 internal class LegacyPagingSource<Key : Any, Value : Any>(
-    private val fetchDispatcher: CoroutineDispatcher = DirectDispatcher,
-    internal val dataSourceFactory: () -> DataSource<Key, Value>
+    private val fetchDispatcher: CoroutineDispatcher,
+    internal val dataSource: DataSource<Key, Value>
 ) : PagingSource<Key, Value>() {
-    // Lazily initialize because it must be created on fetchDispatcher, but PagingSourceFactory
-    // passed to Pager is a non-suspending method.
-    internal val dataSource by lazy {
-        dataSourceFactory().also { dataSource ->
-            dataSource.addInvalidatedCallback(::invalidate)
-            // LegacyPagingSource registers invalidate callback after DataSource is created, so we
-            // need to check for race condition here. If DataSource is already invalid, simply
-            // propagate invalidation manually.
-            if (dataSource.isInvalid && !invalid) {
-                dataSource.removeInvalidatedCallback(::invalidate)
-                // Note: Calling this.invalidate directly will re-evaluate dataSource's by lazy
-                // init block, since we haven't returned a value for dataSource yet.
-                super.invalidate()
+    private var pageSize: Int = PAGE_SIZE_NOT_SET
+
+    init {
+        dataSource.addInvalidatedCallback(::invalidate)
+        // technically, there is a possibly race where data source might call back our invalidate.
+        // in practice, it is fine because all fields are initialized at this point.
+        registerInvalidatedCallback {
+            dataSource.removeInvalidatedCallback(::invalidate)
+            dataSource.invalidate()
+        }
+
+        // LegacyPagingSource registers invalidate callback after DataSource is created, so we
+        // need to check for race condition here. If DataSource is already invalid, simply
+        // propagate invalidation manually.
+        if (!invalid && dataSource.isInvalid) {
+            invalidate()
+        }
+    }
+
+    fun setPageSize(pageSize: Int) {
+        check(this.pageSize == PAGE_SIZE_NOT_SET || pageSize == this.pageSize) {
+            "Page size is already set to ${this.pageSize}."
+        }
+        this.pageSize = pageSize
+    }
+
+    /**
+     * This only ever happens in testing if Pager / PagedList is not used hence we'll not get the
+     * page size. For those cases, guess :).
+     */
+    private fun guessPageSize(params: LoadParams<Key>): Int {
+        if (params is LoadParams.Refresh) {
+            if (params.loadSize % PagingConfig.DEFAULT_INITIAL_PAGE_MULTIPLIER == 0) {
+                return params.loadSize / PagingConfig.DEFAULT_INITIAL_PAGE_MULTIPLIER
             }
         }
+        return params.loadSize
     }
 
     override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
@@ -56,13 +78,30 @@
             is LoadParams.Append -> APPEND
             is LoadParams.Prepend -> PREPEND
         }
+        if (pageSize == PAGE_SIZE_NOT_SET) {
+            // println because we don't have android logger here
+            println(
+                """
+                WARNING: pageSize on the LegacyPagingSource is not set.
+                When using legacy DataSource / DataSourceFactory with Paging3, page size
+                should've been set by the paging library but it is not set yet.
+
+                If you are seeing this message in tests where you are testing DataSource
+                in isolation (without a Pager), it is expected and page size will be estimated
+                based on parameters.
+
+                If you are seeing this message despite using a Pager, please file a bug:
+                https://issuetracker.google.com/issues/new?component=413106
+                """.trimIndent()
+            )
+            pageSize = guessPageSize(params)
+        }
         val dataSourceParams = Params(
             type,
             params.key,
             params.loadSize,
             params.placeholdersEnabled,
-            @Suppress("DEPRECATION")
-            params.pageSize
+            pageSize
         )
 
         return withContext(fetchDispatcher) {
@@ -80,12 +119,6 @@
         }
     }
 
-    override fun invalidate() {
-        super.invalidate()
-        dataSource.invalidate()
-    }
-
-    @OptIn(ExperimentalPagingApi::class)
     @Suppress("UNCHECKED_CAST")
     override fun getRefreshKey(state: PagingState<Key, Value>): Key? {
         return when (dataSource.type) {
@@ -105,4 +138,8 @@
 
     override val jumpingSupported: Boolean
         get() = dataSource.type == POSITIONAL
+
+    companion object {
+        const val PAGE_SIZE_NOT_SET = Integer.MIN_VALUE
+    }
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/MutableLoadStateCollection.kt b/paging/common/src/main/kotlin/androidx/paging/MutableLoadStateCollection.kt
index baaf577..31ed7eb 100644
--- a/paging/common/src/main/kotlin/androidx/paging/MutableLoadStateCollection.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/MutableLoadStateCollection.kt
@@ -16,27 +16,45 @@
 
 package androidx.paging
 
-import androidx.annotation.RestrictTo
+import androidx.paging.LoadState.Error
+import androidx.paging.LoadState.Loading
+import androidx.paging.LoadState.NotLoading
 
 /**
- * TODO: Remove this once [PageEvent.LoadStateUpdate] contained [CombinedLoadStates].
- *
- * @hide
+ * Helper to construct [CombinedLoadStates] that accounts for previous state to set the convenience
+ * properties correctly.
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class MutableLoadStateCollection {
+internal class MutableLoadStateCollection {
+    private var refresh: LoadState = NotLoading.Incomplete
+    private var prepend: LoadState = NotLoading.Incomplete
+    private var append: LoadState = NotLoading.Incomplete
     private var source: LoadStates = LoadStates.IDLE
     private var mediator: LoadStates? = null
 
-    fun snapshot() = CombinedLoadStates(source, mediator)
+    fun snapshot() = CombinedLoadStates(
+        refresh = refresh,
+        prepend = prepend,
+        append = append,
+        source = source,
+        mediator = mediator,
+    )
 
     fun set(combinedLoadStates: CombinedLoadStates) {
+        refresh = combinedLoadStates.refresh
+        prepend = combinedLoadStates.prepend
+        append = combinedLoadStates.append
         source = combinedLoadStates.source
         mediator = combinedLoadStates.mediator
     }
 
+    fun set(sourceLoadStates: LoadStates, remoteLoadStates: LoadStates?) {
+        source = sourceLoadStates
+        mediator = remoteLoadStates
+        updateHelperStates()
+    }
+
     fun set(type: LoadType, remote: Boolean, state: LoadState): Boolean {
-        return if (remote) {
+        val didChange = if (remote) {
             val lastMediator = mediator
             mediator = (mediator ?: LoadStates.IDLE).modifyState(type, state)
             mediator != lastMediator
@@ -45,12 +63,61 @@
             source = source.modifyState(type, state)
             source != lastSource
         }
+
+        updateHelperStates()
+        return didChange
     }
 
     fun get(type: LoadType, remote: Boolean): LoadState? {
         return (if (remote) mediator else source)?.get(type)
     }
 
+    private fun updateHelperStates() {
+        refresh = computeHelperState(
+            previousState = refresh,
+            sourceRefreshState = source.refresh,
+            sourceState = source.refresh,
+            remoteState = mediator?.refresh
+        )
+        prepend = computeHelperState(
+            previousState = prepend,
+            sourceRefreshState = source.refresh,
+            sourceState = source.prepend,
+            remoteState = mediator?.prepend
+        )
+        append = computeHelperState(
+            previousState = append,
+            sourceRefreshState = source.refresh,
+            sourceState = source.append,
+            remoteState = mediator?.append
+        )
+    }
+
+    /**
+     * Computes the next value for the convenience helpers in [CombinedLoadStates], which
+     * generally defers to remote state, but waits for both source and remote states to become
+     * [NotLoading] before moving to that state. This provides a reasonable default for the common
+     * use-case where you generally want to wait for both RemoteMediator to return and for the
+     * update to get applied before signaling to UI that a network fetch has "finished".
+     */
+    private fun computeHelperState(
+        previousState: LoadState,
+        sourceRefreshState: LoadState,
+        sourceState: LoadState,
+        remoteState: LoadState?
+    ): LoadState {
+        if (remoteState == null) return sourceState
+
+        return when (previousState) {
+            is Loading -> when {
+                sourceRefreshState is NotLoading && remoteState is NotLoading -> remoteState
+                remoteState is Error -> remoteState
+                else -> previousState
+            }
+            else -> remoteState
+        }
+    }
+
     internal inline fun forEach(op: (LoadType, Boolean, LoadState) -> Unit) {
         source.forEach { type, state ->
             op(type, false, state)
@@ -59,4 +126,9 @@
             op(type, true, state)
         }
     }
+
+    internal fun terminates(loadType: LoadType): Boolean {
+        return get(loadType, false)!!.endOfPaginationReached &&
+            get(loadType, true)?.endOfPaginationReached != false
+    }
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageEvent.kt b/paging/common/src/main/kotlin/androidx/paging/PageEvent.kt
index 827e631..f44eb824 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageEvent.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageEvent.kt
@@ -39,11 +39,11 @@
     ) : PageEvent<T>() {
         init {
             require(loadType == APPEND || placeholdersBefore >= 0) {
-                "Append state defining placeholdersBefore must be > 0, but was" +
+                "Prepend insert defining placeholdersBefore must be > 0, but was" +
                     " $placeholdersBefore"
             }
             require(loadType == PREPEND || placeholdersAfter >= 0) {
-                "Prepend state defining placeholdersAfter must be > 0, but was" +
+                "Append insert defining placeholdersAfter must be > 0, but was" +
                     " $placeholdersAfter"
             }
             require(loadType != REFRESH || pages.isNotEmpty()) {
@@ -150,11 +150,14 @@
                 placeholdersBefore = 0,
                 placeholdersAfter = 0,
                 combinedLoadStates = CombinedLoadStates(
+                    refresh = LoadState.NotLoading.Incomplete,
+                    prepend = LoadState.NotLoading.Complete,
+                    append = LoadState.NotLoading.Complete,
                     source = LoadStates(
                         refresh = LoadState.NotLoading.Incomplete,
                         prepend = LoadState.NotLoading.Complete,
-                        append = LoadState.NotLoading.Complete
-                    )
+                        append = LoadState.NotLoading.Complete,
+                    ),
                 )
             )
         }
@@ -212,8 +215,7 @@
              * This prevents multiple related RV animations from happening simultaneously
              */
             internal fun canDispatchWithoutInsert(loadState: LoadState, fromMediator: Boolean) =
-                loadState is LoadState.Loading || loadState is LoadState.Error ||
-                    (loadState.endOfPaginationReached && fromMediator)
+                loadState is LoadState.Loading || loadState is LoadState.Error || fromMediator
         }
     }
 
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
index eccc298..2663be5 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
@@ -33,7 +33,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
 internal class PageFetcher<Key : Any, Value : Any>(
-    private val pagingSourceFactory: () -> PagingSource<Key, Value>,
+    private val pagingSourceFactory: suspend () -> PagingSource<Key, Value>,
     private val initialKey: Key?,
     private val config: PagingConfig,
     @OptIn(ExperimentalPagingApi::class)
@@ -87,7 +87,6 @@
                     previousPagingState = previousGeneration.state
                 }
 
-                @OptIn(ExperimentalPagingApi::class)
                 val initialKey: Key? = previousPagingState?.let { pagingSource.getRefreshKey(it) }
                     ?: initialKey
 
@@ -110,11 +109,10 @@
             }
             .filterNotNull()
             .mapLatest { generation ->
-                val downstreamFlow = if (remoteMediatorAccessor == null) {
-                    generation.snapshot.pageEventFlow
-                } else {
-                    generation.snapshot.injectRemoteEvents(remoteMediatorAccessor)
-                }
+                val downstreamFlow = generation.snapshot
+                    .injectRemoteEvents(remoteMediatorAccessor)
+                // .mapRemoteCompleteAsTrailingInsertForSeparators()
+
                 PagingData(
                     flow = downstreamFlow,
                     receiver = PagerUiReceiver(generation.snapshot, retryEvents)
@@ -124,47 +122,78 @@
     }
 
     private fun PageFetcherSnapshot<Key, Value>.injectRemoteEvents(
-        accessor: RemoteMediatorAccessor<Key, Value>
-    ): Flow<PageEvent<Value>> = channelFlow {
-        suspend fun dispatchIfValid(type: LoadType, state: LoadState) {
-            // not loading events are sent w/ insert-drop events.
-            if (PageEvent.LoadStateUpdate.canDispatchWithoutInsert(state, fromMediator = true)) {
-                send(
-                    PageEvent.LoadStateUpdate<Value>(type, true, state)
-                )
-            } else {
-                // ignore. Some invalidation will happened and we'll send the event there instead
-            }
-        }
-        launch {
-            var prev = LoadStates.IDLE
-            accessor.state.collect {
-                if (prev.refresh != it.refresh) {
-                    dispatchIfValid(REFRESH, it.refresh)
-                }
-                if (prev.prepend != it.prepend) {
-                    dispatchIfValid(PREPEND, it.prepend)
-                }
-                if (prev.append != it.append) {
-                    dispatchIfValid(APPEND, it.append)
-                }
-                prev = it
-            }
-        }
+        accessor: RemoteMediatorAccessor<Key, Value>?
+    ): Flow<PageEvent<Value>> {
+        if (accessor == null) return pageEventFlow
 
-        this@injectRemoteEvents.pageEventFlow.collect {
-            // only insert events have combinedLoadStates.
-            if (it is PageEvent.Insert<Value>) {
-                send(
-                    it.copy(
-                        combinedLoadStates = CombinedLoadStates(
-                            it.combinedLoadStates.source,
-                            accessor.state.value
+        return channelFlow {
+            val loadStates = MutableLoadStateCollection()
+
+            suspend fun dispatchIfValid(type: LoadType, state: LoadState) {
+                // not loading events are sent w/ insert-drop events.
+                if (PageEvent.LoadStateUpdate.canDispatchWithoutInsert(
+                        state,
+                        fromMediator = true
+                    )
+                ) {
+                    send(
+                        PageEvent.LoadStateUpdate<Value>(
+                            loadType = type,
+                            fromMediator = true,
+                            loadState = state
                         )
                     )
-                )
-            } else {
-                send(it)
+                } else {
+                    // Wait for invalidation to set state to NotLoading via Insert to prevent any
+                    // potential for flickering.
+                }
+            }
+
+            launch {
+                var prev = LoadStates.IDLE
+                accessor.state.collect {
+                    if (prev.refresh != it.refresh) {
+                        loadStates.set(REFRESH, true, it.refresh)
+                        dispatchIfValid(REFRESH, it.refresh)
+                    }
+                    if (prev.prepend != it.prepend) {
+                        loadStates.set(PREPEND, true, it.prepend)
+                        dispatchIfValid(PREPEND, it.prepend)
+                    }
+                    if (prev.append != it.append) {
+                        loadStates.set(APPEND, true, it.append)
+                        dispatchIfValid(APPEND, it.append)
+                    }
+                    prev = it
+                }
+            }
+
+            this@injectRemoteEvents.pageEventFlow.collect { event ->
+                when (event) {
+                    is PageEvent.Insert -> {
+                        loadStates.set(
+                            sourceLoadStates = event.combinedLoadStates.source,
+                            remoteLoadStates = accessor.state.value
+                        )
+                        send(event.copy(combinedLoadStates = loadStates.snapshot()))
+                    }
+                    is PageEvent.Drop -> {
+                        loadStates.set(
+                            type = event.loadType,
+                            remote = false,
+                            state = LoadState.NotLoading.Incomplete
+                        )
+                        send(event)
+                    }
+                    is PageEvent.LoadStateUpdate -> {
+                        loadStates.set(
+                            type = event.loadType,
+                            remote = event.fromMediator,
+                            state = event.loadState
+                        )
+                        send(event)
+                    }
+                }
             }
         }
     }
@@ -177,11 +206,13 @@
         refreshEvents.send(false)
     }
 
-    private fun generateNewPagingSource(
+    private suspend fun generateNewPagingSource(
         previousPagingSource: PagingSource<Key, Value>?
     ): PagingSource<Key, Value> {
         val pagingSource = pagingSourceFactory()
-
+        if (pagingSource is LegacyPagingSource) {
+            pagingSource.setPageSize(config.pageSize)
+        }
         // Ensure pagingSourceFactory produces a new instance of PagingSource.
         check(pagingSource !== previousPagingSource) {
             """
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
index a641388..020ae17 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -285,7 +285,6 @@
         key = key,
         loadSize = if (loadType == REFRESH) config.initialLoadSize else config.pageSize,
         placeholdersEnabled = config.enablePlaceholders,
-        pageSize = config.pageSize
     )
 
     private suspend fun doInitialLoad() {
@@ -304,19 +303,13 @@
                     if (result.prevKey == null) {
                         state.setSourceLoadState(
                             type = PREPEND,
-                            newState = when (remoteMediatorConnection) {
-                                null -> NotLoading.Complete
-                                else -> NotLoading.Incomplete
-                            }
+                            newState = NotLoading.Complete
                         )
                     }
                     if (result.nextKey == null) {
                         state.setSourceLoadState(
                             type = APPEND,
-                            newState = when (remoteMediatorConnection) {
-                                null -> NotLoading.Complete
-                                else -> NotLoading.Incomplete
-                            }
+                            newState = NotLoading.Complete
                         )
                     }
                 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshotState.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshotState.kt
index 147618c..4a0009d 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshotState.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshotState.kt
@@ -152,24 +152,33 @@
                 placeholdersBefore = placeholdersBefore,
                 placeholdersAfter = placeholdersAfter,
                 combinedLoadStates = CombinedLoadStates(
+                    refresh = sourceLoadStates.refresh,
+                    prepend = sourceLoadStates.prepend,
+                    append = sourceLoadStates.append,
                     source = sourceLoadStates,
-                    mediator = null
+                    mediator = null,
                 )
             )
             PREPEND -> Prepend(
                 pages = pages,
                 placeholdersBefore = placeholdersBefore,
                 combinedLoadStates = CombinedLoadStates(
+                    refresh = sourceLoadStates.refresh,
+                    prepend = sourceLoadStates.prepend,
+                    append = sourceLoadStates.append,
                     source = sourceLoadStates,
-                    mediator = null
+                    mediator = null,
                 )
             )
             APPEND -> Append(
                 pages = pages,
                 placeholdersAfter = placeholdersAfter,
                 combinedLoadStates = CombinedLoadStates(
+                    refresh = sourceLoadStates.refresh,
+                    prepend = sourceLoadStates.prepend,
+                    append = sourceLoadStates.append,
                     source = sourceLoadStates,
-                    mediator = null
+                    mediator = null,
                 )
             )
         }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 4b472b2..2b22813 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -26,7 +26,6 @@
 import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
 import java.lang.ref.WeakReference
 import java.util.AbstractList
 import java.util.concurrent.Executor
@@ -179,12 +178,9 @@
                         key,
                         config.initialLoadSizeHint,
                         config.enablePlaceholders,
-                        config.pageSize
                     )
                     runBlocking {
-                        val initialResult = withContext(DirectDispatcher) {
-                            pagingSource.load(params)
-                        }
+                        val initialResult = pagingSource.load(params)
                         when (initialResult) {
                             is PagingSource.LoadResult.Page -> initialResult
                             is PagingSource.LoadResult.Error -> throw initialResult.throwable
@@ -480,7 +476,14 @@
         @Suppress("DEPRECATION")
         fun build(): PagedList<Value> {
             val fetchDispatcher = fetchDispatcher ?: Dispatchers.IO
-            val pagingSource = pagingSource ?: dataSource?.let { LegacyPagingSource { it } }
+            val pagingSource = pagingSource ?: dataSource?.let { dataSource ->
+                LegacyPagingSource(
+                    fetchDispatcher = fetchDispatcher,
+                    dataSource = dataSource
+                ).also {
+                    it.setPageSize(config.pageSize)
+                }
+            }
 
             check(pagingSource != null) {
                 "PagedList cannot be built without a PagingSource or DataSource"
diff --git a/paging/common/src/main/kotlin/androidx/paging/Pager.kt b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
index ed3cfda..8f2483d 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Pager.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
@@ -38,22 +38,41 @@
  * `androidx.paging:paging-rxjava2` artifact.
  */
 class Pager<Key : Any, Value : Any>
-@JvmOverloads constructor(
+// Experimental usage is propagated to public API via constructor argument.
+@ExperimentalPagingApi constructor(
     config: PagingConfig,
     initialKey: Key? = null,
-    @OptIn(ExperimentalPagingApi::class)
-    remoteMediator: RemoteMediator<Key, Value>? = null,
+    remoteMediator: RemoteMediator<Key, Value>?,
     pagingSourceFactory: () -> PagingSource<Key, Value>
 ) {
+    // Experimental usage is internal, so opt-in is allowed here.
+    @JvmOverloads
+    @OptIn(ExperimentalPagingApi::class)
+    constructor(
+        config: PagingConfig,
+        initialKey: Key? = null,
+        pagingSourceFactory: () -> PagingSource<Key, Value>
+    ) : this(config, initialKey, null, pagingSourceFactory)
+
     /**
      * A cold [Flow] of [PagingData], which emits new instances of [PagingData] once they become
      * invalidated by [PagingSource.invalidate] or calls to [AsyncPagingDataDiffer.refresh] or
      * [PagingDataAdapter.refresh].
      */
     val flow: Flow<PagingData<Value>> = PageFetcher(
-        pagingSourceFactory,
-        initialKey,
-        config,
-        remoteMediator
+        pagingSourceFactory = if (
+            pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
+        ) {
+            pagingSourceFactory::create
+        } else {
+            // cannot pass it as is since it is not a suspend function. Hence, we wrap it in {}
+            // which means we are calling the original factory inside a suspend function
+            {
+                pagingSourceFactory()
+            }
+        },
+        initialKey = initialKey,
+        config = config,
+        remoteMediator = remoteMediator
     ).flow
 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingData.kt b/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
index 2f842b7..61548a2 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingData.kt
@@ -143,13 +143,15 @@
                     placeholdersBefore = 0,
                     placeholdersAfter = 0,
                     combinedLoadStates = CombinedLoadStates(
+                        refresh = LoadState.NotLoading.Incomplete,
+                        prepend = LoadState.NotLoading.Complete,
+                        append = LoadState.NotLoading.Complete,
                         source = LoadStates(
                             refresh = LoadState.NotLoading.Incomplete,
                             prepend = LoadState.NotLoading.Complete,
                             append = LoadState.NotLoading.Complete
                         )
                     )
-
                 )
             ),
             receiver = NOOP_RECEIVER
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
index 345b7cf..4db4e26 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
@@ -32,7 +32,6 @@
         key,
         initialLoadSizeHint,
         enablePlaceholders,
-        pageSize
     )
 
 /**
@@ -100,15 +99,6 @@
          * [LoadResult.Page.itemsAfter] if possible.
          */
         val placeholdersEnabled: Boolean,
-        /**
-         * From [PagingConfig.pageSize], the configured page size.
-         */
-        @Deprecated(
-            message = "PagingConfig.pageSize will be removed in future versions, use " +
-                "PagingConfig.loadSize instead.",
-            replaceWith = ReplaceWith("loadSize")
-        )
-        val pageSize: Int = loadSize
     ) {
         /**
          * Key for the page to be loaded.
@@ -131,45 +121,39 @@
          * Params for an initial load request on a [PagingSource] from [PagingSource.load] or a
          * refresh triggered by [invalidate].
          */
-        class Refresh<Key : Any> @JvmOverloads constructor(
+        class Refresh<Key : Any> constructor(
             override val key: Key?,
             loadSize: Int,
             placeholdersEnabled: Boolean,
-            pageSize: Int = loadSize
         ) : LoadParams<Key>(
             loadSize = loadSize,
             placeholdersEnabled = placeholdersEnabled,
-            pageSize = pageSize
         )
 
         /**
          * Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
          * appended to the end of the list.
          */
-        class Append<Key : Any> @JvmOverloads constructor(
+        class Append<Key : Any> constructor(
             override val key: Key,
             loadSize: Int,
             placeholdersEnabled: Boolean,
-            pageSize: Int = loadSize
         ) : LoadParams<Key>(
             loadSize = loadSize,
             placeholdersEnabled = placeholdersEnabled,
-            pageSize = pageSize
         )
 
         /**
          * Params to load a page of data from a [PagingSource] via [PagingSource.load] to be
          * prepended to the start of the list.
          */
-        class Prepend<Key : Any> @JvmOverloads constructor(
+        class Prepend<Key : Any> constructor(
             override val key: Key,
             loadSize: Int,
             placeholdersEnabled: Boolean,
-            pageSize: Int = loadSize
         ) : LoadParams<Key>(
             loadSize = loadSize,
             placeholdersEnabled = placeholdersEnabled,
-            pageSize = pageSize
         )
 
         internal companion object {
@@ -178,13 +162,11 @@
                 key: Key?,
                 loadSize: Int,
                 placeholdersEnabled: Boolean,
-                pageSize: Int
             ): LoadParams<Key> = when (loadType) {
                 LoadType.REFRESH -> Refresh(
                     key = key,
                     loadSize = loadSize,
                     placeholdersEnabled = placeholdersEnabled,
-                    pageSize = pageSize
                 )
                 LoadType.PREPEND -> Prepend(
                     loadSize = loadSize,
@@ -192,7 +174,6 @@
                         "key cannot be null for prepend"
                     },
                     placeholdersEnabled = placeholdersEnabled,
-                    pageSize = pageSize
                 )
                 LoadType.APPEND -> Append(
                     loadSize = loadSize,
@@ -200,7 +181,6 @@
                         "key cannot be null for append"
                     },
                     placeholdersEnabled = placeholdersEnabled,
-                    pageSize = pageSize
                 )
             }
         }
@@ -335,7 +315,6 @@
      * list of loaded pages is not empty. In the case where a refresh is triggered before the
      * initial load succeeds or it errors out, the initial key passed to [Pager] will be used.
      */
-    @ExperimentalPagingApi
     open fun getRefreshKey(state: PagingState<Key, Value>): Key? = null
 
     private val onInvalidatedCallbacks = CopyOnWriteArrayList<() -> Unit>()
@@ -355,9 +334,7 @@
      * This method is idempotent. i.e., If [invalidate] has already been called, subsequent calls to
      * this method should have no effect.
      */
-    open fun invalidate() {
-        // TODO(b/137971356): Investigate making this not open when able to remove
-        //  LegacyPagingSource.
+    fun invalidate() {
         if (_invalid.compareAndSet(false, true)) {
             onInvalidatedCallbacks.forEach { it.invoke() }
         }
diff --git a/paging/common/src/main/kotlin/androidx/paging/Separators.kt b/paging/common/src/main/kotlin/androidx/paging/Separators.kt
index 94ed298..632f888 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Separators.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Separators.kt
@@ -16,10 +16,13 @@
 
 package androidx.paging
 
+import androidx.paging.LoadState.NotLoading
 import androidx.paging.LoadType.APPEND
 import androidx.paging.LoadType.PREPEND
+import androidx.paging.LoadType.REFRESH
 import androidx.paging.PageEvent.Drop
 import androidx.paging.PageEvent.Insert
+import androidx.paging.PageEvent.LoadStateUpdate
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 
@@ -165,17 +168,18 @@
     var endTerminalSeparatorDeferred = false
     var startTerminalSeparatorDeferred = false
 
+    val loadStates = MutableLoadStateCollection()
+    var placeholdersBefore = 0
+    var placeholdersAfter = 0
+
     var footerAdded = false
     var headerAdded = false
 
     @Suppress("UNCHECKED_CAST")
     suspend fun onEvent(event: PageEvent<T>): PageEvent<R> = when (event) {
         is Insert<T> -> onInsert(event)
-        is Drop -> {
-            onDrop(event) // Update pageStash state
-            event as Drop<R>
-        }
-        is PageEvent.LoadStateUpdate -> event as PageEvent<R>
+        is Drop -> onDrop(event)
+        is LoadStateUpdate -> onLoadStateUpdate(event)
     }.also {
         // validate internal state after each modification
         if (endTerminalSeparatorDeferred) {
@@ -191,16 +195,14 @@
         return this as Insert<R>
     }
 
-    fun LoadState.isTerminal(): Boolean {
-        return this is LoadState.NotLoading && endOfPaginationReached
-    }
-
     fun CombinedLoadStates.terminatesStart(): Boolean {
-        return source.prepend.isTerminal() && mediator?.prepend?.isTerminal() != false
+        return source.prepend.endOfPaginationReached &&
+            mediator?.prepend?.endOfPaginationReached != false
     }
 
     fun CombinedLoadStates.terminatesEnd(): Boolean {
-        return source.append.isTerminal() && mediator?.append?.isTerminal() != false
+        return source.append.endOfPaginationReached &&
+            mediator?.append?.endOfPaginationReached != false
     }
 
     fun <T : Any> Insert<T>.terminatesStart(): Boolean = if (loadType == APPEND) {
@@ -227,6 +229,17 @@
             "Additional append event after append state is done"
         }
 
+        // Update SeparatorState before we do any real work.
+        loadStates.set(event.combinedLoadStates)
+        // Append insert has placeholdersBefore = -1 as a placeholder value.
+        if (event.loadType != APPEND) {
+            placeholdersBefore = event.placeholdersBefore
+        }
+        // Prepend insert has placeholdersAfter = -1 as a placeholder value.
+        if (event.loadType != PREPEND) {
+            placeholdersAfter = event.placeholdersAfter
+        }
+
         if (eventEmpty) {
             if (eventTerminatesStart && eventTerminatesEnd) {
                 // if event is empty, and fully terminal, resolve single separator, and that's it
@@ -410,7 +423,16 @@
     /**
      * Process a [Drop] event to update [pageStash] stage.
      */
-    fun onDrop(event: Drop<T>) {
+    fun onDrop(event: Drop<T>): Drop<R> {
+        loadStates.set(type = event.loadType, remote = false, state = NotLoading.Incomplete)
+        if (event.loadType == PREPEND) {
+            placeholdersBefore = event.placeholdersRemaining
+            headerAdded = false
+        } else if (event.loadType == APPEND) {
+            placeholdersAfter = event.placeholdersRemaining
+            footerAdded = false
+        }
+
         if (pageStash.isEmpty()) {
             if (event.loadType == PREPEND) {
                 startTerminalSeparatorDeferred = false
@@ -424,6 +446,49 @@
         pageStash.removeAll { stash ->
             stash.originalPageOffsets.any { pageOffsetsToDrop.contains(it) }
         }
+
+        @Suppress("UNCHECKED_CAST")
+        return event as Drop<R>
+    }
+
+    suspend fun onLoadStateUpdate(event: LoadStateUpdate<T>): PageEvent<R> {
+        // Check for redundant LoadStateUpdate events to avoid unnecessary mapping to empty inserts
+        // that might cause terminal separators to get added out of place.
+        if (loadStates.get(event.loadType, event.fromMediator) == event.loadState) {
+            @Suppress("UNCHECKED_CAST")
+            return event as PageEvent<R>
+        }
+
+        loadStates.set(type = event.loadType, remote = event.fromMediator, state = event.loadState)
+
+        // Transform terminal load state updates into empty inserts for header + footer support
+        // when used with RemoteMediator. In cases where we defer adding a terminal separator,
+        // RemoteMediator can report endOfPaginationReached via LoadStateUpdate event, which
+        // isn't possible to add a separator to. Note: Adding a separate insert event also
+        // doesn't work in the case where .insertSeparators() is called multiple times on the
+        // same page event stream - we have to transform the terminating LoadStateUpdate event.
+        if (event.loadType != REFRESH && event.fromMediator &&
+            event.loadState.endOfPaginationReached
+        ) {
+            val emptyTerminalInsert: Insert<T> = if (event.loadType == PREPEND) {
+                Insert.Prepend(
+                    pages = emptyList(),
+                    placeholdersBefore = placeholdersBefore,
+                    combinedLoadStates = loadStates.snapshot(),
+                )
+            } else {
+                Insert.Append(
+                    pages = emptyList(),
+                    placeholdersAfter = placeholdersAfter,
+                    combinedLoadStates = loadStates.snapshot(),
+                )
+            }
+
+            return onInsert(emptyTerminalInsert)
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        return event as PageEvent<R>
     }
 
     private fun <T : Any> transformablePageToStash(
diff --git a/paging/common/src/main/kotlin/androidx/paging/SuspendingPagingSourceFactory.kt b/paging/common/src/main/kotlin/androidx/paging/SuspendingPagingSourceFactory.kt
new file mode 100644
index 0000000..1b15542f
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/SuspendingPagingSourceFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.paging
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Utility class to convert the paging source factory to a suspend one.
+ *
+ * This is internal because it is only necessary for the legacy paging source implementation
+ * where the data source must be created on the given thread pool for API guarantees.
+ * see: b/173029013
+ * see: b/168061354
+ */
+internal class SuspendingPagingSourceFactory<Key : Any, Value : Any>(
+    private val dispatcher: CoroutineDispatcher,
+    private val delegate: () -> PagingSource<Key, Value>
+) : () -> PagingSource<Key, Value> {
+    suspend fun create(): PagingSource<Key, Value> {
+        return withContext(dispatcher) {
+            delegate()
+        }
+    }
+
+    override fun invoke(): PagingSource<Key, Value> {
+        return delegate()
+    }
+}
diff --git a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
index d83ec83..463c578 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -62,9 +62,9 @@
      * and alignment restrictions. These tests were written before positional+contiguous enforced
      * these behaviors.
      */
-    private inner class TestPagingSource(val listData: List<Item> = ITEMS) :
-        PagingSource<Int, Item>() {
-        @OptIn(ExperimentalPagingApi::class)
+    private inner class TestPagingSource(
+        val listData: List<Item> = ITEMS
+    ) : PagingSource<Int, Item>() {
         override fun getRefreshKey(state: PagingState<Int, Item>): Int? {
             return state.anchorPosition
                 ?.let { anchorPosition -> state.closestItemToPosition(anchorPosition)?.pos }
@@ -189,15 +189,13 @@
 
     private fun PagingSource<Int, Item>.getInitialPage(
         initialKey: Int,
-        loadSize: Int,
-        pageSize: Int
+        loadSize: Int
     ): Page<Int, Item> = runBlocking {
         val result = load(
             PagingSource.LoadParams.Refresh(
                 initialKey,
                 loadSize,
                 placeholdersEnabled,
-                pageSize
             )
         )
 
@@ -216,8 +214,7 @@
     ): PagedList<Item> {
         val initialPage = pagingSource.getInitialPage(
             initialPosition ?: 0,
-            initLoadSize,
-            pageSize
+            initLoadSize
         )
 
         val config = Config.Builder()
diff --git a/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
index 5448b6d..13182ab 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -19,6 +19,7 @@
 import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
 import com.nhaarman.mockitokotlin2.capture
 import com.nhaarman.mockitokotlin2.mock
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
@@ -289,7 +290,7 @@
         @Suppress("DEPRECATION")
         PagedList.Builder(dataSource, 10)
             .setNotifyDispatcher(FailDispatcher())
-            .setFetchDispatcher(DirectDispatcher)
+            .setFetchDispatcher(Dispatchers.IO)
             .setInitialKey("")
             .build()
     }
diff --git a/paging/common/src/test/kotlin/androidx/paging/LegacyPageFetcherTest.kt b/paging/common/src/test/kotlin/androidx/paging/LegacyPageFetcherTest.kt
index a9e2c3d..6e8ede2 100644
--- a/paging/common/src/test/kotlin/androidx/paging/LegacyPageFetcherTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/LegacyPageFetcherTest.kt
@@ -122,7 +122,6 @@
                     key = start,
                     loadSize = end - start,
                     placeholdersEnabled = config.enablePlaceholders,
-                    pageSize = config.pageSize
                 )
             )
         }
@@ -140,7 +139,7 @@
             GlobalScope,
             config,
             pagingSource,
-            DirectDispatcher,
+            testDispatcher,
             testDispatcher,
             consumer,
             storage as LegacyPageFetcher.KeyProvider<Int>
diff --git a/paging/common/src/test/kotlin/androidx/paging/LegacyPagingSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/LegacyPagingSourceTest.kt
index 65a648b..add2986 100644
--- a/paging/common/src/test/kotlin/androidx/paging/LegacyPagingSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/LegacyPagingSourceTest.kt
@@ -17,20 +17,23 @@
 package androidx.paging
 
 import androidx.paging.PagingSource.LoadResult.Page
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Runnable
-import kotlinx.coroutines.launch
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.runBlocking
 import org.junit.Assert
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import kotlin.coroutines.CoroutineContext
+import java.util.concurrent.Executors
 import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
 
-@OptIn(ExperimentalPagingApi::class)
 @RunWith(JUnit4::class)
 class LegacyPagingSourceTest {
     private val fakePagingState = PagingState(
@@ -76,7 +79,10 @@
 
             override fun getKey(item: String) = item.hashCode()
         }
-        val pagingSource = LegacyPagingSource { dataSource }
+        val pagingSource = LegacyPagingSource(
+            fetchDispatcher = Dispatchers.Unconfined,
+            dataSource
+        )
 
         // Check that jumpingSupported is disabled.
         assertFalse { pagingSource.jumpingSupported }
@@ -119,7 +125,10 @@
                 Assert.fail("loadAfter not expected")
             }
         }
-        val pagingSource = LegacyPagingSource { dataSource }
+        val pagingSource = LegacyPagingSource(
+            fetchDispatcher = Dispatchers.Unconfined,
+            dataSource = dataSource
+        )
 
         // Check that jumpingSupported is disabled.
         assertFalse { pagingSource.jumpingSupported }
@@ -139,7 +148,10 @@
 
     @Test
     fun positional() {
-        val pagingSource = LegacyPagingSource { createTestPositionalDataSource() }
+        val pagingSource = LegacyPagingSource(
+            fetchDispatcher = Dispatchers.Unconfined,
+            dataSource = createTestPositionalDataSource()
+        )
 
         // Check that jumpingSupported is enabled.
         assertTrue { pagingSource.jumpingSupported }
@@ -189,7 +201,10 @@
 
     @Test
     fun invalidateFromPagingSource() {
-        val pagingSource = LegacyPagingSource { createTestPositionalDataSource() }
+        val pagingSource = LegacyPagingSource(
+            fetchDispatcher = Dispatchers.Unconfined,
+            dataSource = createTestPositionalDataSource()
+        )
         val dataSource = pagingSource.dataSource
 
         var kotlinInvalidated = false
@@ -216,7 +231,10 @@
 
     @Test
     fun invalidateFromDataSource() {
-        val pagingSource = LegacyPagingSource { createTestPositionalDataSource() }
+        val pagingSource = LegacyPagingSource(
+            fetchDispatcher = Dispatchers.Unconfined,
+            dataSource = createTestPositionalDataSource()
+        )
         val dataSource = pagingSource.dataSource
 
         var kotlinInvalidated = false
@@ -241,41 +259,57 @@
         assertTrue { javaInvalidated }
     }
 
+    @Suppress("DEPRECATION")
     @Test
     fun createDataSourceOnFetchDispatcher() {
-        val manualDispatcher = object : CoroutineDispatcher() {
-            val coroutines = ArrayList<Pair<CoroutineContext, Runnable>>()
-            override fun dispatch(context: CoroutineContext, block: Runnable) {
-                coroutines.add(context to block)
+        val methodCalls = mutableMapOf<String, MutableList<Thread>>()
+
+        val dataSourceFactory = object : DataSource.Factory<Int, String>() {
+            override fun create(): DataSource<Int, String> {
+                return ThreadCapturingDataSource { methodName ->
+                    methodCalls.getOrPut(methodName) {
+                        mutableListOf()
+                    }.add(Thread.currentThread())
+                }
             }
         }
 
-        var initialized = false
-        val pagingSource = LegacyPagingSource(manualDispatcher) {
-            initialized = true
-            createTestPositionalDataSource(expectInitialLoad = true)
-        }
+        // create an executor special to the legacy data source
+        val executor = Executors.newSingleThreadExecutor()
 
-        assertFalse { initialized }
+        // extract the thread instance from the executor. we'll use it to assert calls later
+        var dataSourceThread: Thread? = null
+        executor.submit {
+            dataSourceThread = Thread.currentThread()
+        }.get()
 
-        // Trigger lazy-initialization dispatch.
-        val job = GlobalScope.launch {
-            pagingSource.load(PagingSource.LoadParams.Refresh(0, 1, false, 1))
-        }
-
-        // Assert that initialization has been scheduled on manualDispatcher, which has not been
-        // triggered yet.
-        assertFalse { initialized }
-
-        // Force all tasks on manualDispatcher to run.
-        while (!job.isCompleted) {
-            while (manualDispatcher.coroutines.isNotEmpty()) {
-                @OptIn(ExperimentalStdlibApi::class)
-                manualDispatcher.coroutines.removeFirst().second.run()
+        val pager = Pager(
+            config = PagingConfig(10, enablePlaceholders = false),
+            pagingSourceFactory = dataSourceFactory.asPagingSourceFactory(
+                executor.asCoroutineDispatcher()
+            )
+        )
+        // collect from pager. we take only 2 paging data generations and only take 1 PageEvent
+        // from them
+        runBlocking {
+            pager.flow.take(2).collectLatest { pagingData ->
+                // wait until first insert happens
+                pagingData.flow.filter {
+                    it is PageEvent.Insert
+                }.first()
+                pagingData.receiver.refresh()
             }
         }
-
-        assertTrue { initialized }
+        // validate method calls (to ensure test did run as expected) and their threads.
+        assertThat(methodCalls["<init>"]).hasSize(2)
+        assertThat(methodCalls["<init>"]?.toSet()).containsExactly(dataSourceThread)
+        assertThat(methodCalls["addInvalidatedCallback"]).hasSize(2)
+        assertThat(methodCalls["addInvalidatedCallback"]?.toSet()).containsExactly(dataSourceThread)
+        assertThat(methodCalls["loadInitial"]).hasSize(2)
+        assertThat(methodCalls).containsKey("isInvalid")
+        assertThat(methodCalls["loadInitial"]?.toSet()).containsExactly(dataSourceThread)
+        // TODO b/174625633 this should also be 2
+        assertThat(methodCalls["removeInvalidatedCallback"]).hasSize(1)
     }
 
     @Test
@@ -333,4 +367,55 @@
                 Assert.fail("loadRange not expected")
             }
         }
+
+    /**
+     * A data source implementation which tracks method calls and their threads.
+     */
+    @Suppress("DEPRECATION")
+    class ThreadCapturingDataSource(
+        private val recordMethodCall: (methodName: String) -> Unit
+    ) : PositionalDataSource<String>() {
+        init {
+            recordMethodCall("<init>")
+        }
+
+        override fun loadInitial(
+            params: LoadInitialParams,
+            callback: LoadInitialCallback<String>
+        ) {
+            recordMethodCall("loadInitial")
+            callback.onResult(
+                data = emptyList(),
+                position = 0,
+            )
+        }
+
+        override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+            recordMethodCall("loadRange")
+            callback.onResult(data = emptyList())
+        }
+
+        override val isInvalid: Boolean
+            get() {
+                // this is important because room's implementation might run a db query to
+                // update invalidations.
+                recordMethodCall("isInvalid")
+                return super.isInvalid
+            }
+
+        override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+            recordMethodCall("addInvalidatedCallback")
+            super.addInvalidatedCallback(onInvalidatedCallback)
+        }
+
+        override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+            recordMethodCall("removeInvalidatedCallback")
+            super.removeInvalidatedCallback(onInvalidatedCallback)
+        }
+
+        override fun invalidate() {
+            recordMethodCall("invalidate")
+            super.invalidate()
+        }
+    }
 }
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
index 21736e3..232c4f7 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
@@ -72,11 +72,12 @@
 class PageFetcherSnapshotTest {
     private val testScope = TestCoroutineScope()
     private val retryBus = ConflatedEventBus<Unit>()
-    private val pagingSourceFactory = {
-        TestPagingSource().also {
+    private val pagingSourceFactory = suspend {
+        TestPagingSource(loadDelay = 1000).also {
             currentPagingSource = it
         }
     }
+
     private var currentPagingSource: TestPagingSource? = null
     private val config = PagingConfig(
         pageSize = 1,
@@ -1752,7 +1753,7 @@
         }
 
         var createdPagingSource = false
-        val factory = {
+        val factory = suspend {
             check(!createdPagingSource)
             createdPagingSource = true
             TestPagingSource(items = List(2) { it })
@@ -1801,7 +1802,7 @@
         }
 
         var createdPagingSource = false
-        val factory = {
+        val factory = suspend {
             check(!createdPagingSource)
             createdPagingSource = true
             TestPagingSource(items = List(2) { it })
@@ -2183,7 +2184,14 @@
                 LoadStateUpdate(REFRESH, true, Loading),
                 LoadStateUpdate(REFRESH, true, Error(EXCEPTION)),
                 LoadStateUpdate(REFRESH, false, Loading),
-                createRefresh(0..2, remoteLoadStatesOf(refreshRemote = Error(EXCEPTION))),
+                createRefresh(
+                    range = 0..2,
+                    remoteLoadStatesOf(
+                        refresh = Error(EXCEPTION),
+                        prependLocal = NotLoading.Complete,
+                        refreshRemote = Error(EXCEPTION),
+                    ),
+                ),
                 // since remote refresh failed and launch initial refresh is requested,
                 // we won't receive any append/prepend events
             )
@@ -2262,6 +2270,79 @@
     }
 
     @Test
+    fun remoteMediator_remoteRefreshEndOfPaginationReached() = testScope.runBlockingTest {
+        @OptIn(ExperimentalPagingApi::class)
+        val remoteMediator = RemoteMediatorMock().apply {
+            initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+            loadCallback = { _, _ -> RemoteMediator.MediatorResult.Success(true) }
+        }
+
+        val config = PagingConfig(
+            pageSize = 1,
+            prefetchDistance = 2,
+            enablePlaceholders = true,
+            initialLoadSize = 1,
+            maxSize = 5
+        )
+        val pager = PageFetcher(
+            initialKey = 0,
+            pagingSourceFactory = { TestPagingSource(items = listOf(0)) },
+            config = config,
+            remoteMediator = remoteMediator
+        )
+
+        val state = collectFetcherState(pager)
+
+        advanceUntilIdle()
+        assertThat(state.newEvents()).isEqualTo(
+            listOf(
+                LoadStateUpdate(loadType = REFRESH, fromMediator = true, loadState = Loading),
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = true,
+                    loadState = NotLoading(endOfPaginationReached = true)
+                ),
+                LoadStateUpdate(
+                    loadType = PREPEND,
+                    fromMediator = true,
+                    loadState = NotLoading(endOfPaginationReached = true)
+                ),
+                LoadStateUpdate(
+                    loadType = APPEND,
+                    fromMediator = true,
+                    loadState = NotLoading(endOfPaginationReached = true)
+                ),
+                LoadStateUpdate(loadType = REFRESH, fromMediator = false, loadState = Loading),
+                Refresh(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(0),
+                            data = listOf(0),
+                            hintOriginalPageOffset = 0,
+                            hintOriginalIndices = null
+                        )
+                    ),
+                    placeholdersBefore = 0,
+                    placeholdersAfter = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        refresh = NotLoading(endOfPaginationReached = true),
+                        prepend = NotLoading(endOfPaginationReached = true),
+                        append = NotLoading(endOfPaginationReached = true),
+                        refreshLocal = NotLoading(endOfPaginationReached = false),
+                        prependLocal = NotLoading(endOfPaginationReached = true),
+                        appendLocal = NotLoading(endOfPaginationReached = true),
+                        refreshRemote = NotLoading(endOfPaginationReached = true),
+                        prependRemote = NotLoading(endOfPaginationReached = true),
+                        appendRemote = NotLoading(endOfPaginationReached = true),
+                    )
+                )
+            )
+        )
+
+        state.job.cancel()
+    }
+
+    @Test
     fun remoteMediator_endOfPaginationNotReachedLoadStatePrepend() = testScope.runBlockingTest {
         @OptIn(ExperimentalPagingApi::class)
         val remoteMediator = object : RemoteMediatorMock() {
@@ -2305,7 +2386,9 @@
                     ),
                     placeholdersBefore = 0,
                     placeholdersAfter = 99,
-                    combinedLoadStates = remoteLoadStatesOf()
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prependLocal = NotLoading.Complete
+                    )
                 ),
                 LoadStateUpdate(
                     loadType = PREPEND,
@@ -2328,7 +2411,9 @@
                     ),
                     placeholdersBefore = 0,
                     placeholdersAfter = 99,
-                    combinedLoadStates = remoteLoadStatesOf()
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prependLocal = NotLoading.Complete
+                    )
                 )
             )
 
@@ -2380,7 +2465,9 @@
                         ),
                         placeholdersBefore = 0,
                         placeholdersAfter = 99,
-                        combinedLoadStates = remoteLoadStatesOf()
+                        combinedLoadStates = remoteLoadStatesOf(
+                            prependLocal = NotLoading.Complete,
+                        )
                     ),
                     LoadStateUpdate(
                         loadType = PREPEND,
@@ -2444,10 +2531,7 @@
                     combinedLoadStates = remoteLoadStatesOf()
                 )
             )
-            assertEvents(
-                eventsByGeneration[0],
-                refreshEvents
-            )
+            assertThat(eventsByGeneration[0]).isEqualTo(refreshEvents)
             accessHint(
                 ViewportHint.Access(
                     pageOffset = 0,
@@ -2485,7 +2569,7 @@
                     loadType = PREPEND,
                     fromMediator = true,
                     loadState = NotLoading.Complete
-                )
+                ),
             )
             awaitEventCount(refreshEvents.size + postHintEvents.size)
             assertEquals(
@@ -2539,7 +2623,9 @@
                         ),
                         placeholdersBefore = 99,
                         placeholdersAfter = 0,
-                        combinedLoadStates = remoteLoadStatesOf()
+                        combinedLoadStates = remoteLoadStatesOf(
+                            appendLocal = NotLoading.Complete,
+                        )
                     ),
                     LoadStateUpdate(
                         loadType = APPEND,
@@ -2562,7 +2648,9 @@
                         ),
                         placeholdersBefore = 99,
                         placeholdersAfter = 0,
-                        combinedLoadStates = remoteLoadStatesOf()
+                        combinedLoadStates = remoteLoadStatesOf(
+                            appendLocal = NotLoading.Complete,
+                        )
                     ),
                 )
             )
@@ -2597,7 +2685,7 @@
         )
 
         val expected = listOf(
-            listOf<PageEvent<Int>>(
+            listOf(
                 LoadStateUpdate(
                     loadType = REFRESH,
                     fromMediator = false,
@@ -2612,7 +2700,9 @@
                     ),
                     placeholdersBefore = 99,
                     placeholdersAfter = 0,
-                    combinedLoadStates = remoteLoadStatesOf()
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                    )
                 ),
                 LoadStateUpdate(
                     loadType = APPEND,
@@ -2716,7 +2806,7 @@
                 ),
             )
             awaitEventCount(initialEvents.size + postHintEvents.size)
-            assertEvents(initialEvents + postHintEvents, eventsByGeneration[0])
+            assertThat(eventsByGeneration[0]).isEqualTo(initialEvents + postHintEvents)
         }
     }
 
@@ -2760,8 +2850,13 @@
                         fromMediator = true,
                         loadState = Loading
                     ),
+                    LoadStateUpdate(
+                        loadType = REFRESH,
+                        fromMediator = true,
+                        loadState = NotLoading.Incomplete
+                    ),
                 ),
-                listOf<PageEvent<Int>>(
+                listOf(
                     LoadStateUpdate(
                         loadType = REFRESH,
                         fromMediator = false,
@@ -2786,7 +2881,7 @@
     @Test
     fun remoteMediator_initialRefreshSuccessEndOfPagination() = testScope.runBlockingTest {
         @OptIn(ExperimentalPagingApi::class)
-        val remoteMediator = object : RemoteMediatorMock() {
+        val remoteMediator = object : RemoteMediatorMock(loadDelay = 2000) {
             override suspend fun initialize(): InitializeAction {
                 super.initialize()
                 return InitializeAction.LAUNCH_INITIAL_REFRESH
@@ -2810,57 +2905,79 @@
         )
         val pager = PageFetcher(
             initialKey = 50,
-            pagingSourceFactory = pagingSourceFactory,
+            pagingSourceFactory = {
+                TestPagingSource().apply {
+                    nextLoadResult = Page(
+                        data = listOf(50),
+                        prevKey = null,
+                        nextKey = null,
+                        itemsBefore = 50,
+                        itemsAfter = 49
+                    )
+                }
+            },
             config = config,
             remoteMediator = remoteMediator
         )
 
-        pager.assertEventByGeneration(
+        val fetcherState = collectFetcherState(pager)
+
+        advanceTimeBy(1000)
+
+        assertThat(fetcherState.newEvents()).isEqualTo(
             listOf(
-                listOf(
-                    LoadStateUpdate(
-                        loadType = REFRESH,
-                        fromMediator = true,
-                        loadState = Loading,
-                    ),
-                    LoadStateUpdate(
-                        loadType = REFRESH,
-                        fromMediator = true,
-                        loadState = NotLoading.Complete,
-                    ),
-                    LoadStateUpdate(
-                        loadType = PREPEND,
-                        fromMediator = true,
-                        loadState = NotLoading.Complete,
-                    ),
-                    LoadStateUpdate(
-                        loadType = APPEND,
-                        fromMediator = true,
-                        loadState = NotLoading.Complete,
-                    ),
-                    LoadStateUpdate(
-                        loadType = REFRESH,
-                        fromMediator = false,
-                        loadState = Loading,
-                    ),
-                    Refresh(
-                        pages = listOf(
-                            TransformablePage(
-                                originalPageOffset = 0,
-                                data = listOf(50)
-                            )
-                        ),
-                        placeholdersBefore = 50,
-                        placeholdersAfter = 49,
-                        combinedLoadStates = remoteLoadStatesOf(
-                            refreshRemote = NotLoading.Complete,
-                            prependRemote = NotLoading.Complete,
-                            appendRemote = NotLoading.Complete,
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = true,
+                    loadState = Loading,
+                ),
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = false,
+                    loadState = Loading,
+                ),
+                Refresh(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffset = 0,
+                            data = listOf(50)
                         )
                     ),
+                    placeholdersBefore = 50,
+                    placeholdersAfter = 49,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        refresh = Loading,
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        refreshRemote = Loading,
+                    )
+                ),
+            ),
+        )
+
+        advanceUntilIdle()
+
+        assertThat(fetcherState.newEvents()).isEqualTo(
+            listOf<PageEvent<Int>>(
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = true,
+                    loadState = NotLoading.Complete,
+                ),
+                LoadStateUpdate(
+                    loadType = PREPEND,
+                    fromMediator = true,
+                    loadState = NotLoading.Complete
+                ),
+                LoadStateUpdate(
+                    loadType = APPEND,
+                    fromMediator = true,
+                    loadState = NotLoading.Complete
                 ),
             )
         )
+
+        fetcherState.job.cancel()
     }
 
     @Test
@@ -3225,15 +3342,6 @@
         assertFalse { initialHint.shouldPrioritizeOver(accessHint, APPEND) }
     }
 
-    @OptIn(ExperimentalPagingApi::class)
-    private suspend fun <Key : Any, Value : Any> createRemoteMediatorAccessor(
-        delegate: RemoteMediator<Key, Value>
-    ): RemoteMediatorAccessor<Key, Value> {
-        return RemoteMediatorAccessor(testScope, delegate).also {
-            it.initialize()
-        }
-    }
-
     internal class CollectedPageEvents<T : Any>(val pageEvents: ArrayList<PageEvent<T>>) {
         var lastIndex = 0
         fun newEvents(): List<PageEvent<T>> = when {
@@ -3309,7 +3417,7 @@
             stop()
         }
         expected.forEachIndexed { index, list ->
-            assertEvents(list, actual.getOrNull(index) ?: emptyList())
+            assertThat(actual.getOrNull(index) ?: emptyList<PageEvent<T>>()).isEqualTo(list)
         }
         assertThat(actual.size).isEqualTo(expected.size)
     }
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
index b347092..00e7d99 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
@@ -49,7 +49,7 @@
 @RunWith(JUnit4::class)
 class PageFetcherTest {
     private val testScope = TestCoroutineScope()
-    private val pagingSourceFactory = { TestPagingSource() }
+    private val pagingSourceFactory = suspend { TestPagingSource() }
     private val config = PagingConfig(
         pageSize = 1,
         prefetchDistance = 1,
@@ -91,7 +91,9 @@
     @Test
     fun refresh_fromPagingSource() = testScope.runBlockingTest {
         var pagingSource: PagingSource<Int, Int>? = null
-        val pagingSourceFactory = { TestPagingSource().also { pagingSource = it } }
+        val pagingSourceFactory = suspend {
+            TestPagingSource().also { pagingSource = it }
+        }
         val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
         val fetcherState = collectFetcherState(pageFetcher)
 
@@ -114,7 +116,9 @@
     @Test
     fun refresh_callsInvalidate() = testScope.runBlockingTest {
         var pagingSource: PagingSource<Int, Int>? = null
-        val pagingSourceFactory = { TestPagingSource().also { pagingSource = it } }
+        val pagingSourceFactory = suspend {
+            TestPagingSource().also { pagingSource = it }
+        }
         val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
         val fetcherState = collectFetcherState(pageFetcher)
 
@@ -303,7 +307,9 @@
     fun jump() = testScope.runBlockingTest {
         pauseDispatcher {
             val pagingSources = mutableListOf<PagingSource<Int, Int>>()
-            val pagingSourceFactory = { TestPagingSource().also { pagingSources.add(it) } }
+            val pagingSourceFactory = suspend {
+                TestPagingSource().also { pagingSources.add(it) }
+            }
             val config = PagingConfig(
                 pageSize = 1,
                 prefetchDistance = 1,
@@ -382,7 +388,11 @@
                 prefetchDistance = 1,
                 initialLoadSize = 2
             )
-            val pageFetcher = PageFetcher({ pagingSource }, 50, config)
+            val pageFetcher = PageFetcher(
+                pagingSourceFactory = suspend { pagingSource },
+                initialKey = 50,
+                config = config
+            )
             val job = testScope.launch {
                 assertFailsWith<IllegalStateException> {
                     pageFetcher.flow.collect { }
@@ -444,7 +454,7 @@
             )
             val pagingSources = mutableListOf<TestPagingSource>()
             val pageFetcher = PageFetcher(
-                pagingSourceFactory = {
+                pagingSourceFactory = suspend {
                     TestPagingSource(loadDelay = 1000).also {
                         pagingSources.add(it)
                     }
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
index 44398e8..c94b1cf 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
@@ -18,6 +18,9 @@
 
 import androidx.testutils.TestDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineDispatcher
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -86,16 +89,17 @@
         }
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun loadFullVerify() {
         // validate paging entire ItemDataSource results in full, correctly ordered data
-        val testCoroutineScope = CoroutineScope(EmptyCoroutineContext)
-
+        val dispatcher = TestCoroutineDispatcher()
+        val testCoroutineScope = CoroutineScope(dispatcher)
         @Suppress("DEPRECATION")
         val pagedList = PagedList.Builder(ItemDataSource(), 100)
             .setCoroutineScope(testCoroutineScope)
-            .setNotifyDispatcher(mainThread)
-            .setFetchDispatcher(DirectDispatcher)
+            .setNotifyDispatcher(dispatcher)
+            .setFetchDispatcher(dispatcher)
             .build()
 
         // validate initial load
@@ -105,7 +109,7 @@
         for (i in 0..PAGE_MAP.keys.size) {
             pagedList.loadAround(0)
             pagedList.loadAround(pagedList.size - 1)
-            drain()
+            dispatcher.advanceUntilIdle()
         }
 
         // validate full load
@@ -148,7 +152,7 @@
         @Suppress("DEPRECATION")
         PagedList.Builder(dataSource, 10)
             .setNotifyDispatcher(FailDispatcher())
-            .setFetchDispatcher(DirectDispatcher)
+            .setFetchDispatcher(Dispatchers.IO)
             .build()
     }
 
@@ -248,7 +252,7 @@
         val pagedList = PagedList.Builder(dataSource, 10)
             .setBoundaryCallback(boundaryCallback)
             .setCoroutineScope(testCoroutineScope)
-            .setFetchDispatcher(dispatcher)
+            .setFetchDispatcher(Dispatchers.Unconfined)
             .setNotifyDispatcher(dispatcher)
             .build()
 
@@ -301,7 +305,7 @@
         val pagedList = PagedList.Builder(dataSource, 10)
             .setBoundaryCallback(boundaryCallback)
             .setCoroutineScope(testCoroutineScope)
-            .setFetchDispatcher(dispatcher)
+            .setFetchDispatcher(Dispatchers.Unconfined)
             .setNotifyDispatcher(dispatcher)
             .build()
 
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
index abb5242..a0ea57f 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
@@ -21,11 +21,14 @@
 import androidx.testutils.TestDispatcher
 import androidx.testutils.TestExecutor
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import java.util.concurrent.Executor
+import kotlin.concurrent.thread
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.test.assertFailsWith
 import kotlin.test.assertTrue
@@ -53,10 +56,18 @@
 
     @Test
     fun createLegacy() {
+        val slowFetchExecutor = Executor {
+            // just be slow to ensure `build()` really waited on fetch to complete.
+            // but still run it on another thread to ensure we are not blocking the test here
+            thread {
+                Thread.sleep(1000)
+                it.run()
+            }
+        }
         @Suppress("DEPRECATION")
         val pagedList = PagedList.Builder(TestPositionalDataSource(ITEMS), 100)
             .setNotifyExecutor(TestExecutor())
-            .setFetchExecutor(TestExecutor())
+            .setFetchExecutor(slowFetchExecutor)
             .build()
         // if build succeeds without flushing an executor, success!
         assertEquals(ITEMS, pagedList)
@@ -76,8 +87,8 @@
                     pagingSource,
                     null,
                     testCoroutineScope,
-                    DirectDispatcher,
-                    DirectDispatcher,
+                    Dispatchers.Default,
+                    Dispatchers.IO,
                     null,
                     Config(10),
                     0
@@ -104,8 +115,8 @@
                     pagingSource,
                     null,
                     testCoroutineScope,
-                    DirectDispatcher,
-                    DirectDispatcher,
+                    Dispatchers.Default,
+                    Dispatchers.IO,
                     null,
                     Config(10),
                     0
@@ -121,14 +132,13 @@
                 key = null,
                 loadSize = 10,
                 placeholdersEnabled = false,
-                pageSize = 10
             )
         ) as PagingSource.LoadResult.Page
 
         @Suppress("DEPRECATION")
         val pagedList = PagedList.Builder(pagingSource, initialPage, config)
-            .setNotifyDispatcher(DirectDispatcher)
-            .setFetchDispatcher(DirectDispatcher)
+            .setNotifyDispatcher(Dispatchers.Default)
+            .setFetchDispatcher(Dispatchers.IO)
             .build()
 
         assertEquals(pagingSource, pagedList.pagingSource)
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
index 54047f9..6f90f6b 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
@@ -43,7 +43,6 @@
                 key,
                 initialLoadSize,
                 enablePlaceholders,
-                10
             )
         )
     }
@@ -61,7 +60,7 @@
 
             // Verify error is propagated correctly.
             pagingSource.enqueueError()
-            val errorParams = LoadParams.Refresh(key, 10, false, 10)
+            val errorParams = LoadParams.Refresh(key, 10, false)
             assertFailsWith<CustomException> {
                 pagingSource.load(errorParams)
             }
@@ -193,7 +192,7 @@
 
         runBlocking {
             val key = ITEMS_BY_NAME_ID[5].key()
-            val params = LoadParams.Prepend(key, 5, false, 5)
+            val params = LoadParams.Prepend(key, 5, false)
             val observed = (dataSource.load(params) as LoadResult.Page).data
 
             assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
@@ -201,7 +200,7 @@
             // Verify error is propagated correctly.
             dataSource.enqueueError()
             assertFailsWith<CustomException> {
-                val errorParams = LoadParams.Prepend(key, 5, false, 5)
+                val errorParams = LoadParams.Prepend(key, 5, false)
                 dataSource.load(errorParams)
             }
         }
@@ -213,7 +212,7 @@
 
         runBlocking {
             val key = ITEMS_BY_NAME_ID[5].key()
-            val params = LoadParams.Append(key, 5, false, 5)
+            val params = LoadParams.Append(key, 5, false)
             val observed = (dataSource.load(params) as LoadResult.Page).data
 
             assertEquals(ITEMS_BY_NAME_ID.subList(6, 11), observed)
@@ -221,7 +220,7 @@
             // Verify error is propagated correctly.
             dataSource.enqueueError()
             assertFailsWith<CustomException> {
-                val errorParams = LoadParams.Append(key, 5, false, 5)
+                val errorParams = LoadParams.Append(key, 5, false)
                 dataSource.load(errorParams)
             }
         }
@@ -257,7 +256,6 @@
 
         private var error = false
 
-        @OptIn(ExperimentalPagingApi::class)
         override fun getRefreshKey(state: PagingState<Key, Item>): Key? {
             return state.anchorPosition
                 ?.let { anchorPosition -> state.closestItemToPosition(anchorPosition) }
diff --git a/paging/common/src/test/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt b/paging/common/src/test/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
index 151a55d..29d1dbd 100644
--- a/paging/common/src/test/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/RemoteMediatorAccessorTest.kt
@@ -39,6 +39,7 @@
 class RemoteMediatorAccessorTest {
     private val testScope = TestCoroutineScope()
     private var mockStateId = 0
+
     // creates a unique state using the anchor position to be able to do equals check in assertions
     private fun createMockState(
         anchorPosition: Int? = mockStateId++
@@ -52,6 +53,110 @@
     }
 
     @Test
+    fun load_reportsPrependLoadState() = testScope.runBlockingTest {
+        val emptyState = PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+        val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
+        val remoteMediatorAccessor = createAccessor(remoteMediator)
+
+        // Assert initial state is NotLoading.Incomplete.
+        assertEquals(
+            LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
+            remoteMediatorAccessor.state.value,
+        )
+
+        // Start a PREPEND load.
+        remoteMediatorAccessor.requestLoad(
+            loadType = PREPEND,
+            pagingState = emptyState,
+        )
+
+        // Assert state is immediately set to Loading.
+        assertEquals(
+            LoadStates.IDLE.copy(prepend = LoadState.Loading),
+            remoteMediatorAccessor.state.value,
+        )
+
+        // Wait for load to finish.
+        advanceUntilIdle()
+
+        // Assert state is set to NotLoading.Incomplete.
+        assertEquals(
+            LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
+            remoteMediatorAccessor.state.value,
+        )
+
+        // Start a PREPEND load which results in endOfPaginationReached = true.
+        remoteMediator.loadCallback = { _, _ ->
+            RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+        }
+        remoteMediatorAccessor.requestLoad(
+            loadType = PREPEND,
+            pagingState = emptyState,
+        )
+
+        // Wait for load to finish.
+        advanceUntilIdle()
+
+        // Assert state is set to NotLoading.Incomplete.
+        assertEquals(
+            LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Complete),
+            remoteMediatorAccessor.state.value,
+        )
+    }
+
+    @Test
+    fun load_reportsAppendLoadState() = testScope.runBlockingTest {
+        val emptyState = PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
+        val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
+        val remoteMediatorAccessor = createAccessor(remoteMediator)
+
+        // Assert initial state is NotLoading.Incomplete.
+        assertEquals(
+            LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
+            remoteMediatorAccessor.state.value,
+        )
+
+        // Start a APPEND load.
+        remoteMediatorAccessor.requestLoad(
+            loadType = APPEND,
+            pagingState = emptyState,
+        )
+
+        // Assert state is immediately set to Loading.
+        assertEquals(
+            LoadStates.IDLE.copy(append = LoadState.Loading),
+            remoteMediatorAccessor.state.value,
+        )
+
+        // Wait for load to finish.
+        advanceUntilIdle()
+
+        // Assert state is set to NotLoading.Incomplete.
+        assertEquals(
+            LoadStates.IDLE.copy(append = LoadState.NotLoading.Incomplete),
+            remoteMediatorAccessor.state.value,
+        )
+
+        // Start a APPEND load which results in endOfPaginationReached = true.
+        remoteMediator.loadCallback = { _, _ ->
+            RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+        }
+        remoteMediatorAccessor.requestLoad(
+            loadType = APPEND,
+            pagingState = emptyState,
+        )
+
+        // Wait for load to finish.
+        advanceUntilIdle()
+
+        // Assert state is set to NotLoading.Incomplete.
+        assertEquals(
+            LoadStates.IDLE.copy(append = LoadState.NotLoading.Complete),
+            remoteMediatorAccessor.state.value,
+        )
+    }
+
+    @Test
     fun load_conflatesPrepend() = testScope.runBlockingTest {
         val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
         val remoteMediatorAccessor = createAccessor(remoteMediator)
diff --git a/paging/common/src/test/kotlin/androidx/paging/SeparatorsTest.kt b/paging/common/src/test/kotlin/androidx/paging/SeparatorsTest.kt
index 38d8dc7..276285b 100644
--- a/paging/common/src/test/kotlin/androidx/paging/SeparatorsTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/SeparatorsTest.kt
@@ -19,10 +19,12 @@
 import androidx.paging.LoadState.NotLoading
 import androidx.paging.LoadType.APPEND
 import androidx.paging.LoadType.PREPEND
+import androidx.paging.LoadType.REFRESH
 import androidx.paging.PageEvent.Drop
 import androidx.paging.PageEvent.Insert.Companion.Append
 import androidx.paging.PageEvent.Insert.Companion.Prepend
 import androidx.paging.PageEvent.Insert.Companion.Refresh
+import androidx.paging.PageEvent.LoadStateUpdate
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
@@ -985,6 +987,481 @@
         )
     }
 
+    @Test
+    fun remoteRefreshEndOfPaginationReached() = runBlockingTest {
+        assertThat(
+            flowOf(
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = true,
+                    loadState = LoadState.Loading
+                ),
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = false,
+                    loadState = LoadState.Loading
+                ),
+                Refresh(
+                    pages = listOf(listOf("a1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = true,
+                    loadState = NotLoading(endOfPaginationReached = true)
+                ),
+                LoadStateUpdate(
+                    loadType = PREPEND,
+                    fromMediator = true,
+                    loadState = NotLoading(endOfPaginationReached = true)
+                ),
+                LoadStateUpdate(
+                    loadType = APPEND,
+                    fromMediator = true,
+                    loadState = NotLoading(endOfPaginationReached = true)
+                ),
+            ).insertEventSeparators(LETTER_SEPARATOR_GENERATOR).toList()
+        ).isEqualTo(
+            listOf(
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = true,
+                    loadState = LoadState.Loading
+                ),
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = false,
+                    loadState = LoadState.Loading
+                ),
+                Refresh(
+                    pages = listOf(listOf("a1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                LoadStateUpdate(
+                    loadType = REFRESH,
+                    fromMediator = true,
+                    loadState = NotLoading(endOfPaginationReached = true)
+                ),
+                Prepend(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(0),
+                            data = listOf("A"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 0,
+                        ),
+                    ),
+                    placeholdersBefore = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        refresh = NotLoading.Complete,
+                        prepend = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        refreshRemote = NotLoading.Complete,
+                        prependRemote = NotLoading.Complete,
+                    ),
+                ),
+                Append(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(0),
+                            data = listOf("END"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 0,
+                        ),
+                    ),
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        refresh = NotLoading.Complete,
+                        prepend = NotLoading.Complete,
+                        append = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        refreshRemote = NotLoading.Complete,
+                        prependRemote = NotLoading.Complete,
+                        appendRemote = NotLoading.Complete,
+                    ),
+                ),
+            )
+        )
+    }
+
+    @Test
+    fun remotePrependEndOfPaginationReached() = runBlockingTest {
+        assertThat(
+            flowOf(
+                Refresh(
+                    pages = listOf(listOf("a1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                // Signalling that remote prepend is done triggers the header to resolve.
+                LoadStateUpdate(PREPEND, true, NotLoading.Complete),
+            ).insertEventSeparators(LETTER_SEPARATOR_GENERATOR).toList()
+        ).isEqualTo(
+            listOf(
+                Refresh(
+                    pages = listOf(listOf("a1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                Prepend(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(0),
+                            data = listOf("A"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 0,
+                        ),
+                    ),
+                    placeholdersBefore = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prepend = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        prependRemote = NotLoading.Complete,
+                    ),
+                ),
+            )
+        )
+    }
+
+    @Test
+    fun remotePrependEndOfPaginationReachedWithDrops() = runBlockingTest {
+        assertThat(
+            flowOf(
+                Refresh(
+                    pages = listOf(listOf("b1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Incomplete,
+                    )
+                ),
+                Prepend(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffset = -1,
+                            data = listOf("a1"),
+                        )
+                    ),
+                    placeholdersBefore = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                // Signalling that remote prepend is done triggers the header to resolve.
+                LoadStateUpdate(PREPEND, true, NotLoading.Complete),
+                // Drop the first page, header and separator between "b1" and "a1"
+                Drop(
+                    loadType = PREPEND,
+                    minPageOffset = -1,
+                    maxPageOffset = -1,
+                    placeholdersRemaining = 1
+                ),
+                Prepend(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffset = -1,
+                            data = listOf("a1"),
+                        )
+                    ),
+                    placeholdersBefore = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prepend = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        prependRemote = NotLoading.Complete,
+                    )
+                ),
+            ).insertEventSeparators(LETTER_SEPARATOR_GENERATOR).toList()
+        ).isEqualTo(
+            listOf(
+                Refresh(
+                    pages = listOf(listOf("b1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Incomplete,
+                    )
+                ),
+                Prepend(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffset = -1,
+                            data = listOf("a1"),
+                        ),
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(-1, 0),
+                            data = listOf("B"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = -1
+                        ),
+                    ),
+                    placeholdersBefore = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                Prepend(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(-1),
+                            data = listOf("A"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = -1,
+                        ),
+                    ),
+                    placeholdersBefore = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prepend = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        prependRemote = NotLoading.Complete,
+                    ),
+                ),
+                Drop(
+                    loadType = PREPEND,
+                    minPageOffset = -1,
+                    maxPageOffset = -1,
+                    placeholdersRemaining = 1
+                ),
+                Prepend(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(-1),
+                            data = listOf("A"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = -1,
+                        ),
+                        TransformablePage(
+                            originalPageOffset = -1,
+                            data = listOf("a1"),
+                        ),
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(-1, 0),
+                            data = listOf("B"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = -1
+                        ),
+                    ),
+                    placeholdersBefore = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prepend = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        prependRemote = NotLoading.Complete,
+                    )
+                ),
+            )
+        )
+    }
+
+    @Test
+    fun remoteAppendEndOfPaginationReached() = runBlockingTest {
+        assertThat(
+            flowOf(
+                Refresh(
+                    pages = listOf(listOf("a1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                // Signalling that remote append is done triggers the footer to resolve.
+                LoadStateUpdate(APPEND, true, NotLoading.Complete),
+            ).insertEventSeparators(LETTER_SEPARATOR_GENERATOR).toList()
+        ).isEqualTo(
+            listOf(
+                Refresh(
+                    pages = listOf(listOf("a1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                    )
+                ),
+                Append(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(0),
+                            data = listOf("END"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 0,
+                        ),
+                    ),
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        append = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        appendRemote = NotLoading.Complete,
+                    ),
+                ),
+            )
+        )
+    }
+
+    @Test
+    fun remoteAppendEndOfPaginationReachedWithDrops() = runBlockingTest {
+        assertThat(
+            flowOf(
+                Refresh(
+                    pages = listOf(listOf("b1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Incomplete,
+                    )
+                ),
+                Append(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffset = 1,
+                            data = listOf("c1"),
+                        )
+                    ),
+                    placeholdersAfter = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                    )
+                ),
+                // Signalling that remote append is done triggers the footer to resolve.
+                LoadStateUpdate(APPEND, true, NotLoading.Complete),
+                // Drop the last page, footer and separator between "b1" and "c1"
+                Drop(
+                    loadType = APPEND,
+                    minPageOffset = 1,
+                    maxPageOffset = 1,
+                    placeholdersRemaining = 1
+                ),
+                Append(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffset = 1,
+                            data = listOf("c1"),
+                        )
+                    ),
+                    placeholdersAfter = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        append = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        appendRemote = NotLoading.Complete,
+                    )
+                ),
+            ).insertEventSeparators(LETTER_SEPARATOR_GENERATOR).toList()
+        ).isEqualTo(
+            listOf(
+                Refresh(
+                    pages = listOf(listOf("b1")).toTransformablePages(),
+                    placeholdersBefore = 1,
+                    placeholdersAfter = 1,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Incomplete,
+                    )
+                ),
+                Append(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(0, 1),
+                            data = listOf("C"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 1
+                        ),
+                        TransformablePage(
+                            originalPageOffset = 1,
+                            data = listOf("c1"),
+                        ),
+                    ),
+                    placeholdersAfter = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                    )
+                ),
+                Append(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(1),
+                            data = listOf("END"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 1,
+                        ),
+                    ),
+                    placeholdersAfter = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        append = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        appendRemote = NotLoading.Complete,
+                    ),
+                ),
+                Drop(
+                    loadType = APPEND,
+                    minPageOffset = 1,
+                    maxPageOffset = 1,
+                    placeholdersRemaining = 1
+                ),
+                Append(
+                    pages = listOf(
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(0, 1),
+                            data = listOf("C"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 1
+                        ),
+                        TransformablePage(
+                            originalPageOffset = 1,
+                            data = listOf("c1"),
+                        ),
+                        TransformablePage(
+                            originalPageOffsets = intArrayOf(1),
+                            data = listOf("END"),
+                            hintOriginalIndices = listOf(0),
+                            hintOriginalPageOffset = 1
+                        ),
+                    ),
+                    placeholdersAfter = 0,
+                    combinedLoadStates = remoteLoadStatesOf(
+                        append = NotLoading.Complete,
+                        prependLocal = NotLoading.Complete,
+                        appendLocal = NotLoading.Complete,
+                        appendRemote = NotLoading.Complete,
+                    )
+                ),
+            )
+        )
+    }
+
     companion object {
         /**
          * Creates an upper-case letter at the beginning of each section of strings that start
diff --git a/paging/common/src/test/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt
index 2c90329..f642d6c 100644
--- a/paging/common/src/test/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/WrappedItemKeyedDataSourceTest.kt
@@ -21,7 +21,6 @@
 import org.junit.runners.JUnit4
 import kotlin.test.assertTrue
 
-@OptIn(ExperimentalPagingApi::class)
 @RunWith(JUnit4::class)
 class WrappedItemKeyedDataSourceTest {
 
diff --git a/paging/common/src/test/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt
index 129cd3f..cf4ef24 100644
--- a/paging/common/src/test/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/WrappedPageKeyedDataSourceTest.kt
@@ -21,7 +21,6 @@
 import org.junit.runners.JUnit4
 import kotlin.test.assertTrue
 
-@OptIn(ExperimentalPagingApi::class)
 @RunWith(JUnit4::class)
 class WrappedPageKeyedDataSourceTest {
 
diff --git a/paging/common/src/test/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt
index cc02f84..b56fe139 100644
--- a/paging/common/src/test/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/WrappedPositionalDataSourceTest.kt
@@ -21,7 +21,6 @@
 import org.junit.runners.JUnit4
 import kotlin.test.assertTrue
 
-@OptIn(ExperimentalPagingApi::class)
 @RunWith(JUnit4::class)
 class WrappedPositionalDataSourceTest {
 
diff --git a/paging/guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt b/paging/guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt
index 811c92a..1b4ef95 100644
--- a/paging/guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt
+++ b/paging/guava/src/test/java/androidx/paging/ListenableFuturePagingSourceTest.kt
@@ -60,14 +60,14 @@
 
     @Test
     fun basic() = runBlocking {
-        val params = LoadParams.Refresh(0, 2, false, 2)
+        val params = LoadParams.Refresh(0, 2, false)
         assertEquals(pagingSource.load(params), listenableFuturePagingSource.load(params))
     }
 
     @Test
     fun error() {
         runBlocking {
-            val params = LoadParams.Refresh<Int>(null, 2, false, 2)
+            val params = LoadParams.Refresh<Int>(null, 2, false)
             assertFailsWith<NullPointerException> { pagingSource.load(params) }
             assertFailsWith<NullPointerException> { listenableFuturePagingSource.load(params) }
         }
@@ -76,7 +76,7 @@
     @Test
     fun errorWrapped() {
         runBlocking {
-            val params = LoadParams.Refresh(-1, 2, false, 2)
+            val params = LoadParams.Refresh(-1, 2, false)
             assertFailsWith<IllegalArgumentException> { pagingSource.load(params) }
             assertFailsWith<IllegalArgumentException> { listenableFuturePagingSource.load(params) }
         }
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
index 5ce84fd..a107d6e 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import androidx.annotation.ColorInt
-import androidx.paging.ExperimentalPagingApi
 import androidx.paging.PagingSource
 import androidx.paging.PagingState
 import kotlinx.coroutines.delay
@@ -34,7 +33,6 @@
 
     private val generationId = sGenerationId++
 
-    @OptIn(ExperimentalPagingApi::class)
     override fun getRefreshKey(state: PagingState<Int, Item>): Int? = state.anchorPosition
 
     override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> =
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerDao.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerDao.java
index 1b400ca..58cd34c 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerDao.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/CustomerDao.java
@@ -52,16 +52,16 @@
     void removeAll();
 
     /**
-     * @return DataSource.Factory of customers, ordered by last name. Use
+     * @return DataSource.Factory of customers, ordered by id. Use
      * {@link androidx.paging.LivePagedListBuilder} to get a LiveData of PagedLists.
      */
-    @Query("SELECT * FROM customer ORDER BY mLastName ASC")
+    @Query("SELECT * FROM customer ORDER BY mId ASC")
     DataSource.Factory<Integer, Customer> loadPagedAgeOrder();
 
     /**
-     * @return PagingSource of customers, ordered by last name.
+     * @return PagingSource of customers, ordered by id.
      */
-    @Query("SELECT * FROM customer ORDER BY mLastName ASC")
+    @Query("SELECT * FROM customer ORDER BY mId ASC")
     PagingSource<Integer, Customer> loadPagedAgeOrderPagingSource();
 
     /**
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt
index a8eb290..e7f4e4a 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3/ItemPagingSource.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import androidx.annotation.ColorInt
-import androidx.paging.ExperimentalPagingApi
 import androidx.paging.PagingSource
 import androidx.paging.PagingState
 import kotlinx.coroutines.delay
@@ -34,7 +33,6 @@
 
     private val generationId = sGenerationId++
 
-    @OptIn(ExperimentalPagingApi::class)
     override fun getRefreshKey(state: PagingState<Int, Item>): Int? = state.anchorPosition
 
     override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> =
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt
index ae1744b..13709de 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/NetworkCustomerPagingSource.kt
@@ -16,7 +16,6 @@
 
 package androidx.paging.integration.testapp.v3room
 
-import androidx.paging.ExperimentalPagingApi
 import androidx.paging.PagingSource
 import androidx.paging.PagingState
 import androidx.paging.integration.testapp.room.Customer
@@ -28,20 +27,27 @@
 internal class NetworkCustomerPagingSource : PagingSource<Int, Customer>() {
     private fun createCustomer(i: Int): Customer {
         val customer = Customer()
-        customer.name = UUID.randomUUID().toString()
+        customer.name = "customer_$i"
         customer.lastName = "${"%04d".format(i)}_${UUID.randomUUID()}"
         return customer
     }
 
-    @OptIn(ExperimentalPagingApi::class)
-    override fun getRefreshKey(state: PagingState<Int, Customer>): Int? = state.anchorPosition
+    override fun getRefreshKey(
+        state: PagingState<Int, Customer>
+    ): Int? = state.anchorPosition?.let {
+        maxOf(0, it - 5)
+    }
 
     override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Customer> {
         val key = params.key ?: 0
-        val data = List(params.loadSize) { createCustomer(it + key) }
+        val data = if (params is LoadParams.Prepend) {
+            List(params.loadSize) { createCustomer(it + key - params.loadSize) }
+        } else {
+            List(params.loadSize) { createCustomer(it + key) }
+        }
         return LoadResult.Page(
             data = data,
-            prevKey = if (key > 0) key - 1 else null,
+            prevKey = if (key > 0) key else null,
             nextKey = key + data.size
         )
     }
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
index de5eaf8..761a3d8 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
@@ -36,7 +36,9 @@
         loadType: LoadType,
         state: PagingState<Int, Customer>
     ): MediatorResult {
-        if (loadType == LoadType.PREPEND) return MediatorResult.Success(false)
+        if (loadType == LoadType.PREPEND) {
+            return MediatorResult.Success(endOfPaginationReached = true)
+        }
 
         // TODO: Move this to be a more fully featured sample which demonstrated key translation
         //  between two types of PagingSources where the keys do not map 1:1.
@@ -44,15 +46,13 @@
             LoadType.REFRESH -> PagingSource.LoadParams.Refresh(
                 key = 0,
                 loadSize = 10,
-                placeholdersEnabled = false,
-                pageSize = 10
+                placeholdersEnabled = false
             )
             LoadType.PREPEND -> throw IllegalStateException()
             LoadType.APPEND -> PagingSource.LoadParams.Append(
                 key = state.pages.lastOrNull()?.nextKey ?: 0,
                 loadSize = 10,
-                placeholdersEnabled = false,
-                pageSize = 10
+                placeholdersEnabled = false
             )
         }
 
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt
index 5e0f1bf..a1e2dfb 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomAdapter.kt
@@ -40,7 +40,7 @@
     override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
         val item = getItem(position)
         if (item != null) {
-            (holder.itemView as TextView).text = item.lastName
+            (holder.itemView as TextView).text = item.name
             holder.itemView.setBackgroundColor(Color.BLUE)
         } else {
             (holder.itemView as TextView).setText(R.string.loading)
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
index 27e4eea..8c84a91 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
@@ -21,6 +21,7 @@
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
+import androidx.paging.ExperimentalPagingApi
 import androidx.paging.Pager
 import androidx.paging.PagingConfig
 import androidx.paging.cachedIn
@@ -57,6 +58,7 @@
             .executeOnDiskIO { database.customerDao.removeAll() }
     }
 
+    @OptIn(ExperimentalPagingApi::class)
     val flow = Pager(
         PagingConfig(10),
         remoteMediator = V3RemoteMediator(
@@ -76,7 +78,7 @@
                         Customer().apply {
                             id = -1
                             name = "RIGHT ABOVE DIVIDER"
-                            lastName = "LAST NAME"
+                            lastName = "RIGHT ABOVE DIVIDER"
                         }
                     }
                 }
@@ -85,19 +87,21 @@
                         Customer().apply {
                             id = -2
                             name = "RIGHT BELOW DIVIDER"
-                            lastName = "LAST NAME"
+                            lastName = "RIGHT BELOW DIVIDER"
                         }
                     } else null
                 }
                 .insertHeaderItem(
                     Customer().apply {
                         id = Int.MIN_VALUE
+                        name = "HEADER"
                         lastName = "HEADER"
                     }
                 )
                 .insertFooterItem(
                     Customer().apply {
                         id = Int.MAX_VALUE
+                        name = "FOOTER"
                         lastName = "FOOTER"
                     }
                 )
diff --git a/paging/paging-compose/build.gradle b/paging/paging-compose/build.gradle
index 2d099c2..ef5d031 100644
--- a/paging/paging-compose/build.gradle
+++ b/paging/paging-compose/build.gradle
@@ -35,7 +35,7 @@
 
     implementation(KOTLIN_STDLIB)
     api projectOrArtifact(":compose:foundation:foundation")
-    api("androidx.paging:paging-common-ktx:3.0.0-alpha06")
+    api projectOrArtifact(":paging:paging-common")
 
     androidTestImplementation projectOrArtifact(":compose:ui:ui-test-junit4")
     androidTestImplementation projectOrArtifact(':internal-testutils-paging')
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index 12200c8..9ac87ce 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -166,7 +166,12 @@
      * A [CombinedLoadStates] object which represents the current loading state.
      */
     public var loadState: CombinedLoadStates by mutableStateOf(
-        CombinedLoadStates(InitialLoadStates)
+        CombinedLoadStates(
+            refresh = InitialLoadStates.refresh,
+            prepend = InitialLoadStates.prepend,
+            append = InitialLoadStates.append,
+            source = InitialLoadStates,
+        )
     )
         private set
 
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
index 0435993..b0e3005 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
@@ -65,11 +65,16 @@
         data: List<V>,
         initialKey: Int
     ): PagedList<V> {
+        // unblock page loading thread to allow build to succeed
+        pageLoadingThread.autoRun = true
         return PagedList.Builder(TestPositionalDataSource(data), config)
             .setInitialKey(initialKey)
             .setNotifyExecutor(mainThread)
             .setFetchExecutor(pageLoadingThread)
             .build()
+            .also {
+                pageLoadingThread.autoRun = false
+            }
     }
 
     @Test
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
index 6bb8c66..0f9b9c6 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
@@ -23,7 +23,6 @@
 import androidx.paging.ListUpdateEvent.Removed
 import androidx.paging.LoadState.Loading
 import androidx.paging.LoadState.NotLoading
-import androidx.paging.LoadType.REFRESH
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListUpdateCallback
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -133,9 +132,10 @@
         // empty previous list.
         assertEvents(
             listOf(
-                REFRESH to Loading,
-                REFRESH to NotLoading(endOfPaginationReached = false)
-            ).toCombinedLoadStatesLocal(),
+                localLoadStatesOf(),
+                localLoadStatesOf(refreshLocal = Loading),
+                localLoadStatesOf(refreshLocal = NotLoading(endOfPaginationReached = false)),
+            ),
             loadEvents
         )
         loadEvents.clear()
@@ -152,8 +152,8 @@
                 localLoadStatesOf(
                     refreshLocal = NotLoading(endOfPaginationReached = false),
                     prependLocal = NotLoading(endOfPaginationReached = true),
-                    appendLocal = NotLoading(endOfPaginationReached = true)
-                )
+                    appendLocal = NotLoading(endOfPaginationReached = true),
+                ),
             ),
             actual = loadEvents
         )
@@ -190,9 +190,10 @@
         // empty previous list.
         assertEvents(
             listOf(
-                REFRESH to Loading,
-                REFRESH to NotLoading(endOfPaginationReached = false)
-            ).toCombinedLoadStatesLocal(),
+                localLoadStatesOf(),
+                localLoadStatesOf(refreshLocal = Loading),
+                localLoadStatesOf(refreshLocal = NotLoading(endOfPaginationReached = false)),
+            ),
             loadEvents
         )
         loadEvents.clear()
@@ -209,8 +210,8 @@
                 localLoadStatesOf(
                     refreshLocal = NotLoading(endOfPaginationReached = false),
                     prependLocal = NotLoading(endOfPaginationReached = true),
-                    appendLocal = NotLoading(endOfPaginationReached = true)
-                )
+                    appendLocal = NotLoading(endOfPaginationReached = true),
+                ),
             ),
             actual = loadEvents
         )
@@ -510,48 +511,21 @@
 
         // Initial refresh
         advanceUntilIdle()
-        assertEquals(
-            CombinedLoadStates(
-                source = LoadStates(
-                    refresh = NotLoading(endOfPaginationReached = false),
-                    prepend = NotLoading(endOfPaginationReached = false),
-                    append = NotLoading(endOfPaginationReached = false)
-                )
-            ),
-            combinedLoadStates
-        )
+        assertEquals(localLoadStatesOf(), combinedLoadStates)
         assertEquals(10, itemCount)
         assertEquals(10, differ.itemCount)
 
         // Append
         differ.getItem(9)
         advanceUntilIdle()
-        assertEquals(
-            CombinedLoadStates(
-                source = LoadStates(
-                    refresh = NotLoading(endOfPaginationReached = false),
-                    prepend = NotLoading(endOfPaginationReached = false),
-                    append = NotLoading(endOfPaginationReached = false)
-                )
-            ),
-            combinedLoadStates
-        )
+        assertEquals(localLoadStatesOf(), combinedLoadStates)
         assertEquals(20, itemCount)
         assertEquals(20, differ.itemCount)
 
         // Prepend
         differ.getItem(0)
         advanceUntilIdle()
-        assertEquals(
-            CombinedLoadStates(
-                source = LoadStates(
-                    refresh = NotLoading(endOfPaginationReached = false),
-                    prepend = NotLoading(endOfPaginationReached = false),
-                    append = NotLoading(endOfPaginationReached = false)
-                )
-            ),
-            combinedLoadStates
-        )
+        assertEquals(localLoadStatesOf(), combinedLoadStates)
         assertEquals(30, itemCount)
         assertEquals(30, differ.itemCount)
 
@@ -584,48 +558,21 @@
 
             // Initial refresh
             advanceUntilIdle()
-            assertEquals(
-                CombinedLoadStates(
-                    source = LoadStates(
-                        refresh = NotLoading(endOfPaginationReached = false),
-                        prepend = NotLoading(endOfPaginationReached = false),
-                        append = NotLoading(endOfPaginationReached = false)
-                    )
-                ),
-                combinedLoadStates
-            )
+            assertEquals(localLoadStatesOf(), combinedLoadStates)
             assertEquals(10, itemCount)
             assertEquals(10, differ.itemCount)
 
             // Append
             differ.getItem(9)
             advanceUntilIdle()
-            assertEquals(
-                CombinedLoadStates(
-                    source = LoadStates(
-                        refresh = NotLoading(endOfPaginationReached = false),
-                        prepend = NotLoading(endOfPaginationReached = false),
-                        append = NotLoading(endOfPaginationReached = false)
-                    )
-                ),
-                combinedLoadStates
-            )
+            assertEquals(localLoadStatesOf(), combinedLoadStates)
             assertEquals(20, itemCount)
             assertEquals(20, differ.itemCount)
 
             // Prepend
             differ.getItem(0)
             advanceUntilIdle()
-            assertEquals(
-                CombinedLoadStates(
-                    source = LoadStates(
-                        refresh = NotLoading(endOfPaginationReached = false),
-                        prepend = NotLoading(endOfPaginationReached = false),
-                        append = NotLoading(endOfPaginationReached = false)
-                    )
-                ),
-                combinedLoadStates
-            )
+            assertEquals(localLoadStatesOf(), combinedLoadStates)
             assertEquals(30, itemCount)
             assertEquals(30, differ.itemCount)
 
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index 6d27ee0..e2055a6 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -96,8 +96,11 @@
             }
 
             private fun loadInitial(params: LoadParams<Int>): LoadResult<Int, String> {
-                @Suppress("DEPRECATION")
-                assertEquals(2, params.pageSize)
+                if (params is LoadParams.Refresh) {
+                    assertEquals(6, params.loadSize)
+                } else {
+                    assertEquals(2, params.loadSize)
+                }
 
                 throwable?.let { error ->
                     throwable = null
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
index 32bfa57..ab56fae 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
@@ -41,10 +41,12 @@
     private val fetchDispatcher: CoroutineDispatcher
 ) : LiveData<PagedList<Value>>(
     InitialPagedList(
-        pagingSourceFactory(),
-        coroutineScope,
-        config,
-        initialKey
+        pagingSource = pagingSourceFactory(),
+        coroutineScope = coroutineScope,
+        notifyDispatcher = notifyDispatcher,
+        backgroundDispatcher = fetchDispatcher,
+        config = config,
+        initialLastKey = initialKey
     )
 ) {
     private var currentData: PagedList<Value>
diff --git a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
index 0a75c08..2959747 100644
--- a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
+++ b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
@@ -302,7 +302,7 @@
             ?: dataSourceFactory?.asPagingSourceFactory(fetchDispatcher)
 
         check(pagingSourceFactory != null) {
-            "LivePagedList cannot be built without a PagingSourceFactory or DataSource.Factory"
+            "RxPagedList cannot be built without a PagingSourceFactory or DataSource.Factory"
         }
 
         return Observable
@@ -356,10 +356,12 @@
 
         init {
             currentData = InitialPagedList(
-                pagingSourceFactory(),
-                GlobalScope,
-                config,
-                initialLoadKey
+                pagingSource = pagingSourceFactory(),
+                coroutineScope = GlobalScope,
+                notifyDispatcher = notifyDispatcher,
+                backgroundDispatcher = fetchDispatcher,
+                config = config,
+                initialLastKey = initialLoadKey
             )
             currentData.setRetryCallback(refreshRetryCallback)
         }
diff --git a/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt b/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
index 4a1e45b..484e6d3 100644
--- a/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
+++ b/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
@@ -68,8 +68,11 @@
             }
 
             private fun loadInitial(params: LoadParams<Int>): LoadResult<Int, String> {
-                @Suppress("DEPRECATION")
-                assertEquals(2, params.pageSize)
+                if (params is LoadParams.Refresh) {
+                    assertEquals(6, params.loadSize)
+                } else {
+                    assertEquals(2, params.loadSize)
+                }
 
                 throwable?.let { error ->
                     throwable = null
diff --git a/paging/rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt b/paging/rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt
index 0068cfb..a6c5b4e 100644
--- a/paging/rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt
+++ b/paging/rxjava2/src/test/java/androidx/paging/RxPagingSourceTest.kt
@@ -53,14 +53,14 @@
 
     @Test
     fun basic() = runBlocking {
-        val params = PagingSource.LoadParams.Refresh(0, 2, false, 2)
+        val params = PagingSource.LoadParams.Refresh(0, 2, false)
         assertEquals(pagingSource.load(params), rxPagingSource.load(params))
     }
 
     @Test
     fun error() {
         runBlocking {
-            val params = PagingSource.LoadParams.Refresh<Int>(null, 2, false, 2)
+            val params = PagingSource.LoadParams.Refresh<Int>(null, 2, false)
             assertFailsWith<NullPointerException> { pagingSource.load(params) }
             assertFailsWith<NullPointerException> { rxPagingSource.load(params) }
         }
diff --git a/paging/rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt b/paging/rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
index 149b4bf..f7881b1 100644
--- a/paging/rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
+++ b/paging/rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
@@ -53,14 +53,14 @@
 
     @Test
     fun basic() = runBlocking {
-        val params = PagingSource.LoadParams.Refresh(0, 2, false, 2)
+        val params = PagingSource.LoadParams.Refresh(0, 2, false)
         assertEquals(pagingSource.load(params), rxPagingSource.load(params))
     }
 
     @Test
     fun error() {
         runBlocking {
-            val params = PagingSource.LoadParams.Refresh<Int>(null, 2, false, 2)
+            val params = PagingSource.LoadParams.Refresh<Int>(null, 2, false)
             assertFailsWith<NullPointerException> { pagingSource.load(params) }
             assertFailsWith<NullPointerException> { rxPagingSource.load(params) }
         }
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 03609a8..0c651335 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=6990786
+androidx.playground.snapshotBuildId=7012196
 androidx.playground.metalavaBuildId=6990868
 androidx.playground.dokkaBuildId=6915080
 androidx.studio.type=playground
diff --git a/room/compiler-processing/lint-baseline.xml b/room/compiler-processing/lint-baseline.xml
deleted file mode 100644
index 7a7fa1e..0000000
--- a/room/compiler-processing/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-alpha15" client="gradle" version="4.2.0-alpha15">
-
-    <issue
-        id="BanUncheckedReflection"
-        message="Calling Method.invoke without an SDK check"
-        errorLine1="            return enumClass.getDeclaredMethod(&quot;valueOf&quot;, String::class.java)"
-        errorLine2="                   ^">
-        <location
-            file="src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationBox.kt"
-            line="260"
-            column="20"/>
-    </issue>
-
-</issues>
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationBox.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationBox.kt
index bff4938..227d6f6 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationBox.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacAnnotationBox.kt
@@ -91,13 +91,20 @@
             }
             returnType.isArray && returnType.componentType.isAnnotation -> {
                 @Suppress("UNCHECKED_CAST")
-                ListVisitor(env, returnType.componentType as Class<out Annotation>).visit(value)
+                AnnotationListVisitor(env, returnType.componentType as Class<out Annotation>)
+                    .visit(value)
+            }
+            returnType.isArray && returnType.componentType.isEnum -> {
+                @Suppress("UNCHECKED_CAST")
+                EnumListVisitor(returnType.componentType as Class<out Enum<*>>).visit(value)
             }
             returnType.isEnum -> {
                 @Suppress("UNCHECKED_CAST")
                 value.getAsEnum(returnType as Class<out Enum<*>>)
             }
-            else -> throw UnsupportedOperationException("$returnType isn't supported")
+            else -> {
+                throw UnsupportedOperationException("$returnType isn't supported")
+            }
         }
         method.name to result
     }
@@ -230,7 +237,7 @@
 }
 
 @Suppress("DEPRECATION")
-private class ListVisitor<T : Annotation>(
+private class AnnotationListVisitor<T : Annotation>(
     private val env: JavacProcessingEnv,
     private val annotationClass: Class<T>
 ) :
@@ -245,6 +252,24 @@
 }
 
 @Suppress("DEPRECATION")
+private class EnumListVisitor<T : Enum<T>>(private val enumClass: Class<T>) :
+    SimpleAnnotationValueVisitor6<Array<T>, Void?>() {
+    override fun visitArray(
+        values: MutableList<out AnnotationValue>?,
+        void: Void?
+    ): Array<T> {
+        val result = values?.map { it.getAsEnum(enumClass) }
+        @Suppress("UNCHECKED_CAST")
+        val resultArray = java.lang.reflect.Array
+            .newInstance(enumClass, result?.size ?: 0) as Array<T>
+        result?.forEachIndexed { index, value ->
+            resultArray[index] = value
+        }
+        return resultArray
+    }
+}
+
+@Suppress("DEPRECATION")
 private class AnnotationClassVisitor<T : Annotation>(
     private val env: JavacProcessingEnv,
     private val annotationClass: Class<T>
@@ -253,7 +278,7 @@
     override fun visitAnnotation(a: AnnotationMirror?, v: Void?) = a?.box(env, annotationClass)
 }
 
-@Suppress("UNCHECKED_CAST", "DEPRECATION")
+@Suppress("UNCHECKED_CAST", "DEPRECATION", "BanUncheckedReflection")
 private fun <T : Enum<*>> AnnotationValue.getAsEnum(enumClass: Class<T>): T {
     return object : SimpleAnnotationValueVisitor6<T, Void>() {
         override fun visitEnumConstant(value: VariableElement?, p: Void?): T {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAnnotatedExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAnnotatedExt.kt
index f4a48bd..b7ec701 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAnnotatedExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAnnotatedExt.kt
@@ -18,14 +18,17 @@
 
 import com.google.devtools.ksp.symbol.KSAnnotated
 
-internal fun KSAnnotated.hasJvmStaticAnnotation() = annotations.any {
-    it.annotationType.resolve().declaration.qualifiedName?.asString() == "kotlin.jvm.JvmStatic"
+private fun KSAnnotated.hasAnnotationWithQName(qName: String) = annotations.any {
+    try {
+        it.annotationType.resolve().declaration.qualifiedName?.asString() == qName
+    } catch (illegal: IllegalStateException) {
+        // see: https://github.com/google/ksp/issues/173
+        false
+    }
 }
 
-internal fun KSAnnotated.hasJvmFieldAnnotation() = annotations.any {
-    it.annotationType.resolve().declaration.qualifiedName?.asString() == "kotlin.jvm.JvmField"
-}
+internal fun KSAnnotated.hasJvmStaticAnnotation() = hasAnnotationWithQName("kotlin.jvm.JvmStatic")
 
-internal fun KSAnnotated.hasJvmDefaultAnnotation() = annotations.any {
-    it.annotationType.resolve().declaration.qualifiedName?.asString() == "kotlin.jvm.JvmDefault"
-}
+internal fun KSAnnotated.hasJvmFieldAnnotation() = hasAnnotationWithQName("kotlin.jvm.JvmField")
+
+internal fun KSAnnotated.hasJvmDefaultAnnotation() = hasAnnotationWithQName("kotlin.jvm.JvmDefault")
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
index edfd964..b62e6ad 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
@@ -19,6 +19,7 @@
 import androidx.room.compiler.processing.XAnnotationBox
 import androidx.room.compiler.processing.XType
 import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.google.devtools.ksp.symbol.KSType
 import java.lang.reflect.Proxy
 
@@ -29,7 +30,7 @@
     private val annotation: KSAnnotation
 ) : XAnnotationBox<T> {
     override fun getAsType(methodName: String): XType? {
-        val value = getFieldValue<KSType>(methodName)
+        val value = getFieldValue(methodName, KSType::class.java)
         return value?.let {
             env.wrap(
                 ksType = it,
@@ -39,8 +40,8 @@
     }
 
     override fun getAsTypeList(methodName: String): List<XType> {
-        val values = getFieldValue<List<KSType>>(methodName) ?: return emptyList()
-        return values.map {
+        val values = getFieldValue(methodName, Array::class.java) ?: return emptyList()
+        return values.filterIsInstance<KSType>().map {
             env.wrap(
                 ksType = it,
                 allowPrimitives = true
@@ -49,7 +50,17 @@
     }
 
     override fun <R : Annotation> getAsAnnotationBox(methodName: String): XAnnotationBox<R> {
-        val value = getFieldValue<KSAnnotation>(methodName) ?: error("cannot get annotation")
+        val value = getFieldValue(methodName, KSAnnotation::class.java)
+        @Suppress("FoldInitializerAndIfToElvis")
+        if (value == null) {
+            // see https://github.com/google/ksp/issues/53
+            return KspReflectiveAnnotationBox.createFromDefaultValue(
+                env = env,
+                annotationClass = annotationClass,
+                methodName = methodName
+            )
+        }
+
         val annotationType = annotationClass.methods.first {
             it.name == methodName
         }.returnType as Class<R>
@@ -60,22 +71,37 @@
         )
     }
 
-    private inline fun <reified R> getFieldValue(methodName: String): R? {
-        val value = annotation.arguments.firstOrNull {
+    @Suppress("SyntheticAccessor")
+    private fun <R : Any> getFieldValue(
+        methodName: String,
+        returnType: Class<R>
+    ): R? {
+        val methodValue = annotation.arguments.firstOrNull {
             it.name?.asString() == methodName
-        }?.value ?: return null
-        return value as R?
+        }?.value
+        return methodValue?.readAs(returnType)
     }
 
     override fun <R : Annotation> getAsAnnotationBoxArray(
         methodName: String
     ): Array<XAnnotationBox<R>> {
-        val values = getFieldValue<ArrayList<*>>(methodName) ?: return emptyArray()
+        val values = getFieldValue(methodName, Array::class.java) ?: return emptyArray()
         val annotationType = annotationClass.methods.first {
             it.name == methodName
         }.returnType.componentType as Class<R>
+        if (values.isEmpty()) {
+            // KSP is unable to read defaults and returns empty array in that case.
+            // Subsequently, we don't know if developer set it to empty array intentionally or
+            // left it to default.
+            // we error on the side of default
+            return KspReflectiveAnnotationBox.createFromDefaultValues(
+                env = env,
+                annotationClass = annotationClass,
+                methodName = methodName
+            )
+        }
         return values.map {
-            KspAnnotationBox<R>(
+            KspAnnotationBox(
                 env = env,
                 annotationClass = annotationType,
                 annotation = it as KSAnnotation
@@ -84,24 +110,52 @@
     }
 
     private val valueProxy: T = Proxy.newProxyInstance(
-        KspAnnotationBox::class.java.classLoader,
+        annotationClass.classLoader,
         arrayOf(annotationClass)
     ) { _, method, _ ->
-        val fieldValue = getFieldValue(method.name) ?: method.defaultValue
-        // java gives arrays, kotlin gives array list (sometimes?) so fix it up
-        when {
-            fieldValue == null -> null
-            method.returnType.isArray && (fieldValue is ArrayList<*>) -> {
-                val componentType = method.returnType.componentType!!
-                val result =
-                    java.lang.reflect.Array.newInstance(componentType, fieldValue.size) as Array<*>
-                fieldValue.toArray(result)
-                result
-            }
-            else -> fieldValue
-        }
+        getFieldValue(method.name, method.returnType) ?: method.defaultValue
     } as T
 
     override val value: T
         get() = valueProxy
 }
+
+@Suppress("UNCHECKED_CAST")
+private fun <R> Any.readAs(returnType: Class<R>): R? {
+    return when {
+        returnType.isArray -> {
+            val values = when (this) {
+                is List<*> -> {
+                    // KSP might return list for arrays. convert it back.
+                    this.mapNotNull {
+                        it?.readAs(returnType.componentType)
+                    }
+                }
+                is Array<*> -> mapNotNull { it?.readAs(returnType.componentType) }
+                else -> error("unexpected type for array: $this / ${this::class.java}")
+            }
+            val resultArray = java.lang.reflect.Array.newInstance(
+                returnType.componentType,
+                values.size
+            ) as Array<Any?>
+            values.forEachIndexed { index, value ->
+                resultArray[index] = value
+            }
+            resultArray
+        }
+        returnType.isEnum -> {
+            this.readAsEnum(returnType)
+        }
+        else -> this
+    } as R?
+}
+
+private fun <R> Any.readAsEnum(enumClass: Class<R>): R? {
+    val ksType = this as? KSType ?: return null
+    val classDeclaration = ksType.declaration as? KSClassDeclaration ?: return null
+    val enumValue = classDeclaration.simpleName.asString()
+    // get the instance from the valueOf function.
+    @Suppress("UNCHECKED_CAST", "BanUncheckedReflection")
+    return enumClass.getDeclaredMethod("valueOf", String::class.java)
+        .invoke(null, enumValue) as R?
+}
\ No newline at end of file
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBox.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBox.kt
new file mode 100644
index 0000000..3bdd7ae
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBox.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.room.compiler.processing.ksp
+
+import androidx.annotation.VisibleForTesting
+import androidx.room.compiler.processing.XAnnotationBox
+import androidx.room.compiler.processing.XType
+
+/**
+ * KSP sometimes cannot read default values in annotations. This reflective implementation
+ * handles those cases.
+ * see: https://github.com/google/ksp/issues/53
+ */
+internal class KspReflectiveAnnotationBox<T : Annotation> @VisibleForTesting constructor(
+    private val env: KspProcessingEnv,
+    private val annotationClass: Class<T>,
+    private val annotation: T
+) : XAnnotationBox<T> {
+    override val value: T = annotation
+
+    override fun getAsType(methodName: String): XType? {
+        val value = getFieldValue<Class<*>>(methodName) ?: return null
+        return env.findType(value.kotlin)
+    }
+
+    override fun getAsTypeList(methodName: String): List<XType> {
+        val values = getFieldValue<Array<*>>(methodName)
+        return values?.filterIsInstance<Class<*>>()?.mapNotNull {
+            env.findType(it.kotlin)
+        } ?: emptyList()
+    }
+
+    override fun <T : Annotation> getAsAnnotationBox(methodName: String): XAnnotationBox<T> {
+        return createFromDefaultValue(
+            env = env,
+            annotationClass = annotationClass,
+            methodName = methodName
+        )
+    }
+
+    @Suppress("UNCHECKED_CAST", "BanUncheckedReflection")
+    override fun <T : Annotation> getAsAnnotationBoxArray(
+        methodName: String
+    ): Array<XAnnotationBox<T>> {
+        val method = annotationClass.methods.firstOrNull {
+            it.name == methodName
+        } ?: error("$annotationClass does not contain $methodName")
+        val values = method.invoke(annotation) as? Array<T> ?: return emptyArray()
+        return values.map {
+            KspReflectiveAnnotationBox(
+                env = env,
+                annotationClass = method.returnType.componentType as Class<T>,
+                annotation = it
+            )
+        }.toTypedArray()
+    }
+
+    @Suppress("UNCHECKED_CAST", "BanUncheckedReflection")
+    private fun <R : Any> getFieldValue(methodName: String): R? {
+        val value = annotationClass.methods.firstOrNull {
+            it.name == methodName
+        }?.invoke(annotation) ?: return null
+        return value as R?
+    }
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        fun <R : Annotation> createFromDefaultValue(
+            env: KspProcessingEnv,
+            annotationClass: Class<*>,
+            methodName: String
+        ): KspReflectiveAnnotationBox<R> {
+            val method = annotationClass.methods.firstOrNull {
+                it.name == methodName
+            } ?: error("$annotationClass does not contain $methodName")
+            val defaultValue = method.defaultValue
+                ?: error("$annotationClass.$method does not have a default value and is not set")
+            return KspReflectiveAnnotationBox(
+                env = env,
+                annotationClass = method.returnType as Class<R>,
+                annotation = defaultValue as R
+            )
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        fun <R : Annotation> createFromDefaultValues(
+            env: KspProcessingEnv,
+            annotationClass: Class<*>,
+            methodName: String
+        ): Array<XAnnotationBox<R>> {
+            val method = annotationClass.methods.firstOrNull {
+                it.name == methodName
+            } ?: error("$annotationClass does not contain $methodName")
+            check(method.returnType.isArray) {
+                "expected ${method.returnType} to be an array. $method"
+            }
+            val defaultValue = method.defaultValue
+                ?: error("$annotationClass.$method does not have a default value and is not set")
+            val values: Array<R> = defaultValue as Array<R>
+            return values.map {
+                KspReflectiveAnnotationBox(
+                    env = env,
+                    annotationClass = method.returnType.componentType as Class<R>,
+                    annotation = it
+                )
+            }.toTypedArray()
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/OverrideVarianceResolver.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/OverrideVarianceResolver.kt
index 72e1b83..d4f3e0e 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/OverrideVarianceResolver.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/OverrideVarianceResolver.kt
@@ -142,7 +142,11 @@
             // get it from overridee
             return env.resolver.getTypeArgument(
                 typeRef = myTypeRef.inheritVariance(overridee.type),
-                variance = overridee.variance
+                variance = if (overridee.variance == Variance.STAR) {
+                    Variance.COVARIANT
+                } else {
+                    overridee.variance
+                }
             )
         }
         // Now we need to guess from this type. If the type is final, it does not inherit unless
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
index 0d5a275..e8a80d4 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
@@ -17,14 +17,14 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.room.compiler.processing.XExecutableElement
+import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
 import com.google.devtools.ksp.KspExperimental
-import com.google.devtools.ksp.closestClassDeclaration
-import com.google.devtools.ksp.getAllSuperTypes
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.symbol.KSDeclaration
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
 import com.google.devtools.ksp.symbol.KSPropertyAccessor
+import com.google.devtools.ksp.symbol.KSPropertyDeclaration
 import com.google.devtools.ksp.symbol.Origin
 
 internal fun Resolver.findClass(qName: String) = getClassDeclarationByName(
@@ -46,8 +46,8 @@
 }
 
 internal fun Resolver.overrides(
-    overriderElement: XExecutableElement,
-    overrideeElement: XExecutableElement
+    overriderElement: XMethodElement,
+    overrideeElement: XMethodElement
 ): Boolean {
     // in addition to functions declared in kotlin, we also synthesize getter/setter functions for
     // properties which means we cannot simply send the declaration to KSP for override check
@@ -59,18 +59,49 @@
     if (overriderElement.parameters.size != overrideeElement.parameters.size) {
         return false
     }
-    val ksOverrider = overriderElement.getDeclarationForOverride()
-    val ksOverridee = overrideeElement.getDeclarationForOverride()
-    if (!overrides(ksOverrider, ksOverridee)) {
+    // do a quick check on name before doing the more expensive operations
+    if (overriderElement.name != overrideeElement.name) {
         return false
     }
-    // TODO Workaround for https://github.com/google/ksp/issues/123
-    //  remove once that bug is fixed
-    val subClass = ksOverrider.closestClassDeclaration() ?: return false
-    val superClass = ksOverridee.closestClassDeclaration() ?: return false
-    return subClass.getAllSuperTypes().any {
-        it.declaration.closestClassDeclaration() == superClass
+    val ksOverrider = overriderElement.getDeclarationForOverride()
+    val ksOverridee = overrideeElement.getDeclarationForOverride()
+    if (overrides(ksOverrider, ksOverridee)) {
+        return true
     }
+    // workaround for: https://github.com/google/ksp/issues/175
+    if (ksOverrider is KSFunctionDeclaration && ksOverridee is KSFunctionDeclaration) {
+        return ksOverrider.overrides(ksOverridee)
+    }
+    if (ksOverrider is KSPropertyDeclaration && ksOverridee is KSPropertyDeclaration) {
+        return ksOverrider.overrides(ksOverridee)
+    }
+    return false
+}
+
+private fun KSFunctionDeclaration.overrides(other: KSFunctionDeclaration): Boolean {
+    val overridee = try {
+        findOverridee()
+    } catch (ignored: ClassCastException) {
+        // workaround for https://github.com/google/ksp/issues/164
+        null
+    }
+    if (overridee == other) {
+        return true
+    }
+    return overridee?.overrides(other) ?: false
+}
+
+private fun KSPropertyDeclaration.overrides(other: KSPropertyDeclaration): Boolean {
+    val overridee = try {
+        findOverridee()
+    } catch (ex: NoSuchElementException) {
+        // workaround for https://github.com/google/ksp/issues/174
+        null
+    }
+    if (overridee == other) {
+        return true
+    }
+    return overridee?.overrides(other) ?: false
 }
 
 @OptIn(KspExperimental::class)
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index 89d72ba..49ad742 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -16,8 +16,11 @@
 
 package androidx.room.compiler.processing
 
-import androidx.room.compiler.processing.javac.JavacProcessingEnv
+import androidx.room.compiler.processing.javac.JavacMethodElement
+import androidx.room.compiler.processing.javac.JavacTypeElement
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.javaTypeUtils
 import androidx.room.compiler.processing.util.runKaptTest
 import androidx.room.compiler.processing.util.runProcessorTestIncludingKsp
 import com.google.auto.common.MoreTypes
@@ -27,7 +30,6 @@
 import javax.lang.model.element.ExecutableElement
 import javax.lang.model.element.Modifier
 import javax.lang.model.type.DeclaredType
-import javax.lang.model.util.ElementFilter
 import javax.lang.model.util.Types
 
 class MethodSpecHelperTest {
@@ -180,7 +182,7 @@
     }
 
     @Test
-    fun inheritedVariance() {
+    fun inheritedVariance_openType() {
         val source = Source.kotlin(
             "Foo.kt",
             """
@@ -189,7 +191,7 @@
                 fun receiveList(argsInParent : List<T>):Unit
                 suspend fun suspendReturnList(arg1:Int, arg2:String):List<T>
             }
-            data class Book(val id:Int)
+            open class Book(val id:Int)
             interface Baz : MyInterface<Book> {
                 fun myList(args: List<Book>):Unit
                 override fun receiveList(argsInParent : List<Book>):Unit
@@ -215,7 +217,7 @@
             }
             """.trimIndent()
         )
-        overridesCheck(source)
+        overridesCheck(source, ignoreInheritedMethods = true)
     }
 
     @Test
@@ -242,6 +244,26 @@
     }
 
     @Test
+    fun inheritedVariance_multiLevel() {
+        val source = Source.kotlin(
+            "Foo.kt",
+            """
+            package foo.bar;
+            interface GrandParent<T> {
+                fun receiveList(list : List<T>): Unit
+                suspend fun suspendReceiveList(list : List<T>): Unit
+                suspend fun suspendReturnList(): List<T>
+            }
+            interface Parent: GrandParent<Number> {
+            }
+            interface Baz : Parent {
+            }
+            """.trimIndent()
+        )
+        overridesCheck(source)
+    }
+
+    @Test
     fun primitiveOverrides() {
         val source = Source.kotlin(
             "Foo.kt",
@@ -266,46 +288,82 @@
         overridesCheck(source)
     }
 
-    private fun overridesCheck(source: Source) {
+    private fun overridesCheck(source: Source, ignoreInheritedMethods: Boolean = false) {
         // first build golden image with Java processor so we can use JavaPoet's API
-        val golden = buildMethodsViaJavaPoet(source)
+        val golden = buildMethodsViaJavaPoet(source, ignoreInheritedMethods)
         runProcessorTestIncludingKsp(
             sources = listOf(source)
         ) { invocation ->
-            val element = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
-            element.getDeclaredMethods().filter {
-                // TODO b/171572318
-                !invocation.isKsp || it.name != "throwsException"
-            }.forEachIndexed { index, method ->
-                val subject = MethodSpecHelper.overridingWithFinalParams(
-                    method,
-                    element.type
-                ).build().toString()
-                assertThat(subject).isEqualTo(golden[index])
+            val (target, methods) = invocation.getOverrideTestTargets(ignoreInheritedMethods)
+            methods.forEachIndexed { index, method ->
+
+                if (invocation.isKsp && method.name == "throwsException") {
+                    // TODO b/171572318
+                } else {
+                    val subject = MethodSpecHelper.overridingWithFinalParams(
+                        method,
+                        target.type
+                    ).build().toString()
+                    assertThat(subject).isEqualTo(golden[index])
+                }
             }
         }
     }
 
-    private fun buildMethodsViaJavaPoet(source: Source): List<String> {
+    private fun buildMethodsViaJavaPoet(
+        source: Source,
+        ignoreInheritedMethods: Boolean
+    ): List<String> {
         lateinit var result: List<String>
         runKaptTest(
             sources = listOf(source),
             succeed = true
-        ) {
-            val processingEnv = (it.processingEnv as JavacProcessingEnv)
-            val element = processingEnv.elementUtils.getTypeElement("foo.bar.Baz")
-            result = ElementFilter.methodsIn(element.enclosedElements)
+        ) { invocation ->
+            val (target, methods) = invocation.getOverrideTestTargets(
+                ignoreInheritedMethods
+            )
+            val element = (target as JavacTypeElement).element
+            result = methods
                 .map {
+                    (it as JavacMethodElement).element
+                }.map {
                     generateFromJavapoet(
                         it,
                         MoreTypes.asDeclared(element.asType()),
-                        processingEnv.typeUtils
+                        invocation.javaTypeUtils
                     ).build().toString()
                 }
         }
         return result
     }
 
+    /**
+     * Get test targets. There is an edge case where it is not possible to implement an interface
+     * in java, b/174313780. [ignoreInheritedMethods] helps avoid that case.
+     */
+    private fun XTestInvocation.getOverrideTestTargets(
+        ignoreInheritedMethods: Boolean
+    ): Pair<XTypeElement, List<XMethodElement>> {
+        val objectMethodNames = processingEnv
+            .requireTypeElement("java.lang.Object")
+            .getAllNonPrivateInstanceMethods()
+            .map {
+                it.name
+            }
+        val target = processingEnv.requireTypeElement("foo.bar.Baz")
+        val methods = if (ignoreInheritedMethods) {
+            target.getDeclaredMethods().filter { !it.isStatic() }
+        } else {
+            target.getAllNonPrivateInstanceMethods()
+        }
+        val selectedMethods = methods.filter {
+            it.isOverrideableIgnoringContainer()
+        }.filterNot {
+            it.name in objectMethodNames
+        }
+        return target to selectedMethods
+    }
+
     private fun generateFromJavapoet(
         method: ExecutableElement,
         owner: DeclaredType,
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
index 7a9a0ac..45f8aad 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
@@ -16,6 +16,8 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.processing.testcode.JavaAnnotationWithDefaults
+import androidx.room.compiler.processing.testcode.JavaEnum
 import androidx.room.compiler.processing.testcode.MainAnnotation
 import androidx.room.compiler.processing.testcode.OtherAnnotation
 import androidx.room.compiler.processing.util.Source
@@ -27,9 +29,11 @@
 import androidx.room.compiler.processing.util.typeName
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import com.squareup.javapoet.ClassName
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import java.util.LinkedHashMap
 
 @RunWith(JUnit4::class)
 class XAnnotationBoxTest {
@@ -316,6 +320,76 @@
         }
     }
 
+    @Test
+    fun defaultValues() {
+        val kotlinSrc = Source.kotlin(
+            "KotlinClass.kt",
+            """
+            import androidx.room.compiler.processing.testcode.JavaAnnotationWithDefaults
+            @JavaAnnotationWithDefaults
+            class KotlinClass
+            """.trimIndent()
+        )
+        val javaSrc = Source.java(
+            "JavaClass.java",
+            """
+            import androidx.room.compiler.processing.testcode.JavaAnnotationWithDefaults;
+            @JavaAnnotationWithDefaults
+            class JavaClass {}
+            """.trimIndent()
+        )
+        runProcessorTestIncludingKsp(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
+            listOf("KotlinClass", "JavaClass")
+                .map {
+                    invocation.processingEnv.requireTypeElement(it)
+                }.forEach { typeElement ->
+                    val annotation =
+                        typeElement.toAnnotationBox(JavaAnnotationWithDefaults::class)
+                    checkNotNull(annotation)
+                    assertThat(annotation.value.intVal).isEqualTo(3)
+                    assertThat(annotation.value.stringArrayVal).isEqualTo(arrayOf("x", "y"))
+                    assertThat(annotation.value.stringVal).isEqualTo("foo")
+                    assertThat(
+                        annotation.getAsType("typeVal")?.rawType?.typeName
+                    ).isEqualTo(
+                        ClassName.get(HashMap::class.java)
+                    )
+                    assertThat(
+                        annotation.getAsTypeList("typeArrayVal").map {
+                            it.rawType.typeName
+                        }
+                    ).isEqualTo(
+                        listOf(ClassName.get(LinkedHashMap::class.java))
+                    )
+
+                    assertThat(
+                        annotation.value.enumVal
+                    ).isEqualTo(
+                        JavaEnum.DEFAULT
+                    )
+
+                    assertThat(
+                        annotation.value.enumArrayVal
+                    ).isEqualTo(
+                        arrayOf(JavaEnum.VAL1, JavaEnum.VAL2)
+                    )
+
+                    assertThat(
+                        annotation.getAsAnnotationBox<OtherAnnotation>("otherAnnotationVal")
+                            .value.value
+                    ).isEqualTo("def")
+
+                    assertThat(
+                        annotation
+                            .getAsAnnotationBoxArray<OtherAnnotation>("otherAnnotationArrayVal")
+                            .map {
+                                it.value.value
+                            }
+                    ).containsExactly("v1")
+                }
+        }
+    }
+
     // helper function to read what we need
     private fun XAnnotated.getSuppressValues(): Array<String>? {
         return this.toAnnotationBox(SuppressWarnings::class)?.value?.value
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBoxTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBoxTest.kt
new file mode 100644
index 0000000..8c694265
--- /dev/null
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBoxTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.room.compiler.processing.ksp
+
+import androidx.room.compiler.processing.util.runKspTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import org.junit.Test
+import kotlin.reflect.KClass
+
+class KspReflectiveAnnotationBoxTest {
+    enum class TestEnum {
+        VAL1,
+        VAL2
+    }
+
+    annotation class TestAnnotation(
+        val strProp: String = "abc",
+        val intProp: Int = 3,
+        val enumProp: TestEnum = TestEnum.VAL2,
+        val enumArrayProp: Array<TestEnum> = [TestEnum.VAL1, TestEnum.VAL2, TestEnum.VAL1],
+        val annProp: TestAnnotation2 = TestAnnotation2(3),
+        val annArrayProp: Array<TestAnnotation2> = [TestAnnotation2(1), TestAnnotation2(5)],
+        val typeProp: KClass<*> = Int::class,
+        val typeArrayProp: Array<KClass<*>> = [Int::class, String::class]
+    )
+
+    annotation class TestAnnotation2(
+        val intProp: Int = 0
+    )
+
+    @Test
+    @TestAnnotation // putting annotation here to read it back easily :)
+    fun simple() {
+        runKspTest(sources = emptyList()) { invocation ->
+            val box = KspReflectiveAnnotationBox(
+                env = invocation.processingEnv as KspProcessingEnv,
+                annotationClass = TestAnnotation::class.java,
+                annotation = getAnnotationOnMethod("simple")
+            )
+            assertThat(box.value.strProp).isEqualTo("abc")
+            assertThat(box.value.intProp).isEqualTo(3)
+            assertThat(box.value.enumProp).isEqualTo(TestEnum.VAL2)
+            assertThat(box.value.enumArrayProp).isEqualTo(
+                arrayOf(TestEnum.VAL1, TestEnum.VAL2, TestEnum.VAL1)
+            )
+            box.getAsAnnotationBox<TestAnnotation2>("annProp").let {
+                assertThat(it.value.intProp).isEqualTo(3)
+            }
+            box.getAsAnnotationBoxArray<TestAnnotation2>("annArrayProp").let {
+                assertThat(
+                    it.map { it.value.intProp }
+                ).containsExactly(1, 5)
+            }
+            box.getAsType("typeProp")?.let {
+                assertThat(it is KspType).isTrue()
+                assertThat(it.typeName).isEqualTo(TypeName.INT)
+            }
+            box.getAsTypeList("typeArrayProp").let {
+                assertThat(it.all { it is KspType }).isTrue()
+                assertThat(it.map { it.typeName }).containsExactly(
+                    TypeName.INT, ClassName.get(String::class.java)
+                )
+            }
+        }
+    }
+
+    private inline fun <reified T : Annotation> getAnnotationOnMethod(methodName: String): T {
+        return KspReflectiveAnnotationBoxTest::class.java.getMethod(methodName).annotations
+            .first {
+                it is TestAnnotation
+            } as T
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithDefaults.java b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithDefaults.java
new file mode 100644
index 0000000..26361dc
--- /dev/null
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaAnnotationWithDefaults.java
@@ -0,0 +1,32 @@
+/*
+ * 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.room.compiler.processing.testcode;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+
+public @interface JavaAnnotationWithDefaults {
+    String stringVal() default "foo";
+    String[] stringArrayVal() default {"x", "y"};
+    Class<?> typeVal() default HashMap.class;
+    Class[] typeArrayVal() default {LinkedHashMap.class};
+    int intVal() default 3;
+    JavaEnum enumVal() default JavaEnum.DEFAULT;
+    JavaEnum[] enumArrayVal() default {JavaEnum.VAL1, JavaEnum.VAL2};
+    OtherAnnotation otherAnnotationVal() default @OtherAnnotation("def");
+    OtherAnnotation[] otherAnnotationArrayVal() default {@OtherAnnotation("v1")};
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ExperimentalPointerInput.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaEnum.java
similarity index 74%
rename from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ExperimentalPointerInput.kt
rename to room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaEnum.java
index 0bfd44c..c0a037b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ExperimentalPointerInput.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/JavaEnum.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.gesture
+package androidx.room.compiler.processing.testcode;
 
-@RequiresOptIn(
-    "This pointer input API is experimental and is likely to change before becoming " +
-        "stable."
-)
-annotation class ExperimentalPointerInput
\ No newline at end of file
+public enum JavaEnum {
+    VAL1,
+    VAL2,
+    DEFAULT
+}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/XTestInvocationExt.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/XTestInvocationExt.kt
index 0bc2a7d..1e24f10 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/XTestInvocationExt.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/XTestInvocationExt.kt
@@ -20,9 +20,13 @@
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import com.google.devtools.ksp.processing.Resolver
 import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
 
 val XTestInvocation.kspResolver: Resolver
     get() = (processingEnv as KspProcessingEnv).resolver
 
 val XTestInvocation.javaElementUtils: Elements
-    get() = (processingEnv as JavacProcessingEnv).elementUtils
\ No newline at end of file
+    get() = (processingEnv as JavacProcessingEnv).elementUtils
+
+val XTestInvocation.javaTypeUtils: Types
+    get() = (processingEnv as JavacProcessingEnv).typeUtils
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
index 3babfd8..ba1a146 100644
--- a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
@@ -131,6 +131,9 @@
     val STRING = ClassName.get("java.lang", "String")
     val INTEGER = ClassName.get("java.lang", "Integer")
     val OPTIONAL = ClassName.get("java.util", "Optional")
+    val ILLEGAL_ARG_EXCEPTION = ClassName.get(
+        "java.lang", "IllegalArgumentException"
+    )
 }
 
 object GuavaBaseTypeNames {
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
index 1194b1d..090dfc8 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,29 +16,38 @@
 
 package androidx.room.solver.types
 
+import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.CommonTypeNames.ILLEGAL_ARG_EXCEPTION
 import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.S
 import androidx.room.ext.T
 import androidx.room.parser.SQLTypeAffinity.TEXT
 import androidx.room.solver.CodeGenScope
+import androidx.room.writer.ClassWriter
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import java.util.Locale
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.Modifier
 
 /**
  * Uses enum string representation.
  */
 class EnumColumnTypeAdapter(out: XType) :
     ColumnTypeAdapter(out, TEXT) {
-    private val enumTypeName = out.typeName
     override fun readFromCursor(
         outVarName: String,
         cursorVarName: String,
         indexVarName: String,
         scope: CodeGenScope
     ) {
+        val stringToEnumMethod = stringToEnumMethod(scope)
         scope.builder()
             .addStatement(
-                "$L = $T.valueOf($L.getString($L))", outVarName, enumTypeName,
-                cursorVarName,
-                indexVarName
+                "$L = $N($L.getString($L))",
+                outVarName, stringToEnumMethod, cursorVarName, indexVarName
             )
     }
 
@@ -48,12 +57,102 @@
         valueVarName: String,
         scope: CodeGenScope
     ) {
+        val enumToStringMethod = enumToStringMethod(scope)
         scope.builder().apply {
             beginControlFlow("if ($L == null)", valueVarName)
                 .addStatement("$L.bindNull($L)", stmtName, indexVarName)
             nextControlFlow("else")
-                .addStatement("$L.bindString($L, $L.name())", stmtName, indexVarName, valueVarName)
+                .addStatement(
+                    "$L.bindString($L, $N($L))",
+                    stmtName, indexVarName, enumToStringMethod, valueVarName
+                )
             endControlFlow()
         }
     }
-}
\ No newline at end of file
+
+    private fun enumToStringMethod(scope: CodeGenScope): MethodSpec {
+        return scope.writer.getOrCreateMethod(object :
+                ClassWriter.SharedMethodSpec(out.asTypeElement().name + "_enumToString") {
+                override fun getUniqueKey(): String {
+                    return "enumToString_" + out.typeName.toString()
+                }
+
+                override fun prepare(
+                    methodName: String,
+                    writer: ClassWriter,
+                    builder: MethodSpec.Builder
+                ) {
+                    builder.apply {
+                        addModifiers(Modifier.PRIVATE)
+                        returns(String::class.java)
+                        val param = ParameterSpec.builder(
+                            out.typeName, "_value", Modifier.FINAL
+                        ).build()
+                        addParameter(param)
+                        beginControlFlow("if ($N == null)", param)
+                        addStatement("return null")
+                        nextControlFlow("switch ($N)", param)
+                        getEnumConstantElements().forEach { enumConstant ->
+                            addStatement("case $L: return $S", enumConstant.name, enumConstant.name)
+                        }
+                        addStatement(
+                            "default: throw new $T($S)",
+                            ILLEGAL_ARG_EXCEPTION,
+                            "Can't convert ${param.name} to string, unknown enum value."
+                        )
+                        endControlFlow()
+                    }
+                }
+            })
+    }
+
+    private fun stringToEnumMethod(scope: CodeGenScope): MethodSpec {
+        return scope.writer.getOrCreateMethod(object :
+                ClassWriter.SharedMethodSpec(out.asTypeElement().name + "_stringToEnum") {
+                override fun getUniqueKey(): String {
+                    return out.typeName.toString()
+                }
+
+                override fun prepare(
+                    methodName: String,
+                    writer: ClassWriter,
+                    builder: MethodSpec.Builder
+                ) {
+                    builder.apply {
+                        addModifiers(Modifier.PRIVATE)
+                        returns(out.typeName)
+                        val param = ParameterSpec.builder(
+                            String::class.java, "_value", Modifier.FINAL
+                        ).build()
+                        addParameter(param)
+                        beginControlFlow("if ($N == null)", param)
+                        addStatement("return null")
+                        nextControlFlow("switch ($N)", param)
+                        getEnumConstantElements().forEach {
+                            enumConstant ->
+                            addStatement(
+                                "case $S: return $T.$L",
+                                enumConstant.name, out.typeName, enumConstant.name
+                            )
+                        }
+                        addStatement(
+                            "default: throw new $T($S)",
+                            ILLEGAL_ARG_EXCEPTION,
+                            "Can't convert ${param.name} to enum, unknown value."
+                        )
+                        endControlFlow()
+                    }
+                }
+            })
+    }
+
+    private fun getEnumConstantElements(): List<XFieldElement> {
+        // TODO: Switch below logic to use`getDeclaredFields` when the
+        //  functionality is available in the XTypeElement API
+        val typeElementFields = out.asTypeElement().getAllFieldsIncludingPrivateSupers()
+        return typeElementFields.filter {
+            // TODO: (b/173236324) Add kind to the X abstraction API to avoid using kindName()
+            ElementKind.ENUM_CONSTANT.toString().toLowerCase(Locale.US) == it.kindName()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/util/SimpleJavaVersion.kt b/room/compiler/src/main/kotlin/androidx/room/util/SimpleJavaVersion.kt
index 8041f42..5655d9b 100644
--- a/room/compiler/src/main/kotlin/androidx/room/util/SimpleJavaVersion.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/util/SimpleJavaVersion.kt
@@ -23,8 +23,11 @@
  * [androidx.room.RoomProcessor.methodParametersVisibleInClassFiles] check only. If you want to use
  * this class, consider expanding the implementation or use a different library.
  */
-data class SimpleJavaVersion(val major: Int, val minor: Int, val update: Int? = null) :
-    Comparable<SimpleJavaVersion> {
+data class SimpleJavaVersion(
+    val major: Int,
+    val minor: Int,
+    val update: Int? = null
+) : Comparable<SimpleJavaVersion> {
 
     override fun compareTo(other: SimpleJavaVersion): Int {
         return compareValuesBy(
@@ -59,13 +62,23 @@
 
             val parts = version.split('.')
 
-            // There are valid JDK version strings with more than 3 parts when split by dots.
-            // For example: "11.0.6+10-post-Ubuntu-1ubuntu118.04.1".
-            if (parts.size < 3) {
-                return null
+            // There are valid JDK version strings with no parts split by dots.
+            // For example: "15+36".
+            if (parts.size == 1) {
+                return try {
+                    val major = parts[0].substringBeforeNonDigitChar()
+                    SimpleJavaVersion(major.toInt(), 0)
+                } catch (e: NumberFormatException) {
+                    null
+                }
             }
 
             if (parts[0] == "1") {
+                // All 3 parts are needed when JDK versions strings where major version is 1.
+                // For example: "1.8.0_202-release-1483-b39-5396753"
+                if (parts.size < 3) {
+                    return null
+                }
                 val major = parts[1]
                 val minorAndUpdate = parts[2].substringBefore('-').split('_')
                 if (minorAndUpdate.size != 2) {
@@ -82,13 +95,23 @@
                 }
             } else {
                 return try {
-                    SimpleJavaVersion(parts[0].toInt(), parts[1].toInt())
+                    val minor = parts[1].substringBeforeNonDigitChar()
+                    SimpleJavaVersion(parts[0].toInt(), minor.toInt())
                 } catch (e: NumberFormatException) {
                     null
                 }
             }
         }
 
+        private fun String.substringBeforeNonDigitChar(): String {
+            val nonDigitIndex = indexOfFirst { !it.isDigit() }
+            return if (nonDigitIndex == -1) {
+                this
+            } else {
+                substring(0, nonDigitIndex)
+            }
+        }
+
         /**
          * Parses the Java version from the given string (e.g.,
          * "1.8.0_202-release-1483-b39-5396753"), throwing [IllegalArgumentException] if it
diff --git a/room/compiler/src/test/kotlin/androidx/room/util/SimpleJavaVersionTest.kt b/room/compiler/src/test/kotlin/androidx/room/util/SimpleJavaVersionTest.kt
index 830aeba..e930fc9 100644
--- a/room/compiler/src/test/kotlin/androidx/room/util/SimpleJavaVersionTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/util/SimpleJavaVersionTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.util
 
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.fail
 import org.junit.Test
 
@@ -23,37 +24,39 @@
 
     @Test
     fun testTryParse() {
-        assert(SimpleJavaVersion.tryParse("11.0.1+13-LTS") == SimpleJavaVersion(11, 0, null))
-        assert(
-            SimpleJavaVersion.tryParse("11.0.6+10-post-Ubuntu-1ubuntu118.04.1")
-                == SimpleJavaVersion(11, 0, null)
-        )
-        assert(
-            SimpleJavaVersion.tryParse("1.8.0_202-release-1483-b39-5396753")
-                == SimpleJavaVersion(8, 0, 202)
-        )
-        assert(
-            SimpleJavaVersion.tryParse("1.8.0_181-google-v7-238857965-238857965")
-                == SimpleJavaVersion(8, 0, 181)
-        )
-        assert(SimpleJavaVersion.tryParse("a.b.c") == null)
+        assertThat(SimpleJavaVersion.tryParse("1.8.0_181-google-v7-238857965-238857965"))
+            .isEqualTo(SimpleJavaVersion(8, 0, 181))
+        assertThat(SimpleJavaVersion.tryParse("1.8.0_202-release-1483-b39-5396753"))
+            .isEqualTo(SimpleJavaVersion(8, 0, 202))
+        assertThat(SimpleJavaVersion.tryParse("11.0.1+13-LTS"))
+            .isEqualTo(SimpleJavaVersion(11, 0, null))
+        assertThat(SimpleJavaVersion.tryParse("11.0.6+10-post-Ubuntu-1ubuntu118.04.1"))
+            .isEqualTo(SimpleJavaVersion(11, 0, null))
+        assertThat(SimpleJavaVersion.tryParse("11.0.8+10-b944.6842174"))
+            .isEqualTo(SimpleJavaVersion(11, 0, null))
+        assertThat(SimpleJavaVersion.tryParse("14.1-ea"))
+            .isEqualTo(SimpleJavaVersion(14, 1, null))
+        assertThat(SimpleJavaVersion.tryParse("15+13"))
+            .isEqualTo(SimpleJavaVersion(15, 0, null))
+        assertThat(SimpleJavaVersion.tryParse("a.b.c")).isNull()
     }
 
     @Test
     fun testParse() {
-        assert(SimpleJavaVersion.parse("11.0.1+13-LTS") == SimpleJavaVersion(11, 0, null))
-        assert(
-            SimpleJavaVersion.parse("11.0.6+10-post-Ubuntu-1ubuntu118.04.1")
-                == SimpleJavaVersion(11, 0, null)
-        )
-        assert(
-            SimpleJavaVersion.parse("1.8.0_202-release-1483-b39-5396753")
-                == SimpleJavaVersion(8, 0, 202)
-        )
-        assert(
-            SimpleJavaVersion.parse("1.8.0_181-google-v7-238857965-238857965")
-                == SimpleJavaVersion(8, 0, 181)
-        )
+        assertThat(SimpleJavaVersion.parse("1.8.0_181-google-v7-238857965-238857965"))
+            .isEqualTo(SimpleJavaVersion(8, 0, 181))
+        assertThat(SimpleJavaVersion.parse("1.8.0_202-release-1483-b39-5396753"))
+            .isEqualTo(SimpleJavaVersion(8, 0, 202))
+        assertThat(SimpleJavaVersion.parse("11.0.1+13-LTS"))
+            .isEqualTo(SimpleJavaVersion(11, 0, null))
+        assertThat(SimpleJavaVersion.parse("11.0.6+10-post-Ubuntu-1ubuntu118.04.1"))
+            .isEqualTo(SimpleJavaVersion(11, 0, null))
+        assertThat(SimpleJavaVersion.parse("11.0.8+10-b944.6842174"))
+            .isEqualTo(SimpleJavaVersion(11, 0, null))
+        assertThat(SimpleJavaVersion.parse("14.1-ea"))
+            .isEqualTo(SimpleJavaVersion(14, 1, null))
+        assertThat(SimpleJavaVersion.parse("15+13"))
+            .isEqualTo(SimpleJavaVersion(15, 0, null))
         try {
             SimpleJavaVersion.parse("a.b.c")
             fail("Expected IllegalArgumentException")
@@ -64,19 +67,19 @@
 
     @Test
     fun testComparison() {
-        assert(SimpleJavaVersion(11, 1) > SimpleJavaVersion(8, 2))
-        assert(SimpleJavaVersion(8, 2) < SimpleJavaVersion(11, 1))
-        assert(SimpleJavaVersion(8, 1) == SimpleJavaVersion(8, 1))
+        assertThat(SimpleJavaVersion(11, 1)).isGreaterThan(SimpleJavaVersion(8, 2))
+        assertThat(SimpleJavaVersion(8, 2)).isLessThan(SimpleJavaVersion(11, 1))
+        assertThat(SimpleJavaVersion(8, 1)).isEqualTo(SimpleJavaVersion(8, 1))
 
-        assert(SimpleJavaVersion(8, 2, 1) > SimpleJavaVersion(8, 1, 2))
-        assert(SimpleJavaVersion(8, 1, 2) < SimpleJavaVersion(8, 2, 1))
-        assert(SimpleJavaVersion(8, 1, null) == SimpleJavaVersion(8, 1, null))
+        assertThat(SimpleJavaVersion(8, 2, 1)).isGreaterThan(SimpleJavaVersion(8, 1, 2))
+        assertThat(SimpleJavaVersion(8, 1, 2)).isLessThan(SimpleJavaVersion(8, 2, 1))
+        assertThat(SimpleJavaVersion(8, 1, null)).isEqualTo(SimpleJavaVersion(8, 1, null))
 
-        assert(SimpleJavaVersion(8, 1, 2) > SimpleJavaVersion(8, 1, 1))
-        assert(SimpleJavaVersion(8, 1, 1) < SimpleJavaVersion(8, 1, 2))
-        assert(SimpleJavaVersion(8, 1, 1) == SimpleJavaVersion(8, 1, 1))
+        assertThat(SimpleJavaVersion(8, 1, 2)).isGreaterThan(SimpleJavaVersion(8, 1, 1))
+        assertThat(SimpleJavaVersion(8, 1, 1)).isLessThan(SimpleJavaVersion(8, 1, 2))
+        assertThat(SimpleJavaVersion(8, 1, 1)).isEqualTo(SimpleJavaVersion(8, 1, 1))
 
-        assert(SimpleJavaVersion(8, 1, 0) > SimpleJavaVersion(8, 1, null))
-        assert(SimpleJavaVersion(8, 1, null) < SimpleJavaVersion(8, 1, 0))
+        assertThat(SimpleJavaVersion(8, 1, 0)).isGreaterThan(SimpleJavaVersion(8, 1, null))
+        assertThat(SimpleJavaVersion(8, 1, null)).isLessThan(SimpleJavaVersion(8, 1, 0))
     }
 }
\ No newline at end of file
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java
index 4c0df94..94cccaf 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java
@@ -42,6 +42,7 @@
 public class EnumColumnTypeAdapterTest {
 
     private EnumColumnTypeAdapterDatabase mDb;
+    private EnumColumnTypeAdapterDatabase mDbComplex;
 
     @Entity
     public static class EntityWithEnum {
@@ -49,6 +50,12 @@
         public Long id;
         public Fruit fruit;
     }
+    @Entity
+    public static class ComplexEntityWithEnum {
+        @PrimaryKey
+        public Long id;
+        public Season mSeason;
+    }
 
     public enum Fruit {
         BANANA,
@@ -56,6 +63,19 @@
         WILDBERRY
     }
 
+    public enum Season {
+        SUMMER("Sunny"),
+        SPRING("Warm"),
+        WINTER("Cold"),
+        AUTUMN("Rainy");
+
+        private final String mSeason;
+
+        Season(String mSeason) {
+            this.mSeason = mSeason;
+        }
+    }
+
     @Dao
     public interface SampleDao {
         @Query("INSERT INTO EntityWithEnum (id, fruit) VALUES (:id, :fruit)")
@@ -65,9 +85,20 @@
         EntityWithEnum getValueWithId(long id);
     }
 
-    @Database(entities = {EntityWithEnum.class}, version = 1, exportSchema = false)
+    @Dao
+    public interface SampleDaoWithComplexEnum {
+        @Query("INSERT INTO ComplexEntityWithEnum (id, mSeason) VALUES (:id, :season)")
+        long insertComplex(long id, Season season);
+
+        @Query("SELECT * FROM ComplexEntityWithEnum WHERE id = :id")
+        ComplexEntityWithEnum getComplexValueWithId(long id);
+    }
+
+    @Database(entities = {EntityWithEnum.class, ComplexEntityWithEnum.class}, version = 1,
+            exportSchema = false)
     public abstract static class EnumColumnTypeAdapterDatabase extends RoomDatabase {
         public abstract EnumColumnTypeAdapterTest.SampleDao dao();
+        public abstract EnumColumnTypeAdapterTest.SampleDaoWithComplexEnum complexDao();
     }
 
     @Before
@@ -77,6 +108,10 @@
                 context,
                 EnumColumnTypeAdapterDatabase.class)
                 .build();
+        mDbComplex = Room.inMemoryDatabaseBuilder(
+                context,
+                EnumColumnTypeAdapterDatabase.class)
+                .build();
     }
 
     @Test
@@ -87,4 +122,11 @@
         assertThat(mDb.dao().getValueWithId(1).fruit, is(equalTo(Fruit.BANANA)));
         assertThat(mDb.dao().getValueWithId(2).fruit, is(equalTo(Fruit.STRAWBERRY)));
     }
+
+    @Test
+    public void filterOutComplexEnumTest() {
+        final long id1 = mDbComplex.complexDao().insertComplex(1, Season.AUTUMN);
+        assertThat(mDbComplex.complexDao().getComplexValueWithId(1).mSeason,
+                is(equalTo(Season.AUTUMN)));
+    }
 }
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
index d16de8c..0a76af6 100644
--- a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
@@ -406,4 +406,32 @@
                 testValue);
     }
 
+    @Test
+    public void testReentrantCallbackCalls() throws Exception {
+        SharedPreferences encryptedSharedPreferences = EncryptedSharedPreferences
+                .create(mContext,
+                        PREFS_FILE,
+                        mMasterKey,
+                        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+                        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
+
+        encryptedSharedPreferences.registerOnSharedPreferenceChangeListener(
+                new SharedPreferences.OnSharedPreferenceChangeListener() {
+                    @Override
+                    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+                            String key) {
+                        sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
+                    }
+                });
+
+        encryptedSharedPreferences.registerOnSharedPreferenceChangeListener(
+                (sharedPreferences, key) -> {
+                    // No-op
+                });
+
+        SharedPreferences.Editor editor = encryptedSharedPreferences.edit();
+        editor.putString("someKey", "someValue");
+        editor.apply();
+    }
+
 }
diff --git a/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java b/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
index 6a231a6..44b327f 100644
--- a/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
+++ b/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
@@ -78,7 +78,7 @@
     private static final String NULL_VALUE = "__NULL__";
 
     final SharedPreferences mSharedPreferences;
-    final List<OnSharedPreferenceChangeListener> mListeners;
+    final CopyOnWriteArrayList<OnSharedPreferenceChangeListener> mListeners;
     final String mFileName;
     final String mMasterKeyAlias;
 
@@ -95,7 +95,7 @@
         mMasterKeyAlias = masterKeyAlias;
         mValueAead = aead;
         mKeyDeterministicAead = deterministicAead;
-        mListeners = new ArrayList<>();
+        mListeners = new CopyOnWriteArrayList<>();
     }
 
     /**
diff --git a/security/security-app-authenticator/OWNERS b/security/security-app-authenticator/OWNERS
new file mode 100644
index 0000000..fd24685
--- /dev/null
+++ b/security/security-app-authenticator/OWNERS
@@ -0,0 +1 @@
+mpgroover@google.com
\ No newline at end of file
diff --git a/security/security-app-authenticator/api/current.txt b/security/security-app-authenticator/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/security/security-app-authenticator/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/security/security-app-authenticator/api/public_plus_experimental_current.txt b/security/security-app-authenticator/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/security/security-app-authenticator/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/compose/ui/ui-test-font/src/main/res/font/invalid_font.ttf b/security/security-app-authenticator/api/res-current.txt
similarity index 100%
copy from compose/ui/ui-test-font/src/main/res/font/invalid_font.ttf
copy to security/security-app-authenticator/api/res-current.txt
diff --git a/security/security-app-authenticator/api/restricted_current.txt b/security/security-app-authenticator/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/security/security-app-authenticator/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/security/security-app-authenticator/build.gradle b/security/security-app-authenticator/build.gradle
new file mode 100644
index 0000000..60e92d2
--- /dev/null
+++ b/security/security-app-authenticator/build.gradle
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.LibraryType
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+}
+
+dependencies {
+    // Add dependencies here
+}
+
+androidx {
+    name = "Android Security App Package Authenitcator Library"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenVersion = LibraryVersions.SECURITY_APP_AUTHENTICATOR
+    mavenGroup = LibraryGroups.SECURITY
+    inceptionYear = "2020"
+    description = "Verify app packages for proper app to app authentication."
+}
diff --git a/car/app/app/src/androidTest/AndroidManifest.xml b/security/security-app-authenticator/src/androidTest/AndroidManifest.xml
similarity index 92%
copy from car/app/app/src/androidTest/AndroidManifest.xml
copy to security/security-app-authenticator/src/androidTest/AndroidManifest.xml
index 3bc2684..1ae9328 100644
--- a/car/app/app/src/androidTest/AndroidManifest.xml
+++ b/security/security-app-authenticator/src/androidTest/AndroidManifest.xml
@@ -15,5 +15,6 @@
   limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="androidx.car.app">
+    package="androidx.security.app.authenticator.test">
+
 </manifest>
diff --git a/car/app/app/src/androidTest/AndroidManifest.xml b/security/security-app-authenticator/src/main/AndroidManifest.xml
similarity index 92%
copy from car/app/app/src/androidTest/AndroidManifest.xml
copy to security/security-app-authenticator/src/main/AndroidManifest.xml
index 3bc2684..239ae64 100644
--- a/car/app/app/src/androidTest/AndroidManifest.xml
+++ b/security/security-app-authenticator/src/main/AndroidManifest.xml
@@ -15,5 +15,6 @@
   limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="androidx.car.app">
-</manifest>
+    package="androidx.security.app.authenticator">
+
+</manifest>
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index a688c1c..661f378 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -216,6 +216,7 @@
 includeProject(":compose:integration-tests:demos:common", "compose/integration-tests/demos/common", [BuildType.COMPOSE])
 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:material", "compose/material", [BuildType.COMPOSE])
 includeProject(":compose:material:material", "compose/material/material", [BuildType.COMPOSE])
@@ -431,6 +432,7 @@
 includeProject(":room:room-testing", "room/testing", [BuildType.MAIN])
 includeProject(":savedstate:savedstate", "savedstate/savedstate", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":savedstate:savedstate-ktx", "savedstate/savedstate-ktx", [BuildType.MAIN, BuildType.FLAN])
+includeProject(":security:security-app-authenticator", "security/security-app-authenticator", [BuildType.MAIN])
 includeProject(":security:security-biometric", "security/security-biometric", [BuildType.MAIN])
 includeProject(":security:security-crypto", "security/crypto", [BuildType.MAIN])
 includeProject(":security:security-crypto-ktx", "security/security-crypto-ktx", [BuildType.MAIN])
diff --git a/slices/core/api/restricted_current.txt b/slices/core/api/restricted_current.txt
index c1cada5..02fc647 100644
--- a/slices/core/api/restricted_current.txt
+++ b/slices/core/api/restricted_current.txt
@@ -5,6 +5,10 @@
     method public long currentTimeMillis();
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CornerDrawable extends android.graphics.drawable.InsetDrawable {
+    ctor public CornerDrawable(android.graphics.drawable.Drawable?, float);
+  }
+
   @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, isCustom=true) public final class Slice extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.versionedparcelable.VersionedParcelable {
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.Slice? bindSlice(android.content.Context!, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>!);
     method public java.util.List<java.lang.String!>! getHints();
diff --git a/slices/core/src/main/java/androidx/slice/CornerDrawable.java b/slices/core/src/main/java/androidx/slice/CornerDrawable.java
new file mode 100644
index 0000000..5afbe40
--- /dev/null
+++ b/slices/core/src/main/java/androidx/slice/CornerDrawable.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.slice;
+
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+ /**
+ * A wrapper for Drawables that uses a path to add mask for corners around the drawable,
+ * to match the radius of the underlying shape.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class CornerDrawable extends InsetDrawable {
+    private float mCornerRadius;
+    private final Path mPath = new Path();
+
+    public CornerDrawable(@Nullable Drawable drawable, float cornerRadius) {
+        super(drawable, 0);
+        mCornerRadius = cornerRadius;
+    }
+
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        int saveCount = canvas.save();
+        canvas.clipPath(mPath);
+        super.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    protected void onBoundsChange(@Nullable Rect r) {
+        if (mPath != null) {
+            mPath.reset();
+            mPath.addRoundRect(new RectF(r), mCornerRadius, mCornerRadius, Path.Direction.CW);
+        }
+        super.onBoundsChange(r);
+    }
+}
diff --git a/slices/view/lint-baseline.xml b/slices/view/lint-baseline.xml
index 1199cdc7..999809f 100644
--- a/slices/view/lint-baseline.xml
+++ b/slices/view/lint-baseline.xml
@@ -1982,501 +1982,6 @@
     </issue>
 
     <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_min&quot; formatted=&quot;false&quot; msgid=&quot;6996334305156847955&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/strings.xml"
-            line="28"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/strings.xml"
-            line="30"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/strings.xml"
-            line="30"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_years&quot; formatted=&quot;false&quot; msgid=&quot;6212691832333991589&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/strings.xml"
-            line="30"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/strings.xml"
-            line="32"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/strings.xml"
-            line="36"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/strings.xml"
-            line="36"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;abc_slice_duration_days&quot; formatted=&quot;false&quot; msgid=&quot;6241698511167107334&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/strings.xml"
-            line="36"
-            column="5"/>
-    </issue>
-
-    <issue
         id="ObsoleteLayoutParam"
         message="Invalid layout param in a `LinearLayout`: `layout_alignStart`"
         errorLine1="            android:layout_alignStart=&quot;@android:id/title&quot;"
diff --git a/slices/view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java b/slices/view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java
index 3f98f01..8a2b761 100644
--- a/slices/view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java
+++ b/slices/view/src/androidTest/java/androidx/slice/widget/SliceStyleTest.java
@@ -48,6 +48,7 @@
 
     @Before
     public void setup() {
+        mContext.setTheme(R.style.AppTheme);
         // Empty XML file to initialize empty AttributeSet.
         XmlPullParser parser = mContext.getResources().getXml(R.xml.slice_style_test);
         AttributeSet attributes = Xml.asAttributeSet(parser);
diff --git a/slices/view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java b/slices/view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java
index f9dcce2..4fc92c0 100644
--- a/slices/view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java
+++ b/slices/view/src/androidTest/java/androidx/slice/widget/SliceViewTest.java
@@ -74,6 +74,7 @@
     @Before
     @UiThreadTest
     public void setup() {
+        mContext.setTheme(R.style.AppTheme);
         mSliceView = new SliceView(mContext);
         SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
     }
diff --git a/slices/view/src/main/java/androidx/slice/widget/GridRowView.java b/slices/view/src/main/java/androidx/slice/widget/GridRowView.java
index 4ba719b..3d27f6f 100644
--- a/slices/view/src/main/java/androidx/slice/widget/GridRowView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/GridRowView.java
@@ -67,6 +67,7 @@
 import androidx.annotation.ColorInt;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
+import androidx.slice.CornerDrawable;
 import androidx.slice.SliceItem;
 import androidx.slice.core.SliceHints;
 import androidx.slice.core.SliceQuery;
@@ -469,6 +470,8 @@
     private boolean addImageItem(SliceItem item, SliceItem overlayItem, int color,
             ViewGroup container, boolean isSingle) {
         final String format = item.getFormat();
+        final boolean hasRoundedImage =
+                mSliceStyle != null && mSliceStyle.getApplyCornerRadiusToLargeImages();
         if (!FORMAT_IMAGE.equals(format) || item.getIcon() == null) {
             return false;
         }
@@ -477,13 +480,18 @@
             return false;
         }
         ImageView iv = new ImageView(getContext());
-        iv.setImageDrawable(d);
+        if (hasRoundedImage) {
+            CornerDrawable cd = new CornerDrawable(d, mSliceStyle.getImageCornerRadius());
+            iv.setImageDrawable(cd);
+        } else {
+            iv.setImageDrawable(d);
+        }
         LinearLayout.LayoutParams lp;
         if (item.hasHint(SliceHints.HINT_RAW)) {
             iv.setScaleType(ScaleType.CENTER_INSIDE);
             lp = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
         } else if (item.hasHint(HINT_LARGE)) {
-            iv.setScaleType(ScaleType.CENTER_CROP);
+            iv.setScaleType(hasRoundedImage ? ScaleType.FIT_XY : ScaleType.CENTER_CROP);
             int height = isSingle ? MATCH_PARENT : mLargeImageHeight;
             lp = new LinearLayout.LayoutParams(MATCH_PARENT, height);
         } else {
diff --git a/slices/view/src/main/java/androidx/slice/widget/RowView.java b/slices/view/src/main/java/androidx/slice/widget/RowView.java
index b24ab5a..19922e3 100644
--- a/slices/view/src/main/java/androidx/slice/widget/RowView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/RowView.java
@@ -17,6 +17,7 @@
 package androidx.slice.widget;
 
 import static android.app.slice.Slice.EXTRA_RANGE_VALUE;
+import static android.app.slice.Slice.HINT_LARGE;
 import static android.app.slice.Slice.HINT_NO_TINT;
 import static android.app.slice.Slice.HINT_PARTIAL;
 import static android.app.slice.Slice.HINT_SHORTCUT;
@@ -86,6 +87,7 @@
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.core.view.ViewCompat;
+import androidx.slice.CornerDrawable;
 import androidx.slice.SliceItem;
 import androidx.slice.SliceStructure;
 import androidx.slice.core.SliceAction;
@@ -952,7 +954,14 @@
             final float density = getResources().getDisplayMetrics().density;
             ImageView iv = new ImageView(getContext());
             Drawable d = icon.loadDrawable(getContext());
-            iv.setImageDrawable(d);
+            final boolean hasRoundedImage =
+                    mSliceStyle != null && mSliceStyle.getApplyCornerRadiusToLargeImages();
+            if (hasRoundedImage && sliceItem.hasHint(HINT_LARGE)) {
+                CornerDrawable cd = new CornerDrawable(d, mSliceStyle.getImageCornerRadius());
+                iv.setImageDrawable(cd);
+            } else {
+                iv.setImageDrawable(d);
+            }
             if (isIcon && color != -1) {
                 iv.setColorFilter(color);
             }
diff --git a/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java b/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java
index 6432fa2..67e3d90 100644
--- a/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java
+++ b/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java
@@ -89,6 +89,8 @@
 
     private final Context mContext;
 
+    private final float mImageCornerRadius;
+
     public SliceStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SliceView,
                 defStyleAttr, defStyleRes);
@@ -155,6 +157,8 @@
             mHideHeaderRow = a.getBoolean(R.styleable.SliceView_hideHeaderRow, false);
 
             mContext = context;
+
+            mImageCornerRadius = (float) a.getDimension(R.styleable.SliceView_imageCornerRadius, 0);
         } finally {
             a.recycle();
         }
@@ -304,6 +308,14 @@
         return mHideHeaderRow;
     }
 
+    public boolean getApplyCornerRadiusToLargeImages() {
+        return mImageCornerRadius > 0;
+    }
+
+    public float getImageCornerRadius() {
+        return mImageCornerRadius;
+    }
+
     public int getRowHeight(RowContent row, SliceViewPolicy policy) {
         int maxHeight = policy.getMaxSmallHeight() > 0 ? policy.getMaxSmallHeight() : mRowMaxHeight;
 
diff --git a/slices/view/src/main/res/values/attrs.xml b/slices/view/src/main/res/values/attrs.xml
index 973e690..f0116cc 100644
--- a/slices/view/src/main/res/values/attrs.xml
+++ b/slices/view/src/main/res/values/attrs.xml
@@ -35,6 +35,9 @@
          a tint color here will override the app supplied color. -->
         <attr name="tintColor" />
 
+        <!-- The corner radius to be applied to each corner of large images. -->
+        <attr name="imageCornerRadius" format="dimension" />
+
         <!-- Text size to use for title text in the header of the slice. -->
         <attr name="headerTitleSize" format="dimension" />
         <!-- Text size to use for subtitle text in the header of the slice. -->
diff --git a/testutils/testutils-common/src/main/java/androidx/testutils/TestExecutor.kt b/testutils/testutils-common/src/main/java/androidx/testutils/TestExecutor.kt
index f1730b0..f1ac548 100644
--- a/testutils/testutils-common/src/main/java/androidx/testutils/TestExecutor.kt
+++ b/testutils/testutils-common/src/main/java/androidx/testutils/TestExecutor.kt
@@ -20,10 +20,18 @@
 import java.util.concurrent.Executor
 
 class TestExecutor : Executor {
+    /**
+     * If true, adding a new task will drain all existing tasks.
+     */
+    var autoRun: Boolean = false
+
     private val mTasks = LinkedList<Runnable>()
 
     override fun execute(command: Runnable) {
         mTasks.add(command)
+        if (autoRun) {
+            executeAll()
+        }
     }
 
     fun executeAll(): Boolean {
diff --git a/testutils/testutils-paging/src/main/java/androidx/paging/LoadStateUtils.kt b/testutils/testutils-paging/src/main/java/androidx/paging/LoadStateUtils.kt
index 6e22616..76a4482 100644
--- a/testutils/testutils-paging/src/main/java/androidx/paging/LoadStateUtils.kt
+++ b/testutils/testutils-paging/src/main/java/androidx/paging/LoadStateUtils.kt
@@ -19,23 +19,6 @@
 import androidx.paging.LoadState.NotLoading
 
 /**
- * Converts a list of incremental LoadState updates to local source to a list of expected
- * [CombinedLoadStates] events.
- */
-@OptIn(ExperimentalStdlibApi::class)
-fun List<Pair<LoadType, LoadState>>.toCombinedLoadStatesLocal() = scan(
-    CombinedLoadStates(
-        source = LoadStates(
-            refresh = NotLoading(endOfPaginationReached = false),
-            prepend = NotLoading(endOfPaginationReached = false),
-            append = NotLoading(endOfPaginationReached = false)
-        )
-    )
-) { prev, update ->
-    prev.set(update.first, false, update.second)
-}
-
-/**
  * Test-only local-only LoadStates builder which defaults each state to [NotLoading], with
  * [LoadState.endOfPaginationReached] = `false`
  */
@@ -44,11 +27,14 @@
     prependLocal: LoadState = NotLoading(endOfPaginationReached = false),
     appendLocal: LoadState = NotLoading(endOfPaginationReached = false)
 ) = CombinedLoadStates(
+    refresh = refreshLocal,
+    prepend = prependLocal,
+    append = appendLocal,
     source = LoadStates(
         refresh = refreshLocal,
         prepend = prependLocal,
-        append = appendLocal
-    )
+        append = appendLocal,
+    ),
 )
 
 /**
@@ -56,6 +42,9 @@
  * [LoadState.endOfPaginationReached] = `false`
  */
 fun remoteLoadStatesOf(
+    refresh: LoadState = NotLoading(endOfPaginationReached = false),
+    prepend: LoadState = NotLoading(endOfPaginationReached = false),
+    append: LoadState = NotLoading(endOfPaginationReached = false),
     refreshLocal: LoadState = NotLoading(endOfPaginationReached = false),
     prependLocal: LoadState = NotLoading(endOfPaginationReached = false),
     appendLocal: LoadState = NotLoading(endOfPaginationReached = false),
@@ -63,66 +52,17 @@
     prependRemote: LoadState = NotLoading(endOfPaginationReached = false),
     appendRemote: LoadState = NotLoading(endOfPaginationReached = false)
 ) = CombinedLoadStates(
+    refresh = refresh,
+    prepend = prepend,
+    append = append,
     source = LoadStates(
         refresh = refreshLocal,
         prepend = prependLocal,
-        append = appendLocal
+        append = appendLocal,
     ),
     mediator = LoadStates(
         refresh = refreshRemote,
         prepend = prependRemote,
-        append = appendRemote
-    )
+        append = appendRemote,
+    ),
 )
-
-private fun CombinedLoadStates.set(
-    loadType: LoadType,
-    fromMediator: Boolean,
-    loadState: LoadState
-) = when (loadType) {
-    LoadType.REFRESH ->
-        if (fromMediator) {
-            copy(
-                mediator = mediator?.copy(refresh = loadState)
-                    ?: LoadStates(
-                        refresh = loadState,
-                        prepend = NotLoading(false),
-                        append = NotLoading(false)
-                    )
-            )
-        } else {
-            copy(
-                source = source.copy(refresh = loadState)
-            )
-        }
-    LoadType.PREPEND ->
-        if (fromMediator) {
-            copy(
-                mediator = mediator?.copy(prepend = loadState)
-                    ?: LoadStates(
-                        refresh = NotLoading(false),
-                        prepend = loadState,
-                        append = NotLoading(false)
-                    )
-            )
-        } else {
-            copy(
-                source = source.copy(prepend = loadState)
-            )
-        }
-    LoadType.APPEND ->
-        if (fromMediator) {
-            copy(
-                mediator = mediator?.copy(append = loadState)
-                    ?: LoadStates(
-                        refresh = NotLoading(false),
-                        prepend = NotLoading(false),
-                        append = loadState
-                    )
-            )
-        } else {
-            copy(
-                source = source.copy(append = loadState)
-            )
-        }
-}
diff --git a/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt b/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
index d0113b5..d5f3b92 100644
--- a/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
+++ b/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
@@ -82,7 +82,6 @@
         }
     }
 
-    @OptIn(ExperimentalPagingApi::class)
     override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
         getRefreshKeyCalls.add(state)
         return state.anchorPosition
diff --git a/testutils/testutils-truth/src/main/java/androidx/testutils/assertions.kt b/testutils/testutils-truth/src/main/java/androidx/testutils/assertions.kt
index 6e86816..1dfc738 100644
--- a/testutils/testutils-truth/src/main/java/androidx/testutils/assertions.kt
+++ b/testutils/testutils-truth/src/main/java/androidx/testutils/assertions.kt
@@ -46,3 +46,22 @@
 }
 
 fun fail(message: String? = null): Nothing = throw AssertionError(message)
+
+// The assertThrows above cannot be used from Java.
+@Suppress("UNCHECKED_CAST")
+fun <T : Throwable?> assertThrows(
+    expectedType: Class<T>,
+    runnable: Runnable
+): ThrowableSubject {
+    try {
+        runnable.run()
+    } catch (t: Throwable) {
+        if (expectedType.isInstance(t)) {
+            return assertThat(t)
+        }
+        throw t
+    }
+    throw AssertionError(
+        "Body completed successfully. Expected ${expectedType.simpleName}"
+    )
+}
\ No newline at end of file
diff --git a/testutils/testutils-truth/src/test/java/androidx/testutils/AssertionsTest.kt b/testutils/testutils-truth/src/test/java/androidx/testutils/AssertionsTest.kt
new file mode 100644
index 0000000..b65e977
--- /dev/null
+++ b/testutils/testutils-truth/src/test/java/androidx/testutils/AssertionsTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.testutils
+
+import org.junit.Test
+import java.io.IOException
+
+class AssertionsTest {
+    @Test
+    fun testNoFailureThrowsAssertionError() {
+        try {
+            assertThrows(IOException::class.java) {
+                // No Exception thrown
+            }
+        } catch (e: AssertionError) {
+            return // expected
+        }
+
+        fail("expected assertion error for no failure")
+    }
+
+    @Test
+    fun testIncorrectFailureThrowsAssertionError() {
+        try {
+            assertThrows(IOException::class.java) {
+                throw IllegalStateException()
+            }
+        } catch (e: IllegalStateException) {
+            return // expected
+        }
+
+        fail("expected IllegalStateException to propagate")
+    }
+
+    @Test
+    fun testCorrectFailureTypeIsCaughtAndReturnsAsThrowableSubject() {
+        assertThrows(IOException::class.java) {
+            throw IOException("test123")
+        }.hasMessageThat().contains("test123")
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-complications-data/api/current.txt b/wear/wear-complications-data/api/current.txt
index 1a51768..15d192c 100644
--- a/wear/wear-complications-data/api/current.txt
+++ b/wear/wear-complications-data/api/current.txt
@@ -138,6 +138,13 @@
 
 package androidx.wear.complications {
 
+  public final class ComplicationBounds {
+    ctor public ComplicationBounds(java.util.Map<androidx.wear.complications.data.ComplicationType,? extends android.graphics.RectF> perComplicationTypeBounds);
+    ctor public ComplicationBounds(android.graphics.RectF bounds);
+    method public java.util.Map<androidx.wear.complications.data.ComplicationType,android.graphics.RectF> getPerComplicationTypeBounds();
+    property public final java.util.Map<androidx.wear.complications.data.ComplicationType,android.graphics.RectF> perComplicationTypeBounds;
+  }
+
   public final class ComplicationHelperActivity extends android.app.Activity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback {
     ctor public ComplicationHelperActivity();
     method public static android.content.Intent createPermissionRequestHelperIntent(android.content.Context, android.content.ComponentName);
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 8ee1e1c..abee040 100644
--- a/wear/wear-complications-data/api/public_plus_experimental_current.txt
+++ b/wear/wear-complications-data/api/public_plus_experimental_current.txt
@@ -138,6 +138,13 @@
 
 package androidx.wear.complications {
 
+  public final class ComplicationBounds {
+    ctor public ComplicationBounds(java.util.Map<androidx.wear.complications.data.ComplicationType,? extends android.graphics.RectF> perComplicationTypeBounds);
+    ctor public ComplicationBounds(android.graphics.RectF bounds);
+    method public java.util.Map<androidx.wear.complications.data.ComplicationType,android.graphics.RectF> getPerComplicationTypeBounds();
+    property public final java.util.Map<androidx.wear.complications.data.ComplicationType,android.graphics.RectF> perComplicationTypeBounds;
+  }
+
   public final class ComplicationHelperActivity extends android.app.Activity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback {
     ctor public ComplicationHelperActivity();
     method public static android.content.Intent createPermissionRequestHelperIntent(android.content.Context, android.content.ComponentName);
diff --git a/wear/wear-complications-data/api/restricted_current.txt b/wear/wear-complications-data/api/restricted_current.txt
index 16dcece..297a848 100644
--- a/wear/wear-complications-data/api/restricted_current.txt
+++ b/wear/wear-complications-data/api/restricted_current.txt
@@ -156,6 +156,13 @@
 
 package androidx.wear.complications {
 
+  public final class ComplicationBounds {
+    ctor public ComplicationBounds(java.util.Map<androidx.wear.complications.data.ComplicationType,? extends android.graphics.RectF> perComplicationTypeBounds);
+    ctor public ComplicationBounds(android.graphics.RectF bounds);
+    method public java.util.Map<androidx.wear.complications.data.ComplicationType,android.graphics.RectF> getPerComplicationTypeBounds();
+    property public final java.util.Map<androidx.wear.complications.data.ComplicationType,android.graphics.RectF> perComplicationTypeBounds;
+  }
+
   public final class ComplicationHelperActivity extends android.app.Activity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback {
     ctor public ComplicationHelperActivity();
     method public static android.content.Intent createPermissionRequestHelperIntent(android.content.Context, android.content.ComponentName);
diff --git a/wear/wear-complications-data/lint-baseline.xml b/wear/wear-complications-data/lint-baseline.xml
index d0c3c4b..dde667a 100644
--- a/wear/wear-complications-data/lint-baseline.xml
+++ b/wear/wear-complications-data/lint-baseline.xml
@@ -45,994 +45,4 @@
             column="1"/>
     </issue>
 
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_days&quot; formatted=&quot;false&quot; msgid=&quot;8500262093840795448&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/complication_strings.xml"
-            line="4"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/complication_strings.xml"
-            line="8"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/complication_strings.xml"
-            line="10"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/complication_strings.xml"
-            line="10"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_hours&quot; formatted=&quot;false&quot; msgid=&quot;3258361256003469346&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/complication_strings.xml"
-            line="10"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/complication_strings.xml"
-            line="12"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/complication_strings.xml"
-            line="16"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/complication_strings.xml"
-            line="16"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_short_minutes&quot; formatted=&quot;false&quot; msgid=&quot;3812930575997556650&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/complication_strings.xml"
-            line="16"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/complication_strings.xml"
-            line="18"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/complication_strings.xml"
-            line="22"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/complication_strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/complication_strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_days&quot; formatted=&quot;false&quot; msgid=&quot;345557497041553025&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/complication_strings.xml"
-            line="24"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;in&quot; (Indonesian) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-in/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ja&quot; (Japanese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ja/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;km&quot; (Khmer) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-km/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ko&quot; (Korean) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ko/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lo&quot; (Lao) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lo/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;ms&quot; (Malay) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-ms/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;my&quot; (Burmese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-my/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;th&quot; (Thai) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-th/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;vi&quot; (Vietnamese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-vi/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rCN/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rHK/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;zh&quot; (Chinese) the following quantities are not relevant: `one`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-zh-rTW/complication_strings.xml"
-            line="26"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/complication_strings.xml"
-            line="30"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/complication_strings.xml"
-            line="30"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_hours&quot; formatted=&quot;false&quot; msgid=&quot;2990178439049007198&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/complication_strings.xml"
-            line="30"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;cs&quot; (Czech) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-cs/complication_strings.xml"
-            line="36"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;lt&quot; (Lithuanian) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-lt/complication_strings.xml"
-            line="36"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="UnusedQuantity"
-        message="For language &quot;sk&quot; (Slovak) the following quantities are not relevant: `many`"
-        errorLine1="    &lt;plurals name=&quot;time_difference_words_minutes&quot; formatted=&quot;false&quot; msgid=&quot;9081188175463984403&quot;>"
-        errorLine2="    ^">
-        <location
-            file="src/main/res/values-sk/complication_strings.xml"
-            line="36"
-            column="5"/>
-    </issue>
-
 </issues>
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationBounds.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationBounds.kt
new file mode 100644
index 0000000..5f39c06
--- /dev/null
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationBounds.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.wear.complications
+
+import android.graphics.RectF
+import androidx.wear.complications.data.ComplicationType
+
+/**
+ * ComplicationBounds are defined by fractional screen space coordinates in unit-square [0..1].
+ * These bounds will be subsequently clamped to the unit square and converted to screen space
+ * coordinates. NB 0 and 1 are included in the unit square.
+ *
+ * One bound is expected per [ComplicationType] to allow complications to change shape depending on
+ * the type.
+ */
+public class ComplicationBounds(
+    /** Per [ComplicationType] fractional unit-square screen space complication bounds. */
+    public val perComplicationTypeBounds: Map<ComplicationType, RectF>
+) {
+    /**
+     * Constructs a ComplicationBounds where all complication types have the same screen space
+     * unit-square bounds.
+     */
+    public constructor(bounds: RectF) : this(ComplicationType.values().associateWith { bounds })
+
+    init {
+        for (type in ComplicationType.values()) {
+            require(perComplicationTypeBounds.containsKey(type)) {
+                "Missing bounds for $type"
+            }
+        }
+    }
+}
\ No newline at end of file
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 889a3a5..847643a 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
@@ -31,7 +31,7 @@
     /** The type of the complication's bounds. */
     @ComplicationBoundsType public val boundsType: Int,
 
-    /** The [ComplicationType]s supported for this complication. */
+    /** The [ComplicationType]s supported by this complication. */
     public val supportedTypes: List<ComplicationType>,
 
     /** The [DefaultComplicationProviderPolicy] for this complication. */
diff --git a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt
index b59405b..521ef55 100644
--- a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt
+++ b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/HeadlessWatchFaceClient.kt
@@ -30,7 +30,13 @@
 import androidx.wear.watchface.style.UserStyle
 import androidx.wear.watchface.style.UserStyleSchema
 
-/** Controls a stateless remote headless watch face. */
+/**
+ * Controls a stateless remote headless watch face.  This is mostly intended for use by watch face
+ * editor UIs which need to generate screenshots for various styling configurations without
+ * affecting the current watchface.
+ *
+ * Note clients should call [close] when finished.
+ */
 public interface HeadlessWatchFaceClient : AutoCloseable {
     /** The UTC reference preview time for this watch face in milliseconds since the epoch. */
     public val previewReferenceTimeMillis: Long
diff --git a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceSysUiClient.kt b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceSysUiClient.kt
index 9a5ca44..5d41c43 100644
--- a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceSysUiClient.kt
+++ b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceSysUiClient.kt
@@ -50,7 +50,9 @@
 
 /**
  * Controls a stateful remote interactive watch face with an interface tailored for SysUI the
- * WearOS 3.0 launcher app. Typically this will be used for the current active watch face.
+ * WearOS launcher app. Typically this will be used for the current active watch face.
+ *
+ * Note clients should call [close] when finished.
  */
 public interface InteractiveWatchFaceSysUiClient : AutoCloseable {
 
@@ -72,7 +74,10 @@
          */
         public const val TAP_TYPE_TAP: Int = IInteractiveWatchFaceSysUI.TAP_TYPE_TAP
 
-        /** Constructs a [InteractiveWatchFaceSysUiClient] from an [IBinder]. */
+        /**
+         * Constructs an [InteractiveWatchFaceSysUiClient] from the [IBinder] returned by
+         * [asBinder].
+         */
         @JvmStatic
         public fun createFromBinder(binder: IBinder): InteractiveWatchFaceSysUiClient =
             InteractiveWatchFaceSysUiClientImpl(binder)
diff --git a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceWcsClient.kt b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceWcsClient.kt
index c65f1db..1205802 100644
--- a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceWcsClient.kt
+++ b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceWcsClient.kt
@@ -32,13 +32,17 @@
 
 /**
  * Controls a stateful remote interactive watch face with an interface tailored for WCS the
- * WearOS 3.0 system server responsible for watch face management. Typically this will be used for
+ * WearOS system server responsible for watch face management. Typically this will be used for
  * the current active watch face.
+ *
+ * Note clients should call [close] when finished.
  */
 public interface InteractiveWatchFaceWcsClient : AutoCloseable {
 
     public companion object {
-        /** Constructs a [InteractiveWatchFaceWcsClient] from an [IBinder]. */
+        /**
+         * Constructs an [InteractiveWatchFaceWcsClient] from the [IBinder] returned by [asBinder].
+         */
         @JvmStatic
         public fun createFromBinder(binder: IBinder): InteractiveWatchFaceWcsClient =
             InteractiveWatchFaceWcsClientImpl(binder)
diff --git a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
index e051212..d3498be 100644
--- a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
+++ b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
@@ -35,13 +35,17 @@
 import com.google.common.util.concurrent.ListenableFuture
 
 /**
- * Connects to a watch face's WatchFaceControlService which allows the user to control the
- * watch face.
+ * Connects to a watch face's WatchFaceControlService which allows the user to control the watch
+ * face.
  */
 public interface WatchFaceControlClient : AutoCloseable {
 
     public companion object {
-        /** Constructs a client which connects to a watch face in the given android package. */
+        /**
+         * Constructs a [WatchFaceControlClient] which attempts to connect to a watch face in the
+         * android package [watchFacePackageName]. If this fails the [ListenableFuture]s returned by
+         * WatchFaceControlClient methods will fail with [ServiceNotBoundException].
+         */
         @JvmStatic
         public fun createWatchFaceControlClient(
             /** Calling application's [Context]. */
diff --git a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
index 95e4126..91c08a4 100644
--- a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
+++ b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
@@ -34,7 +34,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
@@ -51,8 +50,8 @@
 import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.wear.complications.ComplicationHelperActivity;
+import androidx.wear.watchface.CanvasType;
 import androidx.wear.watchface.ComplicationsManager;
-import androidx.wear.watchface.RenderParameters;
 import androidx.wear.watchface.Renderer;
 import androidx.wear.watchface.WatchFace;
 import androidx.wear.watchface.WatchFaceService;
@@ -720,18 +719,13 @@
                     WatchFaceType.ANALOG,
                     userStyleRepository,
                     new ComplicationsManager(new ArrayList<>(), userStyleRepository),
-                    new Renderer(surfaceHolder, userStyleRepository, watchState, 16L) {
-                        @NotNull
+                    new Renderer.CanvasRenderer(
+                            surfaceHolder, userStyleRepository, watchState, CanvasType.SOFTWARE,
+                            16L) {
                         @Override
-                        public Bitmap takeScreenshot$wear_watchface_debug(
-                                @NotNull Calendar calendar,
-                                @NonNull RenderParameters renderParameters) {
-                            return null;
-                        }
+                        public void render(@NonNull Canvas canvas, @NonNull Rect bounds,
+                                @NonNull Calendar calendar) {
 
-                        @Override
-                        public void renderInternal$wear_watchface_debug(
-                                @NotNull Calendar calendar) {
                         }
                     }
             );
diff --git a/wear/wear-watchface-data/api/restricted_current.txt b/wear/wear-watchface-data/api/restricted_current.txt
index 8d43ba1..f95a226 100644
--- a/wear/wear-watchface-data/api/restricted_current.txt
+++ b/wear/wear-watchface-data/api/restricted_current.txt
@@ -276,7 +276,7 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public static class ComplicationsUserStyleSettingWireFormat.ComplicationOverlayWireFormat implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
-    ctor public ComplicationsUserStyleSettingWireFormat.ComplicationOverlayWireFormat(int, Boolean?, android.graphics.RectF?, int[]?, java.util.List<android.content.ComponentName!>?, Integer?, @android.support.wearable.complications.ComplicationData.ComplicationType Integer?);
+    ctor public ComplicationsUserStyleSettingWireFormat.ComplicationOverlayWireFormat(int, Boolean?, java.util.Map<androidx.wear.complications.data.ComplicationType!,android.graphics.RectF!>?, int[]?, java.util.List<android.content.ComponentName!>?, Integer?, @android.support.wearable.complications.ComplicationData.ComplicationType Integer?);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<androidx.wear.watchface.style.data.ComplicationsUserStyleSettingWireFormat.ComplicationOverlayWireFormat!>! CREATOR;
@@ -284,11 +284,11 @@
     field public static final int ENABLED_UNKNOWN = -1; // 0xffffffff
     field public static final int ENABLED_YES = 1; // 0x1
     field public static final int NO_DEFAULT_PROVIDER_TYPE = -1; // 0xffffffff
-    field @androidx.versionedparcelable.ParcelField(3) public android.graphics.RectF? mBounds;
     field @androidx.versionedparcelable.ParcelField(1) public int mComplicationId;
     field @android.support.wearable.complications.ComplicationData.ComplicationType @androidx.versionedparcelable.ParcelField(7) public int mDefaultProviderType;
     field @androidx.versionedparcelable.ParcelField(5) public java.util.List<android.content.ComponentName!>? mDefaultProviders;
     field @androidx.versionedparcelable.ParcelField(2) public int mEnabled;
+    field @androidx.versionedparcelable.ParcelField(3) public java.util.Map<androidx.wear.complications.data.ComplicationType!,android.graphics.RectF!>? mPerComplicationTypeBounds;
     field @androidx.versionedparcelable.ParcelField(4) public int[]? mSupportedTypes;
     field @androidx.versionedparcelable.ParcelField(6) @androidx.wear.complications.SystemProviders.ProviderId public int mSystemProviderFallback;
   }
diff --git a/wear/wear-watchface-data/build.gradle b/wear/wear-watchface-data/build.gradle
index f32aa2f..97a32fd 100644
--- a/wear/wear-watchface-data/build.gradle
+++ b/wear/wear-watchface-data/build.gradle
@@ -48,6 +48,10 @@
     buildFeatures {
         aidl = true
     }
+
+    buildTypes.all {
+        consumerProguardFiles 'proguard-rules.pro'
+    }
 }
 
 androidx {
diff --git a/wear/wear-watchface-data/proguard-rules.pro b/wear/wear-watchface-data/proguard-rules.pro
new file mode 100644
index 0000000..f50b7ac
--- /dev/null
+++ b/wear/wear-watchface-data/proguard-rules.pro
@@ -0,0 +1,16 @@
+# 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.
+
+# Prevent Parcelizer objects from being removed or renamed.
+-keep public class androidx.wear.watchface.**Parcelizer { *; }
\ No newline at end of file
diff --git a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationsUserStyleSettingWireFormat.java b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationsUserStyleSettingWireFormat.java
index 2340a93..357f31a 100644
--- a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationsUserStyleSettingWireFormat.java
+++ b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationsUserStyleSettingWireFormat.java
@@ -32,8 +32,10 @@
 import androidx.versionedparcelable.VersionedParcelable;
 import androidx.versionedparcelable.VersionedParcelize;
 import androidx.wear.complications.SystemProviders;
+import androidx.wear.complications.data.ComplicationType;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * Wire format for {@link androidx.wear.watchface.style.ComplicationsUserStyleSetting}.
@@ -80,7 +82,7 @@
 
         @ParcelField(3)
         @Nullable
-        public RectF mBounds;
+        public Map<ComplicationType, RectF> mPerComplicationTypeBounds;
 
         @ParcelField(4)
         @Nullable
@@ -111,7 +113,7 @@
         public ComplicationOverlayWireFormat(
                 int complicationId,
                 @Nullable Boolean enabled,
-                @Nullable RectF bounds,
+                @Nullable Map<ComplicationType, RectF> perComplicationTypeBounds,
                 @Nullable int[] supportedTypes,
                 @Nullable List<ComponentName> defaultProviders,
                 @Nullable Integer systemProviderFallback,
@@ -123,7 +125,7 @@
             } else {
                 mEnabled = ENABLED_UNKNOWN;
             }
-            mBounds = bounds;
+            mPerComplicationTypeBounds = perComplicationTypeBounds;
             mSupportedTypes = supportedTypes;
             mDefaultProviders = defaultProviders;
             if (systemProviderFallback != null) {
diff --git a/wear/wear-watchface-style/api/current.txt b/wear/wear-watchface-style/api/current.txt
index 9b09a99..a049435 100644
--- a/wear/wear-watchface-style/api/current.txt
+++ b/wear/wear-watchface-style/api/current.txt
@@ -71,14 +71,14 @@
   }
 
   public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay {
-    ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, android.graphics.RectF? bounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
-    method public android.graphics.RectF? getBounds();
+    ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, androidx.wear.complications.ComplicationBounds? complicationBounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
+    method public androidx.wear.complications.ComplicationBounds? getComplicationBounds();
     method public int getComplicationId();
     method public androidx.wear.complications.DefaultComplicationProviderPolicy? getDefaultProviderPolicy();
     method public androidx.wear.complications.data.ComplicationType? getDefaultProviderType();
     method public java.util.List<androidx.wear.complications.data.ComplicationType>? getSupportedTypes();
     method public Boolean? isEnabled();
-    property public final android.graphics.RectF? bounds;
+    property public final androidx.wear.complications.ComplicationBounds? complicationBounds;
     property public final int complicationId;
     property public final androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy;
     property public final androidx.wear.complications.data.ComplicationType? defaultProviderType;
@@ -89,7 +89,7 @@
   public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder {
     ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder(int complicationId);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay build();
-    method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setBounds(android.graphics.RectF bounds);
+    method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setComplicationBounds(androidx.wear.complications.ComplicationBounds complicationBounds);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setDefaultProviderPolicy(androidx.wear.complications.DefaultComplicationProviderPolicy? defaultComplicationProviderPolicy);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setDefaultProviderType(androidx.wear.complications.data.ComplicationType defaultComplicationProviderType);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setEnabled(boolean enabled);
diff --git a/wear/wear-watchface-style/api/public_plus_experimental_current.txt b/wear/wear-watchface-style/api/public_plus_experimental_current.txt
index 622fce2..b3f4470 100644
--- a/wear/wear-watchface-style/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-style/api/public_plus_experimental_current.txt
@@ -74,14 +74,14 @@
   }
 
   public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay {
-    ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, android.graphics.RectF? bounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
-    method public android.graphics.RectF? getBounds();
+    ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, androidx.wear.complications.ComplicationBounds? complicationBounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
+    method public androidx.wear.complications.ComplicationBounds? getComplicationBounds();
     method public int getComplicationId();
     method public androidx.wear.complications.DefaultComplicationProviderPolicy? getDefaultProviderPolicy();
     method public androidx.wear.complications.data.ComplicationType? getDefaultProviderType();
     method public java.util.List<androidx.wear.complications.data.ComplicationType>? getSupportedTypes();
     method public Boolean? isEnabled();
-    property public final android.graphics.RectF? bounds;
+    property public final androidx.wear.complications.ComplicationBounds? complicationBounds;
     property public final int complicationId;
     property public final androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy;
     property public final androidx.wear.complications.data.ComplicationType? defaultProviderType;
@@ -92,7 +92,7 @@
   public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder {
     ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder(int complicationId);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay build();
-    method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setBounds(android.graphics.RectF bounds);
+    method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setComplicationBounds(androidx.wear.complications.ComplicationBounds complicationBounds);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setDefaultProviderPolicy(androidx.wear.complications.DefaultComplicationProviderPolicy? defaultComplicationProviderPolicy);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setDefaultProviderType(androidx.wear.complications.data.ComplicationType defaultComplicationProviderType);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setEnabled(boolean enabled);
diff --git a/wear/wear-watchface-style/api/restricted_current.txt b/wear/wear-watchface-style/api/restricted_current.txt
index b05e004..162e2b9 100644
--- a/wear/wear-watchface-style/api/restricted_current.txt
+++ b/wear/wear-watchface-style/api/restricted_current.txt
@@ -80,14 +80,14 @@
   }
 
   public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay {
-    ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, android.graphics.RectF? bounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
-    method public android.graphics.RectF? getBounds();
+    ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, androidx.wear.complications.ComplicationBounds? complicationBounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
+    method public androidx.wear.complications.ComplicationBounds? getComplicationBounds();
     method public int getComplicationId();
     method public androidx.wear.complications.DefaultComplicationProviderPolicy? getDefaultProviderPolicy();
     method public androidx.wear.complications.data.ComplicationType? getDefaultProviderType();
     method public java.util.List<androidx.wear.complications.data.ComplicationType>? getSupportedTypes();
     method public Boolean? isEnabled();
-    property public final android.graphics.RectF? bounds;
+    property public final androidx.wear.complications.ComplicationBounds? complicationBounds;
     property public final int complicationId;
     property public final androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy;
     property public final androidx.wear.complications.data.ComplicationType? defaultProviderType;
@@ -98,7 +98,7 @@
   public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder {
     ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder(int complicationId);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay build();
-    method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setBounds(android.graphics.RectF bounds);
+    method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setComplicationBounds(androidx.wear.complications.ComplicationBounds complicationBounds);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setDefaultProviderPolicy(androidx.wear.complications.DefaultComplicationProviderPolicy? defaultComplicationProviderPolicy);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setDefaultProviderType(androidx.wear.complications.data.ComplicationType defaultComplicationProviderType);
     method public androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay.Builder setEnabled(boolean enabled);
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleRepository.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleRepository.kt
index cf35f1c..ad56258 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleRepository.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleRepository.kt
@@ -88,8 +88,9 @@
 }
 
 /**
- * In memory storage for user style choices which allows listeners to be registered to observe
- * style changes.
+ * An in memory storage for user style choices represented as [UserStyle], listeners can be
+ * registered to observe style changes. The UserStyleRepository is initialized with a
+ * [UserStyleSchema].
  */
 public class UserStyleRepository(
     /**
@@ -98,16 +99,19 @@
      */
     public val schema: UserStyleSchema
 ) {
-    /** A listener for observing user style changes. */
+    /** A listener for observing [UserStyle] changes. */
     public interface UserStyleListener {
-        /** Called whenever the user style changes. */
+        /** Called whenever the [UserStyle] changes. */
         @UiThread
         public fun onUserStyleChanged(userStyle: UserStyle)
     }
 
     private val styleListeners = HashSet<UserStyleListener>()
 
-    /** The current user controlled style for rendering etc... */
+    /**
+     * The current [UserStyle]. Assigning to this property triggers immediate [UserStyleListener]
+     * callbacks if if any options have changed.
+     */
     public var userStyle: UserStyle = UserStyle(
         HashMap<UserStyleSetting, UserStyleSetting.Option>().apply {
             for (setting in schema.userStyleSettings) {
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
index 978e023..8903a6d 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
@@ -16,9 +16,9 @@
 
 package androidx.wear.watchface.style
 
-import android.graphics.RectF
 import android.graphics.drawable.Icon
 import androidx.annotation.RestrictTo
+import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.data.ComplicationType
 import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay
@@ -32,10 +32,12 @@
 import java.security.InvalidParameterException
 
 /**
- * Watch faces often have user configurable styles. The definition of what is a style is left up
- * to the watch face but it typically incorporates a variety of settings such as: color,
- * visual theme for watch hands, font, tick shape, complications, audio elements, etc...
- * A UserStyleSetting represents one of these dimensions.
+ * Watch faces often have user configurable styles, the definition of what is a style is left up to
+ * the watch face but it typically incorporates a variety of settings such as: color, visual theme
+ * for watch hands, font, tick shape, complications, audio elements, etc...
+ *
+ * A UserStyleSetting represents one of these dimensions. See also [UserStyleSchema] which defines
+ * the list of UserStyleSettings provided by the watch face.
  */
 public sealed class UserStyleSetting(
     /** Identifier for the element, must be unique. */
@@ -291,10 +293,10 @@
             public val enabled: Boolean? = null,
 
             /**
-             * If non null, the new unit square screen space complication bounds for this
-             * configuration. If null then no changes are made.
+             * If non null, the new [ComplicationBounds] for this configuration. If null then no
+             * changes are made.
              */
-            public val bounds: RectF? = null,
+            public val complicationBounds: ComplicationBounds? = null,
 
             /**
              * If non null, the new types of complication supported by this complication for this
@@ -319,7 +321,7 @@
                 private val complicationId: Int
             ) {
                 private var enabled: Boolean? = null
-                private var bounds: RectF? = null
+                private var complicationBounds: ComplicationBounds? = null
                 private var supportedTypes: List<ComplicationType>? = null
                 private var defaultComplicationProviderPolicy: DefaultComplicationProviderPolicy? =
                     null
@@ -330,10 +332,11 @@
                     this.enabled = enabled
                 }
 
-                /** Overrides the complication's unit-square screen space bounds. */
-                public fun setBounds(bounds: RectF): Builder = apply {
-                    this.bounds = bounds
-                }
+                /** Overrides the complication's per [ComplicationBounds]. */
+                public fun setComplicationBounds(complicationBounds: ComplicationBounds): Builder =
+                    apply {
+                        this.complicationBounds = complicationBounds
+                    }
 
                 /** Overrides the complication's supported complication types. */
                 public fun setSupportedTypes(supportedTypes: List<ComplicationType>): Builder =
@@ -357,14 +360,15 @@
                     this.defaultComplicationProviderType = defaultComplicationProviderType
                 }
 
-                public fun build(): ComplicationOverlay = ComplicationOverlay(
-                    complicationId,
-                    enabled,
-                    bounds,
-                    supportedTypes,
-                    defaultComplicationProviderPolicy,
-                    defaultComplicationProviderType
-                )
+                public fun build(): ComplicationOverlay =
+                    ComplicationOverlay(
+                        complicationId,
+                        enabled,
+                        complicationBounds,
+                        supportedTypes,
+                        defaultComplicationProviderPolicy,
+                        defaultComplicationProviderType
+                    )
             }
 
             internal constructor(
@@ -382,7 +386,7 @@
                         "Unrecognised wireFormat.mEnabled " + wireFormat.mEnabled
                     )
                 },
-                wireFormat.mBounds,
+                wireFormat.mPerComplicationTypeBounds?.let { ComplicationBounds(it) },
                 wireFormat.mSupportedTypes?.let { ComplicationType.fromWireTypeList(it) },
                 wireFormat.mDefaultProviders?.let {
                     DefaultComplicationProviderPolicy(it, wireFormat.mSystemProviderFallback)
@@ -401,7 +405,7 @@
                 ComplicationsUserStyleSettingWireFormat.ComplicationOverlayWireFormat(
                     complicationId,
                     enabled,
-                    bounds,
+                    complicationBounds?.perComplicationTypeBounds,
                     supportedTypes?.let { ComplicationType.toWireTypes(it) },
                     defaultProviderPolicy?.providersAsList(),
                     defaultProviderPolicy?.systemProviderFallback,
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index 2196f43..d3c5678 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -15,7 +15,7 @@
 
   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 drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
+    method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
     method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
     method @UiThread public boolean isHighlighted();
@@ -30,37 +30,28 @@
     property @UiThread public boolean isHighlighted;
   }
 
-  public abstract class CanvasRenderer extends androidx.wear.watchface.Renderer {
-    ctor public 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);
-    method @UiThread public abstract void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
-  }
-
   public final class Complication {
     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, android.graphics.RectF unitSquareBounds);
+    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 @UiThread public androidx.wear.complications.ComplicationBounds getComplicationBounds();
     method public androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> getComplicationData();
     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 @UiThread public java.util.List<androidx.wear.complications.data.ComplicationType> getSupportedTypes();
-    method @UiThread public android.graphics.RectF getUnitSquareBounds();
     method public void invalidate();
     method public boolean isActiveAt(long dateTimeMillis);
     method @UiThread public boolean isEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method @UiThread public void setDefaultProviderPolicy(androidx.wear.complications.DefaultComplicationProviderPolicy value);
-    method @UiThread public void setDefaultProviderType(androidx.wear.complications.data.ComplicationType value);
-    method @UiThread public void setEnabled(boolean value);
+    method @UiThread public void setComplicationBounds(androidx.wear.complications.ComplicationBounds value);
     method @UiThread public void setRenderer(androidx.wear.watchface.CanvasComplication value);
-    method @UiThread public void setSupportedTypes(java.util.List<? extends androidx.wear.complications.data.ComplicationType> value);
-    method @UiThread public void setUnitSquareBounds(android.graphics.RectF value);
+    property @UiThread public final androidx.wear.complications.ComplicationBounds complicationBounds;
     property public final androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> complicationData;
     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 @UiThread public final androidx.wear.watchface.CanvasComplication renderer;
     property @UiThread public final java.util.List<androidx.wear.complications.data.ComplicationType> supportedTypes;
-    property @UiThread public final android.graphics.RectF unitSquareBounds;
     field public static final androidx.wear.watchface.Complication.Companion Companion;
   }
 
@@ -71,7 +62,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, android.graphics.RectF unitSquareBounds);
+    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);
   }
 
   public final class ComplicationOutlineRenderer {
@@ -111,18 +102,6 @@
     enum_constant public static final androidx.wear.watchface.DrawMode MUTE;
   }
 
-  public abstract class GlesRenderer extends androidx.wear.watchface.Renderer {
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, int[] eglConfigAttribList, int[] eglSurfaceAttribList);
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, int[] eglConfigAttribList);
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
-    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);
-  }
-
-  public final class GlesRendererKt {
-  }
-
   public final class GlesTextureComplication {
     ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
     method public void bind();
@@ -147,8 +126,8 @@
   public class ObservableWatchData<T> {
     method @UiThread public final void addObserver(androidx.wear.watchface.Observer<T> observer);
     method @UiThread public T getValue();
-    method public final T getValueOr(T p);
-    method public final boolean hasValue();
+    method @UiThread public final T getValueOr(T p);
+    method @UiThread public final boolean hasValue();
     method @UiThread public final void removeObserver(androidx.wear.watchface.Observer<T> observer);
     method @UiThread protected void setValue(T v);
     property @UiThread public T value;
@@ -174,8 +153,7 @@
   public static final class RenderParameters.Companion {
   }
 
-  public abstract class Renderer {
-    ctor public Renderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+  public abstract sealed class Renderer {
     method public final float getCenterX();
     method public final float getCenterY();
     method public final long getInteractiveDrawModeUpdateDelayMillis();
@@ -197,6 +175,23 @@
     property public final android.view.SurfaceHolder surfaceHolder;
   }
 
+  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);
+    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, int[] eglConfigAttribList, 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, 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);
+    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);
+  }
+
+  public final class RendererKt {
+  }
+
   public final class WatchFace {
     ctor public WatchFace(int watchFaceType, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.ComplicationsManager complicationsManager, androidx.wear.watchface.Renderer renderer);
     method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
@@ -244,11 +239,10 @@
   }
 
   public final class WatchState {
-    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
+    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
     method public long getAnalogPreviewReferenceTimeMillis();
     method public long getDigitalPreviewReferenceTimeMillis();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public int getScreenShape();
     method public boolean hasBurnInProtection();
     method public boolean hasLowBitAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
@@ -260,7 +254,6 @@
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible;
-    property public final int screenShape;
   }
 
 }
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index 2196f43..d3c5678 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -15,7 +15,7 @@
 
   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 drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
+    method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
     method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
     method @UiThread public boolean isHighlighted();
@@ -30,37 +30,28 @@
     property @UiThread public boolean isHighlighted;
   }
 
-  public abstract class CanvasRenderer extends androidx.wear.watchface.Renderer {
-    ctor public 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);
-    method @UiThread public abstract void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
-  }
-
   public final class Complication {
     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, android.graphics.RectF unitSquareBounds);
+    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 @UiThread public androidx.wear.complications.ComplicationBounds getComplicationBounds();
     method public androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> getComplicationData();
     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 @UiThread public java.util.List<androidx.wear.complications.data.ComplicationType> getSupportedTypes();
-    method @UiThread public android.graphics.RectF getUnitSquareBounds();
     method public void invalidate();
     method public boolean isActiveAt(long dateTimeMillis);
     method @UiThread public boolean isEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method @UiThread public void setDefaultProviderPolicy(androidx.wear.complications.DefaultComplicationProviderPolicy value);
-    method @UiThread public void setDefaultProviderType(androidx.wear.complications.data.ComplicationType value);
-    method @UiThread public void setEnabled(boolean value);
+    method @UiThread public void setComplicationBounds(androidx.wear.complications.ComplicationBounds value);
     method @UiThread public void setRenderer(androidx.wear.watchface.CanvasComplication value);
-    method @UiThread public void setSupportedTypes(java.util.List<? extends androidx.wear.complications.data.ComplicationType> value);
-    method @UiThread public void setUnitSquareBounds(android.graphics.RectF value);
+    property @UiThread public final androidx.wear.complications.ComplicationBounds complicationBounds;
     property public final androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> complicationData;
     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 @UiThread public final androidx.wear.watchface.CanvasComplication renderer;
     property @UiThread public final java.util.List<androidx.wear.complications.data.ComplicationType> supportedTypes;
-    property @UiThread public final android.graphics.RectF unitSquareBounds;
     field public static final androidx.wear.watchface.Complication.Companion Companion;
   }
 
@@ -71,7 +62,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, android.graphics.RectF unitSquareBounds);
+    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);
   }
 
   public final class ComplicationOutlineRenderer {
@@ -111,18 +102,6 @@
     enum_constant public static final androidx.wear.watchface.DrawMode MUTE;
   }
 
-  public abstract class GlesRenderer extends androidx.wear.watchface.Renderer {
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, int[] eglConfigAttribList, int[] eglSurfaceAttribList);
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, int[] eglConfigAttribList);
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
-    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);
-  }
-
-  public final class GlesRendererKt {
-  }
-
   public final class GlesTextureComplication {
     ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
     method public void bind();
@@ -147,8 +126,8 @@
   public class ObservableWatchData<T> {
     method @UiThread public final void addObserver(androidx.wear.watchface.Observer<T> observer);
     method @UiThread public T getValue();
-    method public final T getValueOr(T p);
-    method public final boolean hasValue();
+    method @UiThread public final T getValueOr(T p);
+    method @UiThread public final boolean hasValue();
     method @UiThread public final void removeObserver(androidx.wear.watchface.Observer<T> observer);
     method @UiThread protected void setValue(T v);
     property @UiThread public T value;
@@ -174,8 +153,7 @@
   public static final class RenderParameters.Companion {
   }
 
-  public abstract class Renderer {
-    ctor public Renderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+  public abstract sealed class Renderer {
     method public final float getCenterX();
     method public final float getCenterY();
     method public final long getInteractiveDrawModeUpdateDelayMillis();
@@ -197,6 +175,23 @@
     property public final android.view.SurfaceHolder surfaceHolder;
   }
 
+  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);
+    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, int[] eglConfigAttribList, 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, 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);
+    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);
+  }
+
+  public final class RendererKt {
+  }
+
   public final class WatchFace {
     ctor public WatchFace(int watchFaceType, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.ComplicationsManager complicationsManager, androidx.wear.watchface.Renderer renderer);
     method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
@@ -244,11 +239,10 @@
   }
 
   public final class WatchState {
-    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
+    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
     method public long getAnalogPreviewReferenceTimeMillis();
     method public long getDigitalPreviewReferenceTimeMillis();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public int getScreenShape();
     method public boolean hasBurnInProtection();
     method public boolean hasLowBitAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
@@ -260,7 +254,6 @@
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible;
-    property public final int screenShape;
   }
 
 }
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index 180e666..6eddf21 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -15,7 +15,7 @@
 
   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 drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
+    method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
     method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
     method @UiThread public boolean isHighlighted();
@@ -30,37 +30,28 @@
     property @UiThread public boolean isHighlighted;
   }
 
-  public abstract class CanvasRenderer extends androidx.wear.watchface.Renderer {
-    ctor public 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);
-    method @UiThread public abstract void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
-  }
-
   public final class Complication {
     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, android.graphics.RectF unitSquareBounds);
+    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 @UiThread public androidx.wear.complications.ComplicationBounds getComplicationBounds();
     method public androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> getComplicationData();
     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 @UiThread public java.util.List<androidx.wear.complications.data.ComplicationType> getSupportedTypes();
-    method @UiThread public android.graphics.RectF getUnitSquareBounds();
     method public void invalidate();
     method public boolean isActiveAt(long dateTimeMillis);
     method @UiThread public boolean isEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method @UiThread public void setDefaultProviderPolicy(androidx.wear.complications.DefaultComplicationProviderPolicy value);
-    method @UiThread public void setDefaultProviderType(androidx.wear.complications.data.ComplicationType value);
-    method @UiThread public void setEnabled(boolean value);
+    method @UiThread public void setComplicationBounds(androidx.wear.complications.ComplicationBounds value);
     method @UiThread public void setRenderer(androidx.wear.watchface.CanvasComplication value);
-    method @UiThread public void setSupportedTypes(java.util.List<? extends androidx.wear.complications.data.ComplicationType> value);
-    method @UiThread public void setUnitSquareBounds(android.graphics.RectF value);
+    property @UiThread public final androidx.wear.complications.ComplicationBounds complicationBounds;
     property public final androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> complicationData;
     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 @UiThread public final androidx.wear.watchface.CanvasComplication renderer;
     property @UiThread public final java.util.List<androidx.wear.complications.data.ComplicationType> supportedTypes;
-    property @UiThread public final android.graphics.RectF unitSquareBounds;
     field public static final androidx.wear.watchface.Complication.Companion Companion;
   }
 
@@ -71,7 +62,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, android.graphics.RectF unitSquareBounds);
+    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);
   }
 
   public final class ComplicationOutlineRenderer {
@@ -111,18 +102,6 @@
     enum_constant public static final androidx.wear.watchface.DrawMode MUTE;
   }
 
-  public abstract class GlesRenderer extends androidx.wear.watchface.Renderer {
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, int[] eglConfigAttribList, int[] eglSurfaceAttribList);
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, int[] eglConfigAttribList);
-    ctor public GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
-    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);
-  }
-
-  public final class GlesRendererKt {
-  }
-
   public final class GlesTextureComplication {
     ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
     method public void bind();
@@ -151,7 +130,6 @@
     method public boolean getHasBurnInProtection();
     method public boolean getHasLowBitAmbient();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public int getScreenShape();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isAmbient();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging();
     method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isVisible();
@@ -160,7 +138,6 @@
     method public void setHasBurnInProtection(boolean p);
     method public void setHasLowBitAmbient(boolean p);
     method public void setInterruptionFilter(androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> p);
-    method public void setScreenShape(int p);
     property public final long analogPreviewReferenceTimeMillis;
     property public final long digitalPreviewReferenceTimeMillis;
     property public final boolean hasBurnInProtection;
@@ -169,14 +146,13 @@
     property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging;
     property public final androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isVisible;
-    property public final int screenShape;
   }
 
   public class ObservableWatchData<T> {
     method @UiThread public final void addObserver(androidx.wear.watchface.Observer<T> observer);
     method @UiThread public T getValue();
-    method public final T getValueOr(T p);
-    method public final boolean hasValue();
+    method @UiThread public final T getValueOr(T p);
+    method @UiThread public final boolean hasValue();
     method @UiThread public final void removeObserver(androidx.wear.watchface.Observer<T> observer);
     method @UiThread protected void setValue(T v);
     property @UiThread public T value;
@@ -204,8 +180,7 @@
   public static final class RenderParameters.Companion {
   }
 
-  public abstract class Renderer {
-    ctor public Renderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+  public abstract sealed class Renderer {
     method public final float getCenterX();
     method public final float getCenterY();
     method public final long getInteractiveDrawModeUpdateDelayMillis();
@@ -227,6 +202,23 @@
     property public final android.view.SurfaceHolder surfaceHolder;
   }
 
+  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);
+    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, int[] eglConfigAttribList, 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, 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);
+    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);
+  }
+
+  public final class RendererKt {
+  }
+
   public final class WatchFace {
     ctor public WatchFace(int watchFaceType, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.ComplicationsManager complicationsManager, androidx.wear.watchface.Renderer renderer);
     method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
@@ -292,11 +284,10 @@
   }
 
   public final class WatchState {
-    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
+    ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
     method public long getAnalogPreviewReferenceTimeMillis();
     method public long getDigitalPreviewReferenceTimeMillis();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
-    method public int getScreenShape();
     method public boolean hasBurnInProtection();
     method public boolean hasLowBitAmbient();
     method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
@@ -308,7 +299,6 @@
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient;
     property public final androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible;
-    property public final int screenShape;
   }
 
 }
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 6cddb4e..1324b1f 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
@@ -26,15 +26,16 @@
 import android.graphics.drawable.Icon
 import android.icu.util.Calendar
 import android.view.SurfaceHolder
+import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
-import androidx.wear.watchface.CanvasRenderer
 import androidx.wear.watchface.CanvasType
 import androidx.wear.watchface.Complication
 import androidx.wear.watchface.ComplicationsManager
 import androidx.wear.watchface.DrawMode
 import androidx.wear.watchface.LayerMode
+import androidx.wear.watchface.Renderer
 import androidx.wear.watchface.WatchFace
 import androidx.wear.watchface.WatchFaceService
 import androidx.wear.watchface.WatchFaceType
@@ -226,7 +227,7 @@
             ComplicationType.SMALL_IMAGE
         ),
         DefaultComplicationProviderPolicy(SystemProviders.DAY_OF_WEEK),
-        RectF(0.2f, 0.4f, 0.4f, 0.6f)
+        ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f))
     ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
         .build()
     val rightComplication = Complication.createRoundRectComplicationBuilder(
@@ -240,7 +241,7 @@
             ComplicationType.SMALL_IMAGE
         ),
         DefaultComplicationProviderPolicy(SystemProviders.STEP_COUNT),
-        RectF(0.6f, 0.4f, 0.8f, 0.6f)
+        ComplicationBounds(RectF(0.6f, 0.4f, 0.8f, 0.6f))
     ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
         .build()
     val complicationsManager = ComplicationsManager(
@@ -276,7 +277,7 @@
     private val drawPipsStyleSetting: BooleanUserStyleSetting,
     private val watchHandLengthStyleSettingDouble: DoubleRangeUserStyleSetting,
     private val complicationsManager: ComplicationsManager
-) : CanvasRenderer(
+) : Renderer.CanvasRenderer(
     surfaceHolder,
     userStyleRepository,
     watchState,
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 7053c99..cedac02 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
@@ -39,15 +39,16 @@
 import android.view.animation.AnimationUtils
 import android.view.animation.PathInterpolator
 import androidx.annotation.ColorInt
+import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
-import androidx.wear.watchface.CanvasRenderer
 import androidx.wear.watchface.CanvasType
 import androidx.wear.watchface.Complication
 import androidx.wear.watchface.ComplicationsManager
 import androidx.wear.watchface.DrawMode
 import androidx.wear.watchface.LayerMode
+import androidx.wear.watchface.Renderer
 import androidx.wear.watchface.WatchFace
 import androidx.wear.watchface.WatchFaceService
 import androidx.wear.watchface.WatchFaceType
@@ -505,9 +506,11 @@
                 ComplicationType.SMALL_IMAGE
             ),
             DefaultComplicationProviderPolicy(SystemProviders.WATCH_BATTERY),
-            createBoundsRect(
-                LEFT_CIRCLE_COMPLICATION_CENTER_FRACTION,
-                CIRCLE_COMPLICATION_DIAMETER_FRACTION
+            ComplicationBounds(
+                createBoundsRect(
+                    LEFT_CIRCLE_COMPLICATION_CENTER_FRACTION,
+                    CIRCLE_COMPLICATION_DIAMETER_FRACTION
+                )
             )
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
@@ -521,43 +524,64 @@
                 ComplicationType.SMALL_IMAGE
             ),
             DefaultComplicationProviderPolicy(SystemProviders.DATE),
-            createBoundsRect(
-                RIGHT_CIRCLE_COMPLICATION_CENTER_FRACTION,
-                CIRCLE_COMPLICATION_DIAMETER_FRACTION
+            ComplicationBounds(
+                createBoundsRect(
+                    RIGHT_CIRCLE_COMPLICATION_CENTER_FRACTION,
+                    CIRCLE_COMPLICATION_DIAMETER_FRACTION
+                )
             )
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
+
+        val upperAndLowerComplicationTypes = listOf(
+            ComplicationType.LONG_TEXT,
+            ComplicationType.RANGED_VALUE,
+            ComplicationType.SHORT_TEXT,
+            ComplicationType.MONOCHROMATIC_IMAGE,
+            ComplicationType.SMALL_IMAGE
+        )
+        // The upper and lower complications change shape depending on the complication's type.
         val upperComplication = Complication.createRoundRectComplicationBuilder(
             ComplicationID.UPPER.ordinal,
             watchFaceStyle.getComplicationDrawableRenderer(this, watchState),
-            listOf(
-                ComplicationType.LONG_TEXT,
-                ComplicationType.RANGED_VALUE,
-                ComplicationType.SHORT_TEXT,
-                ComplicationType.MONOCHROMATIC_IMAGE,
-                ComplicationType.SMALL_IMAGE
-            ),
+            upperAndLowerComplicationTypes,
             DefaultComplicationProviderPolicy(SystemProviders.WORLD_CLOCK),
-            createBoundsRect(
-                UPPER_CIRCLE_COMPLICATION_CENTER_FRACTION,
-                CIRCLE_COMPLICATION_DIAMETER_FRACTION
+            ComplicationBounds(
+                ComplicationType.values().associateWith {
+                    if (it == ComplicationType.LONG_TEXT) {
+                        createBoundsRect(
+                            UPPER_ROUND_RECT_COMPLICATION_CENTER_FRACTION,
+                            ROUND_RECT_COMPLICATION_SIZE_FRACTION
+                        )
+                    } else {
+                        createBoundsRect(
+                            UPPER_CIRCLE_COMPLICATION_CENTER_FRACTION,
+                            CIRCLE_COMPLICATION_DIAMETER_FRACTION
+                        )
+                    }
+                }
             )
         ).setDefaultProviderType(ComplicationType.LONG_TEXT)
             .build()
         val lowerComplication = Complication.createRoundRectComplicationBuilder(
             ComplicationID.LOWER.ordinal,
             watchFaceStyle.getComplicationDrawableRenderer(this, watchState),
-            listOf(
-                ComplicationType.LONG_TEXT,
-                ComplicationType.RANGED_VALUE,
-                ComplicationType.SHORT_TEXT,
-                ComplicationType.MONOCHROMATIC_IMAGE,
-                ComplicationType.SMALL_IMAGE
-            ),
+            upperAndLowerComplicationTypes,
             DefaultComplicationProviderPolicy(SystemProviders.NEXT_EVENT),
-            createBoundsRect(
-                LOWER_CIRCLE_COMPLICATION_CENTER_FRACTION,
-                CIRCLE_COMPLICATION_DIAMETER_FRACTION
+            ComplicationBounds(
+                ComplicationType.values().associateWith {
+                    if (it == ComplicationType.LONG_TEXT) {
+                        createBoundsRect(
+                            LOWER_ROUND_RECT_COMPLICATION_CENTER_FRACTION,
+                            ROUND_RECT_COMPLICATION_SIZE_FRACTION
+                        )
+                    } else {
+                        createBoundsRect(
+                            LOWER_CIRCLE_COMPLICATION_CENTER_FRACTION,
+                            CIRCLE_COMPLICATION_DIAMETER_FRACTION
+                        )
+                    }
+                }
             )
         ).setDefaultProviderType(ComplicationType.LONG_TEXT)
             .build()
@@ -586,36 +610,12 @@
             colorStyleSetting,
             complicationsManager
         )
-
-        // Make the upper and lower complications change shape depending on the complication's type.
         upperComplication.complicationData.addObserver {
-            if (it.type == ComplicationType.LONG_TEXT) {
-                upperComplication.unitSquareBounds = createBoundsRect(
-                    UPPER_ROUND_RECT_COMPLICATION_CENTER_FRACTION,
-                    ROUND_RECT_COMPLICATION_SIZE_FRACTION
-                )
-            } else {
-                upperComplication.unitSquareBounds = createBoundsRect(
-                    UPPER_CIRCLE_COMPLICATION_CENTER_FRACTION,
-                    CIRCLE_COMPLICATION_DIAMETER_FRACTION
-                )
-            }
             // Force bounds recalculation, because this can affect the size of the central time
             // display.
             renderer.oldBounds.set(0, 0, 0, 0)
         }
         lowerComplication.complicationData.addObserver {
-            if (it.type == ComplicationType.LONG_TEXT) {
-                lowerComplication.unitSquareBounds = createBoundsRect(
-                    LOWER_ROUND_RECT_COMPLICATION_CENTER_FRACTION,
-                    ROUND_RECT_COMPLICATION_SIZE_FRACTION
-                )
-            } else {
-                lowerComplication.unitSquareBounds = createBoundsRect(
-                    LOWER_CIRCLE_COMPLICATION_CENTER_FRACTION,
-                    CIRCLE_COMPLICATION_DIAMETER_FRACTION
-                )
-            }
             // Force bounds recalculation, because this can affect the size of the central time
             // display.
             renderer.oldBounds.set(0, 0, 0, 0)
@@ -637,7 +637,7 @@
     private val watchState: WatchState,
     private val colorStyleSetting: UserStyleSetting.ListUserStyleSetting,
     private val complicationsManager: ComplicationsManager
-) : CanvasRenderer(
+) : Renderer.CanvasRenderer(
     surfaceHolder,
     userStyleRepository,
     watchState,
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 458071d..b6f1782 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
@@ -27,15 +27,16 @@
 import android.util.Log
 import android.view.Gravity
 import android.view.SurfaceHolder
+import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
 import androidx.wear.watchface.Complication
 import androidx.wear.watchface.ComplicationsManager
 import androidx.wear.watchface.DrawMode
-import androidx.wear.watchface.GlesRenderer
 import androidx.wear.watchface.GlesTextureComplication
 import androidx.wear.watchface.LayerMode
+import androidx.wear.watchface.Renderer
 import androidx.wear.watchface.WatchFace
 import androidx.wear.watchface.WatchFaceService
 import androidx.wear.watchface.WatchFaceType
@@ -122,7 +123,7 @@
                     ComplicationType.SMALL_IMAGE
                 ),
                 DefaultComplicationProviderPolicy(SystemProviders.DAY_OF_WEEK),
-                RectF(0.2f, 0.7f, 0.4f, 0.9f)
+                ComplicationBounds(RectF(0.2f, 0.7f, 0.4f, 0.9f))
             ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
                 .build()
         ),
@@ -155,7 +156,7 @@
     watchState: WatchState,
     private val colorStyleSetting: ListUserStyleSetting,
     private val complication: Complication
-) : GlesRenderer(surfaceHolder, userStyleRepository, watchState, FRAME_PERIOD_MS) {
+) : Renderer.GlesRenderer(surfaceHolder, userStyleRepository, watchState, FRAME_PERIOD_MS) {
 
     /** Projection transformation matrix. Converts from 3D to 2D.  */
     private val projectionMatrix = FloatArray(16)
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt
index 8da96bb..ab10e84 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/KDocExampleWatchFace.kt
@@ -24,15 +24,16 @@
 import android.icu.util.Calendar
 import android.view.SurfaceHolder
 import androidx.annotation.Sampled
+import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
 import androidx.wear.watchface.complications.rendering.ComplicationDrawable
-import androidx.wear.watchface.CanvasRenderer
 import androidx.wear.watchface.CanvasType
 import androidx.wear.watchface.Complication
 import androidx.wear.watchface.CanvasComplicationDrawable
 import androidx.wear.watchface.ComplicationsManager
+import androidx.wear.watchface.Renderer
 import androidx.wear.watchface.WatchFace
 import androidx.wear.watchface.WatchFaceService
 import androidx.wear.watchface.WatchFaceType
@@ -117,7 +118,7 @@
                             ComplicationType.SMALL_IMAGE
                         ),
                         DefaultComplicationProviderPolicy(SystemProviders.DAY_OF_WEEK),
-                        RectF(0.15625f, 0.1875f, 0.84375f, 0.3125f)
+                        ComplicationBounds(RectF(0.15625f, 0.1875f, 0.84375f, 0.3125f))
                     ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
                         .build(),
                     Complication.createRoundRectComplicationBuilder(
@@ -134,14 +135,14 @@
                             ComplicationType.SMALL_IMAGE
                         ),
                         DefaultComplicationProviderPolicy(SystemProviders.STEP_COUNT),
-                        RectF(0.1f, 0.5625f, 0.35f, 0.8125f)
+                        ComplicationBounds(RectF(0.1f, 0.5625f, 0.35f, 0.8125f))
                     ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
                         .build()
                 ),
                 userStyleRepository
             )
 
-            val renderer = object : CanvasRenderer(
+            val renderer = object : Renderer.CanvasRenderer(
                 surfaceHolder,
                 userStyleRepository,
                 watchState,
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt
new file mode 100644
index 0000000..78f928f
--- /dev/null
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.wear.watchface
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.annotation.UiThread
+import androidx.annotation.VisibleForTesting
+
+/**
+ * All watchface instances share the same [Context] which is a problem for broadcast receivers
+ * because the OS will mistakenly believe we're leaking them if there's more than one instance. So
+ * we need to use this class to share them.
+ */
+internal class BroadcastReceivers private constructor(private val context: Context) {
+
+    interface BroadcastEventObserver {
+        /** Called when we receive Intent.ACTION_TIME_TICK. */
+        @UiThread
+        fun onActionTimeTick()
+
+        /** Called when we receive Intent.ACTION_TIMEZONE_CHANGED. */
+        @UiThread
+        fun onActionTimeZoneChanged()
+
+        /** Called when we receive Intent.ACTION_TIME_CHANGED. */
+        @UiThread
+        fun onActionTimeChanged()
+
+        /** Called when we receive Intent.ACTION_BATTERY_CHANGED. */
+        @UiThread
+        fun onActionBatteryChanged(intent: Intent)
+
+        /** Called when we receive Intent.MOCK_TIME_INTENT. */
+        @UiThread
+        fun onMockTime(intent: Intent)
+    }
+
+    companion object {
+        val broadcastEventObservers = HashSet<BroadcastEventObserver>()
+
+        /* We don't leak due to balanced calls to[addBroadcastEventObserver] and
+        [removeBroadcastEventObserver] which sets this back to null.
+         */
+        @SuppressWarnings("StaticFieldLeak")
+        var broadcastReceivers: BroadcastReceivers? = null
+
+        @UiThread
+        fun addBroadcastEventObserver(context: Context, observer: BroadcastEventObserver) {
+            broadcastEventObservers.add(observer)
+            if (broadcastReceivers == null) {
+                broadcastReceivers = BroadcastReceivers(context)
+            }
+        }
+
+        @UiThread
+        fun removeBroadcastEventObserver(observer: BroadcastEventObserver) {
+            broadcastEventObservers.remove(observer)
+            if (broadcastEventObservers.isEmpty()) {
+                broadcastReceivers!!.onDestroy()
+                broadcastReceivers = null
+            }
+        }
+
+        @VisibleForTesting
+        fun sendOnActionBatteryChangedForTesting(intent: Intent) {
+            require(broadcastEventObservers.isNotEmpty())
+            for (observer in broadcastEventObservers) {
+                observer.onActionBatteryChanged(intent)
+            }
+        }
+
+        @VisibleForTesting
+        fun sendOnMockTimeForTesting(intent: Intent) {
+            require(broadcastEventObservers.isNotEmpty())
+            for (observer in broadcastEventObservers) {
+                observer.onMockTime(intent)
+            }
+        }
+    }
+
+    private val actionTimeTickReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            for (observer in broadcastEventObservers) {
+                observer.onActionTimeTick()
+            }
+        }
+    }
+
+    private val actionTimeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            for (observer in broadcastEventObservers) {
+                observer.onActionTimeZoneChanged()
+            }
+        }
+    }
+
+    private val actionTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent?) {
+            for (observer in broadcastEventObservers) {
+                observer.onActionTimeChanged()
+            }
+        }
+    }
+
+    private val actionBatteryLevelReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            for (observer in broadcastEventObservers) {
+                observer.onActionBatteryChanged(intent)
+            }
+        }
+    }
+
+    private val mockTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            for (observer in broadcastEventObservers) {
+                observer.onMockTime(intent)
+            }
+        }
+    }
+
+    init {
+        context.registerReceiver(actionTimeTickReceiver, IntentFilter(Intent.ACTION_TIME_TICK))
+        context.registerReceiver(
+            actionTimeZoneReceiver,
+            IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
+        )
+        context.registerReceiver(actionTimeReceiver, IntentFilter(Intent.ACTION_TIME_CHANGED))
+        context.registerReceiver(
+            actionBatteryLevelReceiver,
+            IntentFilter(Intent.ACTION_BATTERY_CHANGED)
+        )
+        context.registerReceiver(mockTimeReceiver, IntentFilter(WatchFaceImpl.MOCK_TIME_INTENT))
+    }
+
+    fun onDestroy() {
+        context.unregisterReceiver(actionTimeTickReceiver)
+        context.unregisterReceiver(actionTimeZoneReceiver)
+        context.unregisterReceiver(actionTimeReceiver)
+        context.unregisterReceiver(actionBatteryLevelReceiver)
+        context.unregisterReceiver(mockTimeReceiver)
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/CanvasRenderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/CanvasRenderer.kt
deleted file mode 100644
index 83deda3..0000000
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/CanvasRenderer.kt
+++ /dev/null
@@ -1,136 +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.wear.watchface
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Rect
-import android.icu.util.Calendar
-import android.view.SurfaceHolder
-import androidx.annotation.IntDef
-import androidx.annotation.IntRange
-import androidx.annotation.UiThread
-import androidx.wear.watchface.style.UserStyleRepository
-
-/** @hide */
-@IntDef(
-    value = [
-        CanvasType.SOFTWARE,
-        CanvasType.HARDWARE
-    ]
-)
-public annotation class CanvasType {
-    public companion object {
-        /** A software canvas will be requested. */
-        public const val SOFTWARE: Int = 0
-
-        /**
-         * A hardware canvas will be requested. This is usually faster than software rendering,
-         * however it can sometimes increase battery usage by rendering at a higher frame rate.
-         *
-         * NOTE this is only supported on API level 26 and above. On lower API levels we fall back
-         * to a software canvas.
-         */
-        public const val HARDWARE: Int = 1
-    }
-}
-
-/**
- * Watch faces that require [Canvas] rendering should extend their [Renderer] from this
- * class.
- */
-public abstract class CanvasRenderer(
-    /** The [SurfaceHolder] that [render] will draw into. */
-    surfaceHolder: SurfaceHolder,
-
-    /** The associated [UserStyleRepository]. */
-    userStyleRepository: UserStyleRepository,
-
-    /** The associated [WatchState]. */
-    watchState: WatchState,
-
-    /** The type of canvas to use. */
-    @CanvasType private val canvasType: Int,
-
-    /**
-     * The interval in milliseconds between frames in interactive [DrawMode]s. To render at 60hz
-     * set to 16. Note when battery is low, the frame rate will be clamped to 10fps. Watch faces are
-     * recommended to use lower frame rates if possible for better battery life. Variable frame
-     * rates can also help preserve battery life, e.g. if a watch face has a short animation once
-     * per second it can adjust the frame rate inorder to sleep when not animating.
-     */
-    @IntRange(from = 0, to = 10000)
-    interactiveDrawModeUpdateDelayMillis: Long
-) : Renderer(surfaceHolder, userStyleRepository, watchState, interactiveDrawModeUpdateDelayMillis) {
-
-    @SuppressWarnings("UnsafeNewApiCall") // We check if the SDK is new enough.
-    internal override fun renderInternal(
-        calendar: Calendar
-    ) {
-        val canvas = (
-            if (canvasType == CanvasType.HARDWARE && android.os.Build.VERSION.SDK_INT >= 26) {
-                surfaceHolder.lockHardwareCanvas() // Requires API level 26.
-            } else {
-                surfaceHolder.lockCanvas()
-            }
-            ) ?: return
-        try {
-            if (watchState.isVisible.value) {
-                render(canvas, surfaceHolder.surfaceFrame, calendar)
-            } else {
-                canvas.drawColor(Color.BLACK)
-            }
-        } finally {
-            surfaceHolder.unlockCanvasAndPost(canvas)
-        }
-    }
-
-    /** {@inheritDoc} */
-    internal override fun takeScreenshot(
-        calendar: Calendar,
-        renderParameters: RenderParameters
-    ): Bitmap {
-        val bitmap = Bitmap.createBitmap(
-            screenBounds.width(),
-            screenBounds.height(),
-            Bitmap.Config.ARGB_8888
-        )
-        val prevRenderParameters = this.renderParameters
-        this.renderParameters = renderParameters
-        render(Canvas(bitmap), screenBounds, calendar)
-        this.renderParameters = prevRenderParameters
-        return bitmap
-    }
-
-    /**
-     * Sub-classes should override this to implement their rendering logic which should respect
-     * the current [DrawMode]. For correct functioning watch faces must use the supplied
-     * [Calendar] and avoid using any other ways of getting the time.
-     *
-     * @param canvas The [Canvas] to render into. Don't assume this is always the canvas from
-     *     the [SurfaceHolder] backing the display
-     * @param bounds A [Rect] describing the bonds of the canvas to draw into
-     * @param calendar The current [Calendar]
-     */
-    @UiThread
-    public abstract fun render(
-        canvas: Canvas,
-        bounds: Rect,
-        calendar: Calendar
-    )
-}
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 b973fc0..8454576 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
@@ -24,32 +24,38 @@
 import android.icu.util.Calendar
 import android.support.wearable.complications.ComplicationData
 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.watchface.complications.rendering.ComplicationDrawable
 import androidx.wear.watchface.data.ComplicationBoundsType
 import androidx.wear.watchface.style.Layer
+import androidx.wear.watchface.style.UserStyleSetting
 
-/** Common interface for rendering complications onto a [Canvas]. */
+/** Interface for rendering complications onto a [Canvas]. */
 public interface CanvasComplication {
     /**
-     * Called when the CanvasComplication attaches to a [Complication].
+     * Called when the CanvasComplication attaches to a [Complication]. This will get called during
+     * [Complication] initialization and if [Complication.renderer] is assigned with this
+     * CanvasComplication.
      */
     @UiThread
     public fun onAttach(complication: Complication)
 
     /**
-     * Called when the CanvasComplication detaches from a [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 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.
+     * Draws the complication defined by [idAndData] 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.
      *
      * @param canvas The [Canvas] to render into
      * @param bounds A [Rect] describing the bounds of the complication
@@ -65,8 +71,8 @@
     )
 
     /**
-     * Whether the complication should be drawn highlighted. This is to provide visual
-     * feedback when the user taps on a complication.
+     * Whether the complication should be drawn highlighted. This is to provide visual feedback when
+     * the user taps on a complication.
      */
     @Suppress("INAPPLICABLE_JVM_NAME") // https://stackoverflow.com/questions/47504279
     @get:JvmName("isHighlighted")
@@ -78,13 +84,17 @@
 }
 
 /**
- * A complication rendered with [ComplicationDrawable] which renders complications in a
- * material design style. This renderer can't be shared by multiple complications.
+ * A complication rendered with [ComplicationDrawable] which renders complications in a material
+ * design style. This renderer can't be shared by multiple complications.
  */
 public open class CanvasComplicationDrawable(
     /** The [ComplicationDrawable] to render with. */
     drawable: ComplicationDrawable,
 
+    /**
+     * The watch's [WatchState] which contains details pertaining to (low-bit) ambient mode and
+     * burn in protection needed to render correctly.
+     */
     private val watchState: WatchState
 ) : CanvasComplication {
 
@@ -153,15 +163,15 @@
                 if (renderParameters.highlightedComplicationId == null ||
                     renderParameters.highlightedComplicationId == idAndData?.complicationId
                 ) {
-                    drawHighlight(canvas, bounds, calendar)
+                    drawOutline(canvas, bounds, calendar)
                 }
             }
             LayerMode.HIDE -> return
         }
     }
 
-    /** Used (indirectly) by the editor, draws a highlight around the complication. */
-    public open fun drawHighlight(
+    /** Used (indirectly) by the editor, draws a dashed line around the complication. */
+    public open fun drawOutline(
         canvas: Canvas,
         bounds: Rect,
         calendar: Calendar
@@ -170,8 +180,8 @@
     }
 
     /**
-     * Whether or not the complication should be drawn highlighted. Used to provide visual
-     * feedback when the complication is tapped.
+     * Whether or not the complication should be drawn highlighted. Used to provide visual feedback
+     * when the complication is tapped.
      */
     override var isHighlighted: Boolean
         @Suppress("INAPPLICABLE_JVM_NAME") // https://stackoverflow.com/questions/47504279
@@ -185,9 +195,7 @@
             drawable.isHighlighted = value
         }
 
-    /**
-     * The [IdAndComplicationData] to use when rendering the complication.
-     */
+    /** The [IdAndComplicationData] to use when rendering the complication. */
     override var idAndData: IdAndComplicationData? = null
         @UiThread
         set(value) {
@@ -198,12 +206,13 @@
 
 /**
  * Represents a individual complication on the screen. The number of complications is fixed
- * (see [ComplicationsManager]) but complications can be enabled or disabled as needed.
+ * (see [ComplicationsManager]) but complications can be enabled or disabled via
+ * [UserStyleSetting.ComplicationsUserStyleSetting].
  */
 public class Complication internal constructor(
     internal val id: Int,
     @ComplicationBoundsType internal val boundsType: Int,
-    unitSquareBounds: RectF,
+    complicationBounds: ComplicationBounds,
     canvasComplication: CanvasComplication,
     supportedTypes: List<ComplicationType>,
     defaultProviderPolicy: DefaultComplicationProviderPolicy,
@@ -220,44 +229,41 @@
          */
         @JvmStatic
         public fun createRoundRectComplicationBuilder(
-            /** The watch face's ID for this complication. */
+            /**
+             * The watch face's ID for this complication. Can be any integer but should be unique
+             * within the watch face.
+             */
             id: Int,
 
             /**
-             * The renderer for this Complication. Renderers may not be sharable between complications.
+             * The [CanvasComplication] to use for rendering. Note renderers should not be shared
+             * between complications.
              */
             renderer: CanvasComplication,
 
             /**
              * The types of complication supported by this Complication. Passed into
              * [ComplicationHelperActivity.createProviderChooserHelperIntent] during complication
-             * configuration.
+             * configuration. This list should be non-empty.
              */
             supportedTypes: List<ComplicationType>,
 
-            /** The [DefaultComplicationProviderPolicy] to use. */
+            /**
+             * The [DefaultComplicationProviderPolicy] used to select the initial complication
+             * provider.
+             */
             defaultProviderPolicy: DefaultComplicationProviderPolicy,
 
-            /**
-             * The fractional bounds for the complication which are clamped to the unit square
-             * [0..1], and subsequently converted to screen space coordinates. NB 0 and 1 are
-             * included in the unit square.
-             */
-            unitSquareBounds: RectF
+            /** The initial [ComplicationBounds]. */
+            complicationBounds: ComplicationBounds
         ): Builder = Builder(
             id,
             renderer,
             supportedTypes,
             defaultProviderPolicy,
             ComplicationBoundsType.ROUND_RECT,
-            RectF().apply {
-                setIntersect(
-                    unitSquareBounds,
-                    unitSquare
-                )
-            }
+            complicationBounds
         )
-
         /**
          * Constructs a [Builder] for a complication with bound type
          * [ComplicationBoundsType.BACKGROUND] whose bounds cover the entire screen. A background
@@ -267,22 +273,29 @@
          */
         @JvmStatic
         public fun createBackgroundComplicationBuilder(
-            /** The watch face's ID for this complication. */
+            /**
+             * The watch face's ID for this complication. Can be any integer but should be unique
+             * within the watch face.
+             */
             id: Int,
 
             /**
-             * The renderer for this Complication. Renderers may not be sharable between complications.
+             * The [CanvasComplication] to use for rendering. Note renderers should not be shared
+             * between complications.
              */
             renderer: CanvasComplication,
 
             /**
              * The types of complication supported by this Complication. Passed into
              * [ComplicationHelperActivity.createProviderChooserHelperIntent] during complication
-             * configuration.
+             * configuration. This list should be non-empty.
              */
             supportedTypes: List<ComplicationType>,
 
-            /** The [DefaultComplicationProviderPolicy] to use. */
+            /**
+             * The [DefaultComplicationProviderPolicy] used to select the initial complication
+             * provider.
+             */
             defaultProviderPolicy: DefaultComplicationProviderPolicy
         ): Builder = Builder(
             id,
@@ -290,7 +303,7 @@
             supportedTypes,
             defaultProviderPolicy,
             ComplicationBoundsType.BACKGROUND,
-            RectF(0f, 0f, 1f, 1f)
+            ComplicationBounds(RectF(0f, 0f, 1f, 1f))
         )
     }
 
@@ -301,12 +314,14 @@
         private val supportedTypes: List<ComplicationType>,
         private val defaultProviderPolicy: DefaultComplicationProviderPolicy,
         @ComplicationBoundsType private val boundsType: Int,
-        private val unitSquareBounds: RectF
+        private val complicationBounds: ComplicationBounds
     ) {
         private var defaultProviderType = ComplicationType.NOT_CONFIGURED
 
         /**
-         * Sets the default complication provider data type.
+         * Sets the initial [ComplicationType] to use with the initial complication provider.
+         * Note care should be taken to ensure [defaultProviderType] is compatible with the
+         * [DefaultComplicationProviderPolicy].
          */
         public fun setDefaultProviderType(
             defaultProviderType: ComplicationType
@@ -319,7 +334,7 @@
         public fun build(): Complication = Complication(
             id,
             boundsType,
-            unitSquareBounds,
+            complicationBounds,
             renderer,
             supportedTypes,
             defaultProviderPolicy,
@@ -340,22 +355,25 @@
     private lateinit var complicationsManager: ComplicationsManager
     private lateinit var invalidateListener: InvalidateListener
 
-    internal var unitSquareBoundsDirty = true
+    internal var complicationBoundsDirty = true
 
     /**
-     * The screen space unit-square bounds of the complication. This is converted to pixels during
-     * rendering.
+     * The complication's [ComplicationBounds] which are converted to pixels during rendering.
+     *
+     * 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 unitSquareBounds: RectF = unitSquareBounds
+    public var complicationBounds: ComplicationBounds = complicationBounds
         @UiThread
         get
         @UiThread
         set(value) {
+            require(boundsType != ComplicationBoundsType.BACKGROUND)
             if (field == value) {
                 return
             }
             field = value
-            unitSquareBoundsDirty = true
+            complicationBoundsDirty = true
 
             // The caller might modify a number of complications. For efficiency we need to coalesce
             // these into one update task.
@@ -364,15 +382,13 @@
 
     internal var enabledDirty = true
 
-    /**
-     * Whether or not the complication should be drawn and accept taps.
-     */
+    /** Whether or not the complication should be drawn and accept taps. */
     public var enabled: Boolean = true
         @JvmName("isEnabled")
         @UiThread
         get
         @UiThread
-        set(value) {
+        internal set(value) {
             if (field == value) {
                 return
             }
@@ -386,9 +402,7 @@
             }
         }
 
-    /**
-     * The [CanvasComplication] used to render the complication.
-     */
+    /** The [CanvasComplication] used to render the complication. */
     public var renderer: CanvasComplication = canvasComplication
         @UiThread
         get
@@ -405,17 +419,16 @@
 
     internal var supportedTypesDirty = true
 
-    /**
-     * The types of complications the complication supports.
-     */
+    /** The types of complications the complication supports. Must be non-empty. */
     public var supportedTypes: List<ComplicationType> = supportedTypes
         @UiThread
         get
         @UiThread
-        set(value) {
+        internal set(value) {
             if (field == value) {
                 return
             }
+            require(value.isNotEmpty())
             field = value
             supportedTypesDirty = true
 
@@ -430,13 +443,13 @@
 
     /**
      * The [DefaultComplicationProviderPolicy] which defines the default complications providers
-     * selected when the user hasn't yet made a choice. See also [.defaultProviderType].
+     * selected when the user hasn't yet made a choice. See also [defaultProviderType].
      */
     public var defaultProviderPolicy: DefaultComplicationProviderPolicy = defaultProviderPolicy
         @UiThread
         get
         @UiThread
-        set(value) {
+        internal set(value) {
             if (field == value) {
                 return
             }
@@ -453,13 +466,13 @@
     internal var defaultProviderTypeDirty = true
 
     /**
-     * The default [ComplicationData.ComplicationType] to use alongside [.defaultProviderPolicy].
+     * The default [ComplicationData.ComplicationType] to use alongside [defaultProviderPolicy].
      */
     public var defaultProviderType: ComplicationType = defaultProviderType
         @UiThread
         get
         @UiThread
-        set(value) {
+        internal set(value) {
             if (field == value) {
                 return
             }
@@ -550,11 +563,19 @@
     }
 
     /** Computes the bounds of the complication by converting the unitSquareBounds to pixels. */
-    internal fun computeBounds(screen: Rect) =
-        Rect(
+    internal fun computeBounds(screen: Rect): Rect {
+        // Try the current type if there is one, otherwise fall back to the bounds for the default
+        // provider type.
+        val unitSquareBounds =
+            renderer.idAndData?.let {
+                complicationBounds.perComplicationTypeBounds[it.complicationData.type]
+            } ?: complicationBounds.perComplicationTypeBounds[defaultProviderType]!!
+        unitSquareBounds.intersect(unitSquare)
+        return Rect(
             (unitSquareBounds.left * screen.width()).toInt(),
             (unitSquareBounds.top * screen.height()).toInt(),
             (unitSquareBounds.right * screen.width()).toInt(),
             (unitSquareBounds.bottom * screen.height()).toInt()
         )
+    }
 }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt
index 57699ba..4d5bb5d 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt
@@ -30,9 +30,9 @@
 public class ComplicationOutlineRenderer {
     public companion object {
         // Dashed lines are used for complication selection.
-        internal val DASH_WIDTH = 10.0f
-        internal var DASH_GAP = 2.0f
-        internal var DASH_LENGTH = 5.0f
+        internal const val DASH_WIDTH = 10.0f
+        internal const val DASH_GAP = 2.0f
+        internal const val DASH_LENGTH = 5.0f
 
         internal val dashPaint = Paint().apply {
             strokeWidth = DASH_WIDTH
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 6061ca2..7dacf5c 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
@@ -20,12 +20,12 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.graphics.RectF
 import android.icu.util.Calendar
 import android.support.wearable.watchface.accessibility.AccessibilityUtils
 import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
 import androidx.annotation.UiThread
 import androidx.annotation.VisibleForTesting
+import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.ComplicationHelperActivity
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.data.ComplicationData
@@ -43,14 +43,11 @@
 )
 
 /**
- * The [Complication]s associated with the [WatchFace]. Dynamic creation of
- * complications isn't supported, however complications can be enabled and disabled, perhaps as
- * part of a user style see [androidx.wear.watchface.style.UserStyleSetting].
+ * The [Complication]s associated with the [WatchFace]. Dynamic creation of complications isn't
+ * supported, however complications can be enabled and disabled by [ComplicationsUserStyleSetting].
  */
 public class ComplicationsManager(
-    /**
-     * The complications associated with the watch face, may be empty.
-     */
+    /** The complications associated with the watch face, may be empty. */
     complicationCollection: Collection<Complication>,
 
     /**
@@ -59,6 +56,10 @@
      */
     private val userStyleRepository: UserStyleRepository
 ) {
+    /**
+     * Interface used to report user taps on the complication. See [addTapListener] and
+     * [removeTapListener].
+     */
     public interface TapCallback {
         /**
          * Called when the user single taps on a complication.
@@ -81,13 +82,12 @@
     private lateinit var renderer: Renderer
     private lateinit var pendingUpdate: CancellableUniqueTask
 
-    // A map of IDs to complications.
+    /** A map of complication IDs to complications. */
     public val complications: Map<Int, Complication> =
         complicationCollection.associateBy(Complication::id)
 
     private class InitialComplicationConfig(
-        val id: Int,
-        val unitSquareBounds: RectF,
+        val complicationBounds: ComplicationBounds,
         val enabled: Boolean,
         val supportedTypes: List<ComplicationType>,
         val defaultProviderPolicy: DefaultComplicationProviderPolicy,
@@ -102,8 +102,7 @@
             { it.id },
             {
                 InitialComplicationConfig(
-                    it.id,
-                    it.unitSquareBounds,
+                    it.complicationBounds,
                     it.enabled,
                     it.supportedTypes,
                     it.defaultProviderPolicy,
@@ -175,8 +174,8 @@
             val override = styleOption.complicationOverlays.find { it.complicationId == id }
             val initialConfig = initialComplicationConfigs[id]!!
             // Apply styleOption overrides.
-            complication.unitSquareBounds =
-                override?.bounds ?: initialConfig.unitSquareBounds
+            complication.complicationBounds =
+                override?.complicationBounds ?: initialConfig.complicationBounds
             complication.enabled =
                 override?.enabled ?: initialConfig.enabled
             complication.supportedTypes =
@@ -188,7 +187,7 @@
         }
     }
 
-    /** Returns the [Complication] corresponding to id or null. */
+    /** Returns the [Complication] corresponding to [id], if there is one, or `null`. */
     public operator fun get(id: Int): Complication? = complications[id]
 
     internal fun scheduleUpdate() {
@@ -246,7 +245,8 @@
                 activeKeys.add(id)
 
                 labelsDirty =
-                    labelsDirty || complication.dataDirty || complication.unitSquareBoundsDirty
+                    labelsDirty || complication.dataDirty ||
+                    complication.complicationBoundsDirty
 
                 if (complication.defaultProviderPolicyDirty ||
                     complication.defaultProviderTypeDirty
@@ -260,7 +260,7 @@
                 }
 
                 complication.dataDirty = false
-                complication.unitSquareBoundsDirty = false
+                complication.complicationBoundsDirty = false
                 complication.supportedTypesDirty = false
                 complication.defaultProviderPolicyDirty = false
                 complication.defaultProviderTypeDirty = false
@@ -299,8 +299,8 @@
     }
 
     /**
-     * Brings attention to the complication by briefly highlighting it to provide visual
-     * feedback when the user has tapped on it.
+     * Brings attention to the complication by briefly highlighting it to provide visual feedback
+     * when the user has tapped on it.
      *
      * @param complicationId The watch face's ID of the complication to briefly highlight
      */
@@ -324,7 +324,7 @@
     }
 
     /**
-     * Returns the id of the complication at coordinates x, y or {@code null} if there isn't one.
+     * Returns the id of the complication at coordinates x, y or `null` if there isn't one.
      *
      * @param x The x coordinate of the point to perform a hit test
      * @param y The y coordinate of the point to perform a hit test
@@ -337,9 +337,9 @@
         }?.value
 
     /**
-     * Returns the background complication if there is one or {@code null} otherwise.
+     * Returns the background complication if there is one or `null` otherwise.
      *
-     * @return The background complication if there is one or {@code null} otherwise
+     * @return The background complication if there is one or `null` otherwise
      */
     public fun getBackgroundComplication(): Complication? =
         complications.entries.firstOrNull {
@@ -410,8 +410,7 @@
     }
 
     /**
-     * Adds a [TapCallback] which is called whenever the user interacts with a
-     * complication.
+     * Adds a [TapCallback] which is called whenever the user interacts with a complication.
      */
     @UiThread
     @SuppressLint("ExecutorRegistration")
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/GlesRenderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/GlesRenderer.kt
deleted file mode 100644
index 8e7cde4..0000000
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/GlesRenderer.kt
+++ /dev/null
@@ -1,312 +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.wear.watchface
-
-import android.annotation.SuppressLint
-import android.graphics.Bitmap
-import android.icu.util.Calendar
-import android.opengl.EGL14
-import android.opengl.EGLConfig
-import android.opengl.EGLContext
-import android.opengl.EGLDisplay
-import android.opengl.EGLSurface
-import android.opengl.GLES20
-import android.util.Log
-import android.view.SurfaceHolder
-import androidx.annotation.CallSuper
-import androidx.annotation.IntRange
-import androidx.annotation.UiThread
-import androidx.wear.watchface.style.UserStyleRepository
-
-import java.nio.ByteBuffer
-
-internal val EGL_CONFIG_ATTRIB_LIST = intArrayOf(
-    EGL14.EGL_RENDERABLE_TYPE,
-    EGL14.EGL_OPENGL_ES2_BIT,
-    EGL14.EGL_RED_SIZE,
-    8,
-    EGL14.EGL_GREEN_SIZE,
-    8,
-    EGL14.EGL_BLUE_SIZE,
-    8,
-    EGL14.EGL_ALPHA_SIZE,
-    8,
-    EGL14.EGL_NONE
-)
-
-private val EGL_CONTEXT_ATTRIB_LIST =
-    intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
-
-internal val EGL_SURFACE_ATTRIB_LIST = intArrayOf(EGL14.EGL_NONE)
-
-/**
- * Watch faces that require [GLES20] rendering should extend their [Renderer] from this
- * class.
- */
-public abstract class GlesRenderer @JvmOverloads constructor(
-    /** The [SurfaceHolder] that [render] will draw into. */
-    surfaceHolder: SurfaceHolder,
-
-    /** The associated [UserStyleRepository]. */
-    userStyleRepository: UserStyleRepository,
-
-    /** The associated [WatchState]. */
-    watchState: WatchState,
-
-    /**
-     * The interval in milliseconds between frames in interactive [DrawMode]s. To render at 60hz
-     * set to 16. Note when battery is low, the frame rate will be clamped to 10fps. Watch faces are
-     * recommended to use lower frame rates if possible for better battery life. Variable frame
-     * rates can also help preserve battery life, e.g. if a watch face has a short animation once
-     * per second it can adjust the frame rate inorder to sleep when not animating.
-     */
-    @IntRange(from = 0, to = 10000)
-    interactiveDrawModeUpdateDelayMillis: Long,
-
-    /** Attributes for [EGL14.eglChooseConfig]. By default this selects an RGBAB8888 back buffer. */
-    private val eglConfigAttribList: IntArray = EGL_CONFIG_ATTRIB_LIST,
-
-    /** The attributes to be passed to [EGL14.eglCreateWindowSurface]. By default this is empty. */
-    private val eglSurfaceAttribList: IntArray = EGL_SURFACE_ATTRIB_LIST
-) : Renderer(surfaceHolder, userStyleRepository, watchState, interactiveDrawModeUpdateDelayMillis) {
-    /** @hide */
-    private companion object {
-        private const val TAG = "Gles2WatchFace"
-    }
-
-    private var eglDisplay: EGLDisplay? = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY).apply {
-        if (this == EGL14.EGL_NO_DISPLAY) {
-            throw RuntimeException("eglGetDisplay returned EGL_NO_DISPLAY")
-        }
-        // Initialize the display. The major and minor version numbers are passed back.
-        val version = IntArray(2)
-        if (!EGL14.eglInitialize(this, version, 0, version, 1)) {
-            throw RuntimeException("eglInitialize failed")
-        }
-    }
-
-    private var eglConfig: EGLConfig = chooseEglConfig(eglDisplay!!)
-
-    @SuppressWarnings("SyntheticAccessor")
-    private var eglContext: EGLContext? = EGL14.eglCreateContext(
-        eglDisplay,
-        eglConfig,
-        EGL14.EGL_NO_CONTEXT,
-        EGL_CONTEXT_ATTRIB_LIST,
-        0
-    )
-
-    init {
-        if (eglContext == EGL14.EGL_NO_CONTEXT) {
-            throw RuntimeException("eglCreateContext failed")
-        }
-    }
-
-    private var eglSurface: EGLSurface? = null
-    private var calledOnGlContextCreated = false
-
-    /**
-     * Chooses the EGLConfig to use.
-     * @throws RuntimeException if [EGL14.eglChooseConfig] fails
-     */
-    private fun chooseEglConfig(eglDisplay: EGLDisplay): EGLConfig {
-        val numEglConfigs = IntArray(1)
-        val eglConfigs = arrayOfNulls<EGLConfig>(1)
-        if (!EGL14.eglChooseConfig(
-                eglDisplay,
-                eglConfigAttribList,
-                0,
-                eglConfigs,
-                0,
-                eglConfigs.size,
-                numEglConfigs,
-                0
-            )
-        ) {
-            throw RuntimeException("eglChooseConfig failed")
-        }
-        if (numEglConfigs[0] == 0) {
-            throw RuntimeException("no matching EGL configs")
-        }
-        return eglConfigs[0]!!
-    }
-
-    private fun createWindowSurface(width: Int, height: Int) {
-        if (eglSurface != null) {
-            if (!EGL14.eglDestroySurface(eglDisplay, eglSurface)) {
-                Log.w(TAG, "eglDestroySurface failed")
-            }
-        }
-        eglSurface = EGL14.eglCreateWindowSurface(
-            eglDisplay,
-            eglConfig,
-            surfaceHolder.surface,
-            eglSurfaceAttribList,
-            0
-        )
-        if (eglSurface == EGL14.EGL_NO_SURFACE) {
-            throw RuntimeException("eglCreateWindowSurface failed")
-        }
-
-        makeContextCurrent()
-        GLES20.glViewport(0, 0, width, height)
-        if (!calledOnGlContextCreated) {
-            calledOnGlContextCreated = true
-            onGlContextCreated()
-        }
-        onGlSurfaceCreated(width, height)
-    }
-
-    @CallSuper
-    override fun onDestroy() {
-        if (eglSurface != null) {
-            if (!EGL14.eglDestroySurface(eglDisplay, eglSurface)) {
-                Log.w(TAG, "eglDestroySurface failed")
-            }
-            eglSurface = null
-        }
-        if (eglContext != null) {
-            if (!EGL14.eglDestroyContext(eglDisplay, eglContext)) {
-                Log.w(TAG, "eglDestroyContext failed")
-            }
-            eglContext = null
-        }
-        if (eglDisplay != null) {
-            if (!EGL14.eglTerminate(eglDisplay)) {
-                Log.w(TAG, "eglTerminate failed")
-            }
-            eglDisplay = null
-        }
-    }
-
-    /**
-     * Sets our GL context to be the current one. This method *must* be called before any
-     * OpenGL APIs are used.
-     */
-    private fun makeContextCurrent() {
-        if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
-            throw RuntimeException("eglMakeCurrent failed")
-        }
-    }
-
-    internal override fun onPostCreate() {
-        surfaceHolder.addCallback(object : SurfaceHolder.Callback {
-            @SuppressLint("SyntheticAccessor")
-            override fun surfaceChanged(
-                holder: SurfaceHolder,
-                format: Int,
-                width: Int,
-                height: Int
-            ) {
-                createWindowSurface(width, height)
-            }
-
-            @SuppressLint("SyntheticAccessor")
-            override fun surfaceDestroyed(holder: SurfaceHolder) {
-                if (!EGL14.eglDestroySurface(eglDisplay, eglSurface)) {
-                    Log.w(TAG, "eglDestroySurface failed")
-                }
-                eglSurface = null
-            }
-
-            override fun surfaceCreated(holder: SurfaceHolder) {
-            }
-        })
-
-        createWindowSurface(
-            surfaceHolder.surfaceFrame.width(),
-            surfaceHolder.surfaceFrame.height()
-        )
-    }
-
-    /** Called when a new GL context is created. It's safe to use GL APIs in this method. */
-    @UiThread
-    public open fun onGlContextCreated() {}
-
-    /**
-     * Called when a new GL surface is created. It's safe to use GL APIs in this method.
-     *
-     * @param width width of surface in pixels
-     * @param height height of surface in pixels
-     */
-    @UiThread
-    public open fun onGlSurfaceCreated(width: Int, height: Int) {}
-
-    internal override fun renderInternal(
-        calendar: Calendar
-    ) {
-        makeContextCurrent()
-        render(calendar)
-        if (!EGL14.eglSwapBuffers(eglDisplay, eglSurface)) {
-            Log.w(TAG, "eglSwapBuffers failed")
-        }
-    }
-
-    /** {@inheritDoc} */
-    internal override fun takeScreenshot(
-        calendar: Calendar,
-        renderParameters: RenderParameters
-    ): Bitmap {
-        val width = screenBounds.width()
-        val height = screenBounds.height()
-        val pixelBuf = ByteBuffer.allocateDirect(width * height * 4)
-        makeContextCurrent()
-        val prevRenderParameters = this.renderParameters
-        this.renderParameters = renderParameters
-        render(calendar)
-        this.renderParameters = prevRenderParameters
-        GLES20.glFinish()
-        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuf)
-        // The image is flipped when using read pixels because the first pixel in the OpenGL buffer
-        // is in bottom left.
-        verticalFlip(pixelBuf, width, height)
-        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
-        bitmap.copyPixelsFromBuffer(pixelBuf)
-        return bitmap
-    }
-
-    private fun verticalFlip(
-        buffer: ByteBuffer,
-        width: Int,
-        height: Int
-    ) {
-        var i = 0
-        val tmp = ByteArray(width * 4)
-        while (i++ < height / 2) {
-            buffer[tmp]
-            System.arraycopy(
-                buffer.array(),
-                buffer.limit() - buffer.position(),
-                buffer.array(),
-                buffer.position() - width * 4,
-                width * 4
-            )
-            System.arraycopy(tmp, 0, buffer.array(), buffer.limit() - buffer.position(), width * 4)
-        }
-        buffer.rewind()
-    }
-
-    /**
-     * Sub-classes should override this to implement their rendering logic which should respect
-     * the current [DrawMode]. For correct functioning watch faces must use the supplied
-     * [Calendar] and avoid using any other ways of getting the time.
-     *
-     * @param calendar The current [Calendar]
-     */
-    @UiThread
-    public abstract fun render(calendar: Calendar)
-}
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 1a8cf62..4caca47 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
@@ -25,7 +25,10 @@
 import android.opengl.GLUtils
 import androidx.annotation.Px
 
-/** Helper for rendering a complication to a GLES20 texture. */
+/**
+ * Helper for rendering a [CanvasComplication] to a GLES20 texture. To use call [renderToTexture]
+ * and then [bind] before drawing.
+ */
 public class GlesTextureComplication(
     /** The [CanvasComplication] to render to texture. */
     public val canvasComplication: CanvasComplication,
@@ -50,7 +53,7 @@
     private val canvas = Canvas(bitmap)
     private val bounds = Rect(0, 0, textureWidth, textureHeight)
 
-    /** Renders the complication to an OpenGL texture. */
+    /** Renders [canvasComplication] to an OpenGL texture. */
     public fun renderToTexture(calendar: Calendar, renderParameters: RenderParameters) {
         canvas.drawColor(Color.BLACK)
         canvasComplication.render(canvas, bounds, calendar, renderParameters)
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt
index d7f7ada..91e83df 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt
@@ -19,9 +19,9 @@
 import androidx.annotation.UiThread
 
 /**
- * An observable UI thread only data holder class.
+ * An observable UI thread only data holder class (see [Observer]).
  *
- * @param <T> The type of data hold by this instance
+ * @param T The type of data held by this instance
  */
 public open class ObservableWatchData<T : Any> internal constructor(internal var _value: T?) {
 
@@ -30,11 +30,13 @@
     private val toBeRemoved = HashSet<Observer<T>>()
 
     /** Whether or not this ObservableWatchData contains a value. */
+    @UiThread
     public fun hasValue(): Boolean = _value != null
 
     /**
      * Returns the value contained within this ObservableWatchData or default if there isn't one.
      */
+    @UiThread
     public fun getValueOr(default: T): T = if (_value != null) {
         _value!!
     } else {
@@ -67,9 +69,9 @@
         }
 
     /**
-     * Adds the given observer to the observers list. The events are dispatched on the ui thread.
-     * If there's any data held within the ObservableWatchData it will be immediately delivered to
-     * the observer.
+     * Adds the given [Observer] to the observers list. If [hasValue] would return true then
+     * [Observer.onChanged] will be called. Subsequently [Observer.onChanged] will also be called
+     * any time [value] changes. All of these callbacks are assumed to occur on the UI thread.
      */
     @UiThread
     public fun addObserver(observer: Observer<T>) {
@@ -98,7 +100,7 @@
 /**
  * [ObservableWatchData] which publicly exposes [setValue(T)] method.
  *
- * @param <T> The type of data hold by this instance
+ * @param T The type of data held by this instance
  */
 public class MutableObservableWatchData<T : Any>(initialValue: T?) :
     ObservableWatchData<T>(initialValue) {
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 de8d660..9c62c0d 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
@@ -16,17 +16,79 @@
 
 package androidx.wear.watchface
 
+import android.annotation.SuppressLint
+import android.content.res.Configuration
 import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
 import android.graphics.Rect
 import android.icu.util.Calendar
+import android.opengl.EGL14
+import android.opengl.EGLConfig
+import android.opengl.EGLContext
+import android.opengl.EGLDisplay
+import android.opengl.EGLSurface
+import android.opengl.GLES20
+import android.util.Log
 import android.view.SurfaceHolder
+import androidx.annotation.CallSuper
+import androidx.annotation.IntDef
 import androidx.annotation.IntRange
 import androidx.annotation.Px
 import androidx.annotation.UiThread
+import androidx.wear.watchface.Renderer.CanvasRenderer
+import androidx.wear.watchface.Renderer.GlesRenderer
 import androidx.wear.watchface.style.UserStyleRepository
+import java.nio.ByteBuffer
+
+/**
+ * Describes the type of [Canvas] a [CanvasRenderer] should request from a [SurfaceHolder].
+ *
+ * @hide
+ */
+@IntDef(
+    value = [
+        CanvasType.SOFTWARE,
+        CanvasType.HARDWARE
+    ]
+)
+public annotation class CanvasType {
+    public companion object {
+        /** A software canvas will be requested. */
+        public const val SOFTWARE: Int = 0
+
+        /**
+         * A hardware canvas will be requested. This is usually faster than software rendering,
+         * however it can sometimes increase battery usage by rendering at a higher frame rate.
+         *
+         * NOTE this is only supported on API level 26 and above. On lower API levels we fall back
+         * to a software canvas.
+         */
+        public const val HARDWARE: Int = 1
+    }
+}
+
+internal val EGL_CONFIG_ATTRIB_LIST = intArrayOf(
+    EGL14.EGL_RENDERABLE_TYPE,
+    EGL14.EGL_OPENGL_ES2_BIT,
+    EGL14.EGL_RED_SIZE,
+    8,
+    EGL14.EGL_GREEN_SIZE,
+    8,
+    EGL14.EGL_BLUE_SIZE,
+    8,
+    EGL14.EGL_ALPHA_SIZE,
+    8,
+    EGL14.EGL_NONE
+)
+
+private val EGL_CONTEXT_ATTRIB_LIST =
+    intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
+
+internal val EGL_SURFACE_ATTRIB_LIST = intArrayOf(EGL14.EGL_NONE)
 
 /** The base class for [CanvasRenderer] and [GlesRenderer]. */
-public abstract class Renderer(
+public sealed class Renderer(
     /** The [SurfaceHolder] that [renderInternal] will draw into. */
     public val surfaceHolder: SurfaceHolder,
 
@@ -74,8 +136,8 @@
     /**
      * The bounds of the [SurfaceHolder] this Renderer renders into. Depending on the shape of the
      * device's screen not all of these pixels may be visible to the user (see
-     * [WatchState.screenShape]).  Note also that API level 27+ devices draw indicators in the top
-     * and bottom 24dp of the screen, avoid rendering anything important there.
+     * [Configuration.isScreenRound]).  Note also that API level 27+ devices draw indicators in the
+     * top and bottom 24dp of the screen, avoid rendering anything important there.
      */
     public var screenBounds: Rect = surfaceHolder.surfaceFrame
         private set
@@ -135,8 +197,8 @@
     ): Bitmap
 
     /**
-     * Called when the [DrawMode] has been updated. Will always be called before the first
-     * call to onDraw().
+     * Called when the [RenderParameters] has been updated. Will always be called before the first
+     * call to [CanvasRenderer.render] or [GlesRenderer.render].
      */
     @UiThread
     protected open fun onRenderParametersChanged(renderParameters: RenderParameters) {
@@ -176,7 +238,10 @@
     public open fun shouldAnimate(): Boolean =
         watchState.isVisible.value && !watchState.isAmbient.value
 
-    /** Schedules a call to [renderInternal] to draw the next frame. */
+    /**
+     * Schedules a call to either [CanvasRenderer.render] or [GlesRenderer.render] to draw the next
+     * frame.
+     */
     @UiThread
     public fun invalidate() {
         if (this::watchFaceHostApi.isInitialized) {
@@ -185,12 +250,388 @@
     }
 
     /**
-     * Posts a message to schedule a call to [renderInternal] to draw the next frame. Unlike
-     * [invalidate], this method is thread-safe and may be called on any thread.
+     * Posts a message to schedule a call to either [CanvasRenderer.render] or [GlesRenderer.render]
+     * to draw the next frame. Unlike [invalidate], this method is thread-safe and may be called
+     * on any thread.
      */
     public fun postInvalidate() {
         if (this::watchFaceHostApi.isInitialized) {
             watchFaceHostApi.getHandler().post { watchFaceHostApi.invalidate() }
         }
     }
+
+    /**
+     * Watch faces that require [Canvas] rendering should extend their [Renderer] from this class.
+     */
+    public abstract class CanvasRenderer(
+        /**
+         * The [SurfaceHolder] from which a [Canvas] to will be obtained and passed into [render].
+         */
+        surfaceHolder: SurfaceHolder,
+
+        /** The watch face's associated [UserStyleRepository]. */
+        userStyleRepository: UserStyleRepository,
+
+        /** The watch face's associated [WatchState]. */
+        watchState: WatchState,
+
+        /** The type of canvas to request. */
+        @CanvasType private val canvasType: Int,
+
+        /**
+         * The interval in milliseconds between frames in interactive [DrawMode]s. To render at 60hz
+         * set to 16. Note when battery is low, the frame rate will be clamped to 10fps. Watch faces
+         * are recommended to use lower frame rates if possible for better battery life. Variable
+         * frame  rates can also help preserve battery life, e.g. if a watch face has a short
+         * animation once per second it can adjust the frame rate inorder to sleep when not
+         * animating.
+         */
+        @IntRange(from = 0, to = 10000)
+        interactiveDrawModeUpdateDelayMillis: Long
+    ) : Renderer(
+        surfaceHolder,
+        userStyleRepository,
+        watchState,
+        interactiveDrawModeUpdateDelayMillis
+    ) {
+
+        @SuppressWarnings("UnsafeNewApiCall") // We check if the SDK is new enough.
+        internal override fun renderInternal(
+            calendar: Calendar
+        ) {
+            val canvas = (
+                if (canvasType == CanvasType.HARDWARE && android.os.Build.VERSION.SDK_INT >= 26) {
+                    surfaceHolder.lockHardwareCanvas() // Requires API level 26.
+                } else {
+                    surfaceHolder.lockCanvas()
+                }
+                ) ?: return
+            try {
+                if (watchState.isVisible.value) {
+                    render(canvas, surfaceHolder.surfaceFrame, calendar)
+                } else {
+                    canvas.drawColor(Color.BLACK)
+                }
+            } finally {
+                surfaceHolder.unlockCanvasAndPost(canvas)
+            }
+        }
+
+        /** {@inheritDoc} */
+        internal override fun takeScreenshot(
+            calendar: Calendar,
+            renderParameters: RenderParameters
+        ): Bitmap {
+            val bitmap = Bitmap.createBitmap(
+                screenBounds.width(),
+                screenBounds.height(),
+                Bitmap.Config.ARGB_8888
+            )
+            val prevRenderParameters = this.renderParameters
+            this.renderParameters = renderParameters
+            render(Canvas(bitmap), screenBounds, calendar)
+            this.renderParameters = prevRenderParameters
+            return bitmap
+        }
+
+        /**
+         * Sub-classes should override this to implement their rendering logic which should respect
+         * the current [DrawMode]. For correct functioning the CanvasRenderer must use the supplied
+         * [Calendar] in favor of any other ways of getting the time.
+         *
+         * @param canvas The [Canvas] to render into. Don't assume this is always the canvas from
+         *     the [SurfaceHolder] backing the display
+         * @param bounds A [Rect] describing the bonds of the canvas to draw into
+         * @param calendar The current [Calendar]
+         */
+        @UiThread
+        public abstract fun render(
+            canvas: Canvas,
+            bounds: Rect,
+            calendar: Calendar
+        )
+    }
+
+    /**
+     * Watch faces that require [GLES20] rendering should extend their [Renderer] from this class.
+     */
+    public abstract class GlesRenderer @JvmOverloads constructor(
+        /** The [SurfaceHolder] whose [android.view.Surface] [render] will draw into. */
+        surfaceHolder: SurfaceHolder,
+
+        /** The associated [UserStyleRepository]. */
+        userStyleRepository: UserStyleRepository,
+
+        /** The associated [WatchState]. */
+        watchState: WatchState,
+
+        /**
+         * The interval in milliseconds between frames in interactive [DrawMode]s. To render at 60hz
+         * set to 16. Note when battery is low, the frame rate will be clamped to 10fps. Watch faces
+         * are recommended to use lower frame rates if possible for better battery life. Variable
+         * frame rates can also help preserve battery life, e.g. if a watch face has a short
+         * animation once per second it can adjust the frame rate inorder to sleep when not
+         * animating.
+         */
+        @IntRange(from = 0, to = 10000)
+        interactiveDrawModeUpdateDelayMillis: Long,
+
+        /**
+         * Attributes for [EGL14.eglChooseConfig]. By default this selects an RGBA8888 back buffer.
+         */
+        private val eglConfigAttribList: IntArray = EGL_CONFIG_ATTRIB_LIST,
+
+        /**
+         * The attributes to be passed to [EGL14.eglCreateWindowSurface]. By default this is empty.
+         */
+        private val eglSurfaceAttribList: IntArray = EGL_SURFACE_ATTRIB_LIST
+    ) : Renderer(
+        surfaceHolder,
+        userStyleRepository,
+        watchState,
+        interactiveDrawModeUpdateDelayMillis
+    ) {
+        /** @hide */
+        private companion object {
+            private const val TAG = "Gles2WatchFace"
+        }
+
+        private var eglDisplay: EGLDisplay? = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY).apply {
+            if (this == EGL14.EGL_NO_DISPLAY) {
+                throw RuntimeException("eglGetDisplay returned EGL_NO_DISPLAY")
+            }
+            // Initialize the display. The major and minor version numbers are passed back.
+            val version = IntArray(2)
+            if (!EGL14.eglInitialize(this, version, 0, version, 1)) {
+                throw RuntimeException("eglInitialize failed")
+            }
+        }
+
+        private var eglConfig: EGLConfig = chooseEglConfig(eglDisplay!!)
+
+        @SuppressWarnings("SyntheticAccessor")
+        private var eglContext: EGLContext? = EGL14.eglCreateContext(
+            eglDisplay,
+            eglConfig,
+            EGL14.EGL_NO_CONTEXT,
+            EGL_CONTEXT_ATTRIB_LIST,
+            0
+        )
+
+        init {
+            if (eglContext == EGL14.EGL_NO_CONTEXT) {
+                throw RuntimeException("eglCreateContext failed")
+            }
+        }
+
+        private var eglSurface: EGLSurface? = null
+        private var calledOnGlContextCreated = false
+
+        /**
+         * Chooses the EGLConfig to use.
+         * @throws RuntimeException if [EGL14.eglChooseConfig] fails
+         */
+        private fun chooseEglConfig(eglDisplay: EGLDisplay): EGLConfig {
+            val numEglConfigs = IntArray(1)
+            val eglConfigs = arrayOfNulls<EGLConfig>(1)
+            if (!EGL14.eglChooseConfig(
+                    eglDisplay,
+                    eglConfigAttribList,
+                    0,
+                    eglConfigs,
+                    0,
+                    eglConfigs.size,
+                    numEglConfigs,
+                    0
+                )
+            ) {
+                throw RuntimeException("eglChooseConfig failed")
+            }
+            if (numEglConfigs[0] == 0) {
+                throw RuntimeException("no matching EGL configs")
+            }
+            return eglConfigs[0]!!
+        }
+
+        private fun createWindowSurface(width: Int, height: Int) {
+            if (eglSurface != null) {
+                if (!EGL14.eglDestroySurface(eglDisplay, eglSurface)) {
+                    Log.w(TAG, "eglDestroySurface failed")
+                }
+            }
+            eglSurface = EGL14.eglCreateWindowSurface(
+                eglDisplay,
+                eglConfig,
+                surfaceHolder.surface,
+                eglSurfaceAttribList,
+                0
+            )
+            if (eglSurface == EGL14.EGL_NO_SURFACE) {
+                throw RuntimeException("eglCreateWindowSurface failed")
+            }
+
+            makeContextCurrent()
+            GLES20.glViewport(0, 0, width, height)
+            if (!calledOnGlContextCreated) {
+                calledOnGlContextCreated = true
+                onGlContextCreated()
+            }
+            onGlSurfaceCreated(width, height)
+        }
+
+        @CallSuper
+        override fun onDestroy() {
+            if (eglSurface != null) {
+                if (!EGL14.eglDestroySurface(eglDisplay, eglSurface)) {
+                    Log.w(TAG, "eglDestroySurface failed")
+                }
+                eglSurface = null
+            }
+            if (eglContext != null) {
+                if (!EGL14.eglDestroyContext(eglDisplay, eglContext)) {
+                    Log.w(TAG, "eglDestroyContext failed")
+                }
+                eglContext = null
+            }
+            if (eglDisplay != null) {
+                if (!EGL14.eglTerminate(eglDisplay)) {
+                    Log.w(TAG, "eglTerminate failed")
+                }
+                eglDisplay = null
+            }
+        }
+
+        /**
+         * Sets our GL context to be the current one. This method *must* be called before any OpenGL
+         * APIs are used.
+         */
+        private fun makeContextCurrent() {
+            if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
+                throw RuntimeException("eglMakeCurrent failed")
+            }
+        }
+
+        internal override fun onPostCreate() {
+            surfaceHolder.addCallback(object : SurfaceHolder.Callback {
+                @SuppressLint("SyntheticAccessor")
+                override fun surfaceChanged(
+                    holder: SurfaceHolder,
+                    format: Int,
+                    width: Int,
+                    height: Int
+                ) {
+                    createWindowSurface(width, height)
+                }
+
+                @SuppressLint("SyntheticAccessor")
+                override fun surfaceDestroyed(holder: SurfaceHolder) {
+                    if (!EGL14.eglDestroySurface(eglDisplay, eglSurface)) {
+                        Log.w(TAG, "eglDestroySurface failed")
+                    }
+                    eglSurface = null
+                }
+
+                override fun surfaceCreated(holder: SurfaceHolder) {
+                }
+            })
+
+            createWindowSurface(
+                surfaceHolder.surfaceFrame.width(),
+                surfaceHolder.surfaceFrame.height()
+            )
+        }
+
+        /** Called when a new GL context is created. It's safe to use GL APIs in this method. */
+        @UiThread
+        public open fun onGlContextCreated() {
+        }
+
+        /**
+         * Called when a new GL surface is created. It's safe to use GL APIs in this method.
+         *
+         * @param width width of surface in pixels
+         * @param height height of surface in pixels
+         */
+        @UiThread
+        public open fun onGlSurfaceCreated(width: Int, height: Int) {
+        }
+
+        internal override fun renderInternal(
+            calendar: Calendar
+        ) {
+            makeContextCurrent()
+            render(calendar)
+            if (!EGL14.eglSwapBuffers(eglDisplay, eglSurface)) {
+                Log.w(TAG, "eglSwapBuffers failed")
+            }
+        }
+
+        /** {@inheritDoc} */
+        internal override fun takeScreenshot(
+            calendar: Calendar,
+            renderParameters: RenderParameters
+        ): Bitmap {
+            val width = screenBounds.width()
+            val height = screenBounds.height()
+            val pixelBuf = ByteBuffer.allocateDirect(width * height * 4)
+            makeContextCurrent()
+            val prevRenderParameters = this.renderParameters
+            this.renderParameters = renderParameters
+            render(calendar)
+            this.renderParameters = prevRenderParameters
+            GLES20.glFinish()
+            GLES20.glReadPixels(
+                0,
+                0,
+                width,
+                height,
+                GLES20.GL_RGBA,
+                GLES20.GL_UNSIGNED_BYTE,
+                pixelBuf
+            )
+            // The image is flipped when using read pixels because the first pixel in the OpenGL
+            // buffer is in bottom left.
+            verticalFlip(pixelBuf, width, height)
+            val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+            bitmap.copyPixelsFromBuffer(pixelBuf)
+            return bitmap
+        }
+
+        private fun verticalFlip(
+            buffer: ByteBuffer,
+            width: Int,
+            height: Int
+        ) {
+            var i = 0
+            val tmp = ByteArray(width * 4)
+            while (i++ < height / 2) {
+                buffer[tmp]
+                System.arraycopy(
+                    buffer.array(),
+                    buffer.limit() - buffer.position(),
+                    buffer.array(),
+                    buffer.position() - width * 4,
+                    width * 4
+                )
+                System.arraycopy(
+                    tmp,
+                    0,
+                    buffer.array(),
+                    buffer.limit() - buffer.position(),
+                    width * 4
+                )
+            }
+            buffer.rewind()
+        }
+
+        /**
+         * Sub-classes should override this to implement their rendering logic which should respect
+         * the current [DrawMode]. For correct functioning the GlesRenderer must use the supplied
+         * [Calendar] in favor of any other ways of getting the time.
+         *
+         * @param calendar The current [Calendar]
+         */
+        @UiThread
+        public abstract fun render(calendar: Calendar)
+    }
 }
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 af4cb9c..c27b9c66 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
@@ -18,11 +18,9 @@
 
 import android.annotation.SuppressLint
 import android.app.NotificationManager
-import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.IntentFilter
 import android.graphics.Point
 import android.graphics.Rect
 import android.icu.util.Calendar
@@ -39,7 +37,6 @@
 import androidx.annotation.VisibleForTesting
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationData
-import androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle
 import androidx.wear.watchface.control.IInteractiveWatchFaceSysUI
 import androidx.wear.watchface.data.RenderParametersWireFormat
 import androidx.wear.watchface.style.UserStyle
@@ -105,8 +102,8 @@
 }
 
 /**
- * A WatchFace is constructed by a user's [WatchFaceService] and brings together rendering,
- * styling, complications and state observers.
+ * The return value of [WatchFaceService.createWatchFace] which brings together rendering, styling,
+ * complications and state observers.
  */
 public class WatchFace(
     /**
@@ -115,13 +112,13 @@
      */
     @WatchFaceType internal var watchFaceType: Int,
 
-    /** The {@UserStyleRepository} for this WatchFaceImpl. */
+    /** The [UserStyleRepository] for this WatchFace. */
     internal val userStyleRepository: UserStyleRepository,
 
-    /** The [ComplicationsManager] for this WatchFaceImpl. */
+    /** The [ComplicationsManager] for this WatchFace. */
     internal var complicationsManager: ComplicationsManager,
 
-    /** The [Renderer] for this WatchFaceImpl. */
+    /** The [Renderer] for this WatchFace. */
     internal val renderer: Renderer
 ) {
     internal var tapListener: TapListener? = null
@@ -210,7 +207,7 @@
         }
     }
 
-    /** The preview time in milliseconds since the epoch, or null if not set. */
+    /** The UTC preview time in milliseconds since the epoch, or null if not set. */
     @get:SuppressWarnings("AutoBoxing")
     @IntRange(from = 0)
     public var overridePreviewReferenceTimeMillis: Long? = null
@@ -331,35 +328,6 @@
     private val pendingPostDoubleTap: CancellableUniqueTask =
         CancellableUniqueTask(watchFaceHostApi.getHandler())
 
-    private val timeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            calendar.timeZone = TimeZone.getDefault()
-            watchFaceHostApi.invalidate()
-        }
-    }
-
-    private val timeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context?, intent: Intent?) {
-            // System time has changed hence next scheduled draw is invalid.
-            nextDrawTimeMillis = systemTimeProvider.getSystemTimeMillis()
-            watchFaceHostApi.invalidate()
-        }
-    }
-
-    internal val batteryLevelReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        @SuppressWarnings("SyntheticAccessor")
-        override fun onReceive(context: Context, intent: Intent) {
-            val isBatteryLowAndNotCharging =
-                watchState.isBatteryLowAndNotCharging as MutableObservableWatchData
-            when (intent.action) {
-                Intent.ACTION_BATTERY_LOW -> isBatteryLowAndNotCharging.value = true
-                Intent.ACTION_BATTERY_OKAY -> isBatteryLowAndNotCharging.value = false
-                Intent.ACTION_POWER_CONNECTED -> isBatteryLowAndNotCharging.value = false
-            }
-            watchFaceHostApi.invalidate()
-        }
-    }
-
     private val componentName =
         ComponentName(
             watchFaceHostApi.getContext().packageName,
@@ -376,14 +344,36 @@
         legacyWatchFaceStyle.tapEventsAccepted
     )
 
-    /**
-     * We listen for MOCK_TIME_INTENTs which we interpret as a request to modify time. E.g. speeding
-     * up or slowing down time, and providing support for making time loop between two instants.
-     * This is intended to help implement animations which may occur infrequently (e.g. hourly).
-     */
-    internal val mockTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        @SuppressWarnings("SyntheticAccessor")
-        override fun onReceive(context: Context, intent: Intent) {
+    private val broadcastEventObserver = object : BroadcastReceivers.BroadcastEventObserver {
+        override fun onActionTimeTick() {
+            if (!watchState.isAmbient.value) {
+                renderer.invalidate()
+            }
+        }
+
+        override fun onActionTimeZoneChanged() {
+            calendar.timeZone = TimeZone.getDefault()
+            renderer.invalidate()
+        }
+
+        override fun onActionTimeChanged() {
+            // System time has changed hence next scheduled draw is invalid.
+            nextDrawTimeMillis = systemTimeProvider.getSystemTimeMillis()
+            renderer.invalidate()
+        }
+
+        override fun onActionBatteryChanged(intent: Intent) {
+            val isBatteryLowAndNotCharging =
+                watchState.isBatteryLowAndNotCharging as MutableObservableWatchData
+            when (intent.action) {
+                Intent.ACTION_BATTERY_LOW -> isBatteryLowAndNotCharging.value = true
+                Intent.ACTION_BATTERY_OKAY -> isBatteryLowAndNotCharging.value = false
+                Intent.ACTION_POWER_CONNECTED -> isBatteryLowAndNotCharging.value = false
+            }
+            renderer.invalidate()
+        }
+
+        override fun onMockTime(intent: Intent) {
             mockTime.speed = intent.getFloatExtra(
                 EXTRA_MOCK_TIME_SPEED_MULTIPLIER,
                 MOCK_TIME_DEFAULT_SPEED_MULTIPLIER
@@ -561,21 +551,9 @@
             return
         }
         registeredReceivers = true
-        watchFaceHostApi.getContext().registerReceiver(
-            timeZoneReceiver,
-            IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
-        )
-        watchFaceHostApi.getContext().registerReceiver(
-            timeReceiver,
-            IntentFilter(Intent.ACTION_TIME_CHANGED)
-        )
-        watchFaceHostApi.getContext().registerReceiver(
-            batteryLevelReceiver,
-            IntentFilter(Intent.ACTION_BATTERY_CHANGED)
-        )
-        watchFaceHostApi.getContext().registerReceiver(
-            mockTimeReceiver,
-            IntentFilter(MOCK_TIME_INTENT)
+        BroadcastReceivers.addBroadcastEventObserver(
+            watchFaceHostApi.getContext(),
+            broadcastEventObserver
         )
     }
 
@@ -584,10 +562,7 @@
             return
         }
         registeredReceivers = false
-        watchFaceHostApi.getContext().unregisterReceiver(timeZoneReceiver)
-        watchFaceHostApi.getContext().unregisterReceiver(timeReceiver)
-        watchFaceHostApi.getContext().unregisterReceiver(batteryLevelReceiver)
-        watchFaceHostApi.getContext().unregisterReceiver(mockTimeReceiver)
+        BroadcastReceivers.removeBroadcastEventObserver(broadcastEventObserver)
     }
 
     private fun scheduleDraw() {
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 e6bdc8f..718d7ac 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
@@ -17,11 +17,9 @@
 package androidx.wear.watchface
 
 import android.annotation.SuppressLint
-import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.IntentFilter
 import android.graphics.Bitmap
 import android.graphics.Canvas
 import android.graphics.Rect
@@ -121,22 +119,19 @@
 /**
  * WatchFaceService and [WatchFace] are a pair of classes intended to handle much of
  * the boilerplate needed to implement a watch face without being too opinionated. The suggested
- * structure of an WatchFaceService based watch face is:
+ * structure of a WatchFaceService based watch face is:
  *
  * @sample androidx.wear.watchface.samples.kDocCreateExampleWatchFaceService
  *
- * Base classes for complications and styles are provided along with a default UI for configuring
- * them. Complications are optional, however if required, WatchFaceService assumes all
- * complications can be enumerated up front and passed as a collection into [ComplicationsManager]'s
- * constructor which is passed to [WatchFace]'s constructor. Some watch faces support different
- * configurations (number & position) of complications and this can be achieved by rendering a
- * subset and only marking the ones you need as enabled (see
- * [UserStyleSetting.ComplicationsUserStyleSetting]).
+ * Sub classes of WatchFaceService are expected to implement [createWatchFace] which is the
+ * factory for making [WatchFace]s. All [Complication]s are assumed to be enumerated up upfront and
+ * passed as a collection into [ComplicationsManager]'s constructor which is in turn passed to
+ * [WatchFace]'s constructor. Complications can be enabled and disabled via [UserStyleSetting
+ * .ComplicationsUserStyleSetting].
  *
- * Many watch faces support styles, typically controlling the color and visual look of watch face
- * elements such as numeric fonts, watch hands and ticks. WatchFaceService doesn't take an
- * an opinion on what comprises a style beyond it should be representable as a map of categories to
- * options.
+ * Watch face styling (color and visual look of watch face elements such as numeric fonts, watch
+ * hands and ticks, etc...) is directly supported via [UserStyleSetting] and
+ * [UserStyleRepository].
  *
  * To aid debugging watch face animations, WatchFaceService allows you to speed up or slow down
  * time, and to loop between two instants.  This is controlled by MOCK_TIME_INTENT intents
@@ -266,14 +261,6 @@
             isVisible.value = this@EngineWrapper.isVisible
         }
 
-        private var timeTickRegistered = false
-        private val timeTickReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-            @SuppressWarnings("SyntheticAccessor")
-            override fun onReceive(context: Context, intent: Intent) {
-                watchFaceImpl.renderer.invalidate()
-            }
-        }
-
         /**
          * Whether or not we allow watchfaces to animate. In some tests or for headless
          * rendering (for remote config) we don't want this.
@@ -306,16 +293,6 @@
 
         private val invalidateRunnable = Runnable(this::invalidate)
 
-        private val ambientTimeTickFilter = IntentFilter().apply {
-            addAction(Intent.ACTION_DATE_CHANGED)
-            addAction(Intent.ACTION_TIME_CHANGED)
-            addAction(Intent.ACTION_TIMEZONE_CHANGED)
-        }
-
-        private val interactiveTimeTickFilter = IntentFilter(ambientTimeTickFilter).apply {
-            addAction(Intent.ACTION_TIME_TICK)
-        }
-
         // TODO(alexclarke): Figure out if we can remove this.
         private var pendingBackgroundAction: Bundle? = null
         private var pendingProperties: Bundle? = null
@@ -402,7 +379,6 @@
                 systemState.inAmbientMode != mutableWatchState.isAmbient.value
             ) {
                 mutableWatchState.isAmbient.value = systemState.inAmbientMode
-                updateTimeTickReceiver()
             }
 
             if (firstSetSystemState ||
@@ -663,11 +639,6 @@
                 choreographer.removeFrameCallback(frameCallback)
             }
 
-            if (timeTickRegistered) {
-                timeTickRegistered = false
-                unregisterReceiver(timeTickReceiver)
-            }
-
             if (this::watchFaceImpl.isInitialized) {
                 watchFaceImpl.onDestroy()
             }
@@ -927,41 +898,6 @@
             pendingSetWatchFaceStyle = false
         }
 
-        /**
-         * Registers [timeTickReceiver] if it should be registered and isn't currently, or
-         * unregisters it if it shouldn't be registered but currently is. It also applies the right
-         * intent filter depending on whether we are in ambient mode or not.
-         */
-        internal fun updateTimeTickReceiver() {
-            // Separate calls are issued to deliver the state of isAmbient and isVisible, so during
-            // init we might not yet know the state of both.
-            if (!mutableWatchState.isAmbient.hasValue() ||
-                !mutableWatchState.isVisible.hasValue()
-            ) {
-                return
-            }
-
-            if (timeTickRegistered) {
-                unregisterReceiver(timeTickReceiver)
-                timeTickRegistered = false
-            }
-
-            // We only register if we are visible, otherwise it doesn't make sense to waste cycles.
-            if (mutableWatchState.isVisible.value) {
-                if (mutableWatchState.isAmbient.value) {
-                    registerReceiver(timeTickReceiver, ambientTimeTickFilter)
-                } else {
-                    registerReceiver(timeTickReceiver, interactiveTimeTickFilter)
-                }
-                timeTickRegistered = true
-
-                // In case we missed a tick while transitioning from ambient to interactive, we
-                // want to make sure the watch face doesn't show stale time when in interactive
-                // mode.
-                watchFaceImpl.renderer.invalidate()
-            }
-        }
-
         override fun onVisibilityChanged(visible: Boolean) {
             super.onVisibilityChanged(visible)
 
@@ -982,7 +918,6 @@
             }
 
             mutableWatchState.isVisible.value = visible
-            updateTimeTickReceiver()
             pendingVisibilityChanged = null
         }
 
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
index f9888b2..26df645 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
@@ -19,25 +19,10 @@
 import android.app.NotificationManager
 import androidx.annotation.RestrictTo
 
-import androidx.annotation.IntDef
-
-/** @hide */
-@IntDef(
-    value = [
-        ScreenShape.ROUND,
-        ScreenShape.RECTANGULAR
-    ]
-)
-public annotation class ScreenShape {
-    public companion object {
-        /** The watch screen has a circular shape. */
-        public const val ROUND: Int = 1
-
-        /** The watch screen has a rectangular or square shape. */
-        public const val RECTANGULAR: Int = 2
-    }
-}
-
+/**
+ * Describes the current state of the wearable including some hardware details such as whether or
+ * not it supports burn in prevention and low-bit ambient.
+ */
 public class WatchState(
     /**
      * The current user interruption settings. See [NotificationManager]. Based on the value
@@ -79,10 +64,6 @@
     @get:JvmName("hasBurnInProtection")
     public val hasBurnInProtection: Boolean,
 
-    /** The physical shape of the screen. */
-    @ScreenShape
-    public val screenShape: Int,
-
     /** UTC reference time for previews of analog watch faces in milliseconds since the epoch. */
     public val analogPreviewReferenceTimeMillis: Long,
 
@@ -100,8 +81,6 @@
     public val isVisible: MutableObservableWatchData<Boolean> = MutableObservableWatchData()
     public var hasLowBitAmbient: Boolean = false
     public var hasBurnInProtection: Boolean = false
-    @ScreenShape
-    public var screenShape: Int = 0
     public var analogPreviewReferenceTimeMillis: Long = 0
     public var digitalPreviewReferenceTimeMillis: Long = 0
 
@@ -112,7 +91,6 @@
         isVisible = isVisible,
         hasLowBitAmbient = hasLowBitAmbient,
         hasBurnInProtection = hasBurnInProtection,
-        screenShape = screenShape,
         analogPreviewReferenceTimeMillis = analogPreviewReferenceTimeMillis,
         digitalPreviewReferenceTimeMillis = digitalPreviewReferenceTimeMillis
     )
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 e8a1f0d..7b3158c 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
@@ -106,6 +106,10 @@
     override fun getHandler() = handler
 
     override fun getMutableWatchState() = watchState
+
+    fun setIsVisible(isVisible: Boolean) {
+        watchState.isVisible.value = isVisible
+    }
 }
 
 /**
@@ -169,7 +173,7 @@
     userStyleRepository: UserStyleRepository,
     watchState: WatchState,
     interactiveFrameRateMs: Long
-) : CanvasRenderer(
+) : Renderer.CanvasRenderer(
     surfaceHolder,
     userStyleRepository,
     watchState,
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 da685aa..034a2c6 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
@@ -34,6 +34,7 @@
 import android.view.SurfaceHolder
 import android.view.ViewConfiguration
 import androidx.test.core.app.ApplicationProvider
+import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
@@ -169,7 +170,7 @@
                 ComplicationType.SMALL_IMAGE
             ),
             DefaultComplicationProviderPolicy(SystemProviders.SUNRISE_SUNSET),
-            RectF(0.2f, 0.4f, 0.4f, 0.6f)
+            ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f))
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
 
@@ -190,7 +191,7 @@
                 ComplicationType.SMALL_IMAGE
             ),
             DefaultComplicationProviderPolicy(SystemProviders.DAY_OF_WEEK),
-            RectF(0.6f, 0.4f, 0.8f, 0.6f)
+            ComplicationBounds(RectF(0.6f, 0.4f, 0.8f, 0.6f))
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
 
@@ -328,6 +329,7 @@
         sendImmutableProperties(engineWrapper, hasLowBitAmbient, hasBurnInProtection)
 
         watchFaceImpl = engineWrapper.watchFaceImpl
+        testWatchFaceService.setIsVisible(true)
     }
 
     private fun initWallpaperInteractiveWatchFaceInstance(
@@ -377,6 +379,7 @@
         // The [SurfaceHolder] must be sent before binding.
         engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100)
         watchFaceImpl = engineWrapper.watchFaceImpl
+        testWatchFaceService.setIsVisible(true)
     }
 
     private fun sendBinder(engine: WatchFaceService.EngineWrapper, apiVersion: Int) {
@@ -517,8 +520,7 @@
         watchState.isAmbient.value = false
         testWatchFaceService.mockSystemTimeMillis = 1000L
 
-        watchFaceImpl.mockTimeReceiver.onReceive(
-            context,
+        BroadcastReceivers.sendOnMockTimeForTesting(
             Intent(WatchFaceImpl.MOCK_TIME_INTENT).apply {
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_SPEED_MULTIPLIER, 2.0f)
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_WRAPPING_MIN_TIME, -1L)
@@ -545,8 +547,7 @@
         watchState.isAmbient.value = false
         testWatchFaceService.mockSystemTimeMillis = 1000L
 
-        watchFaceImpl.mockTimeReceiver.onReceive(
-            context,
+        BroadcastReceivers.sendOnMockTimeForTesting(
             Intent(WatchFaceImpl.MOCK_TIME_INTENT).apply {
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_SPEED_MULTIPLIER, 2.0f)
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_WRAPPING_MIN_TIME, 1000L)
@@ -874,19 +875,13 @@
         )
 
         // The delay should change when battery is low.
-        watchFaceImpl.batteryLevelReceiver.onReceive(
-            context,
-            Intent(Intent.ACTION_BATTERY_LOW)
-        )
+        BroadcastReceivers.sendOnActionBatteryChangedForTesting(Intent(Intent.ACTION_BATTERY_LOW))
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
             WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS
         )
 
         // And go back to normal when battery is OK.
-        watchFaceImpl.batteryLevelReceiver.onReceive(
-            context,
-            Intent(Intent.ACTION_BATTERY_OKAY)
-        )
+        BroadcastReceivers.sendOnActionBatteryChangedForTesting(Intent(Intent.ACTION_BATTERY_OKAY))
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
             INTERACTIVE_UPDATE_RATE_MS
         )
@@ -1261,8 +1256,8 @@
             4
         )
 
-        leftComplication.unitSquareBounds = RectF(0.3f, 0.3f, 0.5f, 0.5f)
-        rightComplication.unitSquareBounds = RectF(0.7f, 0.75f, 0.9f, 0.95f)
+        leftComplication.complicationBounds = ComplicationBounds(RectF(0.3f, 0.3f, 0.5f, 0.5f))
+        rightComplication.complicationBounds = ComplicationBounds(RectF(0.7f, 0.75f, 0.9f, 0.95f))
 
         val complicationDetails = engineWrapper.getComplicationState()
         assertThat(complicationDetails[0].id).isEqualTo(LEFT_COMPLICATION_ID)
@@ -1403,7 +1398,7 @@
                 provider2,
                 SystemProviders.SUNRISE_SUNSET
             ),
-            RectF(0.2f, 0.4f, 0.4f, 0.6f)
+            ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f))
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
         initEngine(WatchFaceType.ANALOG, listOf(complication), UserStyleSchema(emptyList()))
@@ -1431,7 +1426,7 @@
                 provider2,
                 SystemProviders.SUNRISE_SUNSET
             ),
-            RectF(0.2f, 0.4f, 0.4f, 0.6f)
+            ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f))
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
         initEngine(
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt
index 685668c..fd91bb2 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/ui/WatchFaceConfigUiTest.kt
@@ -18,20 +18,21 @@
 
 import android.content.ComponentName
 import android.content.Context
-import android.graphics.Bitmap
+import android.graphics.Canvas
 import android.graphics.Rect
 import android.graphics.RectF
 import android.icu.util.Calendar
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
+import androidx.wear.complications.ComplicationBounds
 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
 import androidx.wear.watchface.MutableWatchState
-import androidx.wear.watchface.RenderParameters
 import androidx.wear.watchface.Renderer
 import androidx.wear.watchface.WatchFaceTestRunner
 import androidx.wear.watchface.complications.rendering.ComplicationDrawable
@@ -135,7 +136,7 @@
                 ComplicationType.SMALL_IMAGE
             ),
             DefaultComplicationProviderPolicy(SystemProviders.SUNRISE_SUNSET),
-            RectF(0.2f, 0.4f, 0.4f, 0.6f)
+            ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f))
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
 
@@ -156,7 +157,7 @@
                 ComplicationType.SMALL_IMAGE
             ),
             DefaultComplicationProviderPolicy(SystemProviders.DAY_OF_WEEK),
-            RectF(0.6f, 0.4f, 0.8f, 0.6f)
+            ComplicationBounds(RectF(0.6f, 0.4f, 0.8f, 0.6f))
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
             .build()
 
@@ -196,20 +197,14 @@
         val complicationSet = ComplicationsManager(
             complications,
             userStyleRepository,
-            object : Renderer(
+            object : Renderer.CanvasRenderer(
                 surfaceHolder,
                 userStyleRepository,
                 watchState.asWatchState(),
+                CanvasType.SOFTWARE,
                 INTERACTIVE_UPDATE_RATE_MS
             ) {
-                override fun renderInternal(calendar: Calendar) {}
-
-                override fun takeScreenshot(
-                    calendar: Calendar,
-                    renderParameters: RenderParameters
-                ): Bitmap {
-                    throw RuntimeException("Not Implemented!")
-                }
+                override fun render(canvas: Canvas, bounds: Rect, calendar: Calendar) {}
             }
         )
 
diff --git a/wear/wear/api/api_lint.ignore b/wear/wear/api/api_lint.ignore
index aedd625..e513a66 100644
--- a/wear/wear/api/api_lint.ignore
+++ b/wear/wear/api/api_lint.ignore
@@ -25,24 +25,6 @@
     Public class androidx.wear.widget.SwipeDismissController stripped of unavailable superclass androidx.wear.widget.DismissController
 
 
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setAnimatedIcon(android.graphics.drawable.Icon):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getAnimatedIcon()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setAnimatedIcon(android.graphics.drawable.Icon)
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setAnimatedIcon(int):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getAnimatedIcon()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setAnimatedIcon(int)
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setStaticIcon(android.graphics.drawable.Icon):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getStaticIcon()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setStaticIcon(android.graphics.drawable.Icon)
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setStaticIcon(int):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getStaticIcon()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setStaticIcon(int)
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setStatus(androidx.wear.ongoingactivity.OngoingActivityStatus):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getStatus()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setStatus(androidx.wear.ongoingactivity.OngoingActivityStatus)
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setTouchIntent(android.app.PendingIntent):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getTouchIntent()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setTouchIntent(android.app.PendingIntent)
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setLocusId(androidx.core.content.LocusIdCompat):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getLocusId()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setLocusId(androidx.core.content.LocusIdCompat)
-MissingGetterMatchingBuilder: androidx.wear.ongoingactivity.OngoingActivity.Builder#setOngoingActivityId(int):
-    androidx.wear.ongoingactivity.OngoingActivity does not declare a `getOngoingActivityId()` method matching method androidx.wear.ongoingactivity.OngoingActivity.Builder.setOngoingActivityId(int)
-
-
 MissingNullability: androidx.wear.activity.ConfirmationActivity#onCreate(android.os.Bundle) parameter #0:
     Missing nullability on parameter `savedInstanceState` in method `onCreate`
 MissingNullability: androidx.wear.ambient.AmbientMode#attachAmbientSupport(T):
diff --git a/wear/wear/api/current.txt b/wear/wear/api/current.txt
index 08278fb..5a86ffd 100644
--- a/wear/wear/api/current.txt
+++ b/wear/wear/api/current.txt
@@ -92,6 +92,7 @@
     method public androidx.wear.ongoingactivity.OngoingActivity build();
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setAnimatedIcon(android.graphics.drawable.Icon);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setAnimatedIcon(@DrawableRes int);
+    method public androidx.wear.ongoingactivity.OngoingActivity.Builder setCategory(String);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setLocusId(androidx.core.content.LocusIdCompat);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setOngoingActivityId(int);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
@@ -103,24 +104,23 @@
   public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
     method public static androidx.wear.ongoingactivity.OngoingActivityData? create(android.app.Notification);
     method public android.graphics.drawable.Icon? getAnimatedIcon();
+    method public String? getCategory();
     method public androidx.core.content.LocusIdCompat? getLocusId();
     method public int getOngoingActivityId();
     method public android.graphics.drawable.Icon getStaticIcon();
     method public androidx.wear.ongoingactivity.OngoingActivityStatus? getStatus();
+    method public long getTimestamp();
     method public android.app.PendingIntent getTouchIntent();
     method public static boolean hasOngoingActivity(android.app.Notification);
   }
 
-  public abstract class OngoingActivityStatus implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public OngoingActivityStatus();
-    method public abstract long getNextChangeTimeMillis(long);
-    method public abstract CharSequence getText(android.content.Context, long);
+  public class OngoingActivityStatus implements androidx.versionedparcelable.VersionedParcelable {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
   }
 
   public class TextOngoingActivityStatus extends androidx.wear.ongoingactivity.OngoingActivityStatus {
     ctor public TextOngoingActivityStatus(String);
-    method public long getNextChangeTimeMillis(long);
-    method public CharSequence getText(android.content.Context, long);
   }
 
   public class TimerOngoingActivityStatus extends androidx.wear.ongoingactivity.OngoingActivityStatus {
@@ -128,9 +128,7 @@
     ctor public TimerOngoingActivityStatus(long, boolean, long);
     ctor public TimerOngoingActivityStatus(long, boolean);
     ctor public TimerOngoingActivityStatus(long);
-    method public long getNextChangeTimeMillis(long);
     method public long getPausedAtMillis();
-    method public CharSequence getText(android.content.Context, long);
     method public long getTimeZeroMillis();
     method public long getTotalDurationMillis();
     method public boolean hasTotalDuration();
@@ -323,7 +321,7 @@
     method public boolean insideClickArea(float, float);
   }
 
-  public static class WearArcLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
+  public static class WearArcLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
     ctor public WearArcLayout.LayoutParams(android.content.Context, android.util.AttributeSet?);
     ctor public WearArcLayout.LayoutParams(int, int);
     ctor public WearArcLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
diff --git a/wear/wear/api/public_plus_experimental_current.txt b/wear/wear/api/public_plus_experimental_current.txt
index 36919fe..47489f1 100644
--- a/wear/wear/api/public_plus_experimental_current.txt
+++ b/wear/wear/api/public_plus_experimental_current.txt
@@ -92,6 +92,7 @@
     method public androidx.wear.ongoingactivity.OngoingActivity build();
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setAnimatedIcon(android.graphics.drawable.Icon);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setAnimatedIcon(@DrawableRes int);
+    method public androidx.wear.ongoingactivity.OngoingActivity.Builder setCategory(String);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setLocusId(androidx.core.content.LocusIdCompat);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setOngoingActivityId(int);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
@@ -103,24 +104,23 @@
   @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
     method public static androidx.wear.ongoingactivity.OngoingActivityData? create(android.app.Notification);
     method public android.graphics.drawable.Icon? getAnimatedIcon();
+    method public String? getCategory();
     method public androidx.core.content.LocusIdCompat? getLocusId();
     method public int getOngoingActivityId();
     method public android.graphics.drawable.Icon getStaticIcon();
     method public androidx.wear.ongoingactivity.OngoingActivityStatus? getStatus();
+    method public long getTimestamp();
     method public android.app.PendingIntent getTouchIntent();
     method public static boolean hasOngoingActivity(android.app.Notification);
   }
 
-  @androidx.versionedparcelable.VersionedParcelize public abstract class OngoingActivityStatus implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public OngoingActivityStatus();
-    method public abstract long getNextChangeTimeMillis(long);
-    method public abstract CharSequence getText(android.content.Context, long);
+  @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityStatus implements androidx.versionedparcelable.VersionedParcelable {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
   }
 
   @androidx.versionedparcelable.VersionedParcelize public class TextOngoingActivityStatus extends androidx.wear.ongoingactivity.OngoingActivityStatus {
     ctor public TextOngoingActivityStatus(String);
-    method public long getNextChangeTimeMillis(long);
-    method public CharSequence getText(android.content.Context, long);
   }
 
   @androidx.versionedparcelable.VersionedParcelize public class TimerOngoingActivityStatus extends androidx.wear.ongoingactivity.OngoingActivityStatus {
@@ -128,9 +128,7 @@
     ctor public TimerOngoingActivityStatus(long, boolean, long);
     ctor public TimerOngoingActivityStatus(long, boolean);
     ctor public TimerOngoingActivityStatus(long);
-    method public long getNextChangeTimeMillis(long);
     method public long getPausedAtMillis();
-    method public CharSequence getText(android.content.Context, long);
     method public long getTimeZeroMillis();
     method public long getTotalDurationMillis();
     method public boolean hasTotalDuration();
@@ -323,7 +321,7 @@
     method public boolean insideClickArea(float, float);
   }
 
-  public static class WearArcLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
+  public static class WearArcLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
     ctor public WearArcLayout.LayoutParams(android.content.Context, android.util.AttributeSet?);
     ctor public WearArcLayout.LayoutParams(int, int);
     ctor public WearArcLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
diff --git a/wear/wear/api/restricted_current.txt b/wear/wear/api/restricted_current.txt
index 9051f85..d80300e 100644
--- a/wear/wear/api/restricted_current.txt
+++ b/wear/wear/api/restricted_current.txt
@@ -92,6 +92,7 @@
     method public androidx.wear.ongoingactivity.OngoingActivity build();
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setAnimatedIcon(android.graphics.drawable.Icon);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setAnimatedIcon(@DrawableRes int);
+    method public androidx.wear.ongoingactivity.OngoingActivity.Builder setCategory(String);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setLocusId(androidx.core.content.LocusIdCompat);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setOngoingActivityId(int);
     method public androidx.wear.ongoingactivity.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
@@ -103,24 +104,23 @@
   @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
     method public static androidx.wear.ongoingactivity.OngoingActivityData? create(android.app.Notification);
     method public android.graphics.drawable.Icon? getAnimatedIcon();
+    method public String? getCategory();
     method public androidx.core.content.LocusIdCompat? getLocusId();
     method public int getOngoingActivityId();
     method public android.graphics.drawable.Icon getStaticIcon();
     method public androidx.wear.ongoingactivity.OngoingActivityStatus? getStatus();
+    method public long getTimestamp();
     method public android.app.PendingIntent getTouchIntent();
     method public static boolean hasOngoingActivity(android.app.Notification);
   }
 
-  @androidx.versionedparcelable.VersionedParcelize public abstract class OngoingActivityStatus implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public OngoingActivityStatus();
-    method public abstract long getNextChangeTimeMillis(long);
-    method public abstract CharSequence getText(android.content.Context, long);
+  @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityStatus implements androidx.versionedparcelable.VersionedParcelable {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
   }
 
   @androidx.versionedparcelable.VersionedParcelize public class TextOngoingActivityStatus extends androidx.wear.ongoingactivity.OngoingActivityStatus {
     ctor public TextOngoingActivityStatus(String);
-    method public long getNextChangeTimeMillis(long);
-    method public CharSequence getText(android.content.Context, long);
   }
 
   @androidx.versionedparcelable.VersionedParcelize public class TimerOngoingActivityStatus extends androidx.wear.ongoingactivity.OngoingActivityStatus {
@@ -128,9 +128,7 @@
     ctor public TimerOngoingActivityStatus(long, boolean, long);
     ctor public TimerOngoingActivityStatus(long, boolean);
     ctor public TimerOngoingActivityStatus(long);
-    method public long getNextChangeTimeMillis(long);
     method public long getPausedAtMillis();
-    method public CharSequence getText(android.content.Context, long);
     method public long getTimeZeroMillis();
     method public long getTotalDurationMillis();
     method public boolean hasTotalDuration();
@@ -327,7 +325,7 @@
     method public boolean insideClickArea(float, float);
   }
 
-  public static class WearArcLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
+  public static class WearArcLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
     ctor public WearArcLayout.LayoutParams(android.content.Context, android.util.AttributeSet?);
     ctor public WearArcLayout.LayoutParams(int, int);
     ctor public WearArcLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
diff --git a/wear/wear/build.gradle b/wear/wear/build.gradle
index eae997b..09fdd9a 100644
--- a/wear/wear/build.gradle
+++ b/wear/wear/build.gradle
@@ -34,6 +34,8 @@
 
     implementation "androidx.core:core-ktx:1.5.0-alpha04"
 
+    annotationProcessor(project(":versionedparcelable:versionedparcelable-compiler"))
+
     compileOnly fileTree(dir: '../wear_stubs', include: ['com.google.android.wearable-stubs.jar'])
 }
 
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTest.java b/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTest.java
index 27b82de..39f35a4 100644
--- a/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTest.java
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTest.java
@@ -23,6 +23,8 @@
 import static androidx.wear.widget.util.AsyncViewActions.waitForMatchingView;
 
 import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
 import android.view.KeyEvent;
@@ -65,6 +67,10 @@
             sendBackKey();
             // AND hidden
             assertHidden(R.id.dismissible_root);
+            // Back button up event is consumed, and not pass to the activity
+            scenario.onActivity(activity -> {
+                assertFalse(activity.mConsumeBackButtonUp);
+            });
         }
     }
 
@@ -78,8 +84,12 @@
             assertNotHidden(R.id.dismissible_root);
             // WHEN back button pressed
             sendBackKey();
-            // AND the layout is still nor hidden
+            // AND the layout is still not hidden
             assertNotHidden(R.id.dismissible_root);
+            // Back button up event is not consumed, and continue to pass to the activity
+            scenario.onActivity(activity -> {
+                assertTrue(activity.mConsumeBackButtonUp);
+            });
         }
     }
 
@@ -113,6 +123,56 @@
         }
     }
 
+    @Test
+    public void testDisableThenEnableBackDismiss() {
+        // GIVEN a freshly setup DismissibleFrameLayout
+        try (ActivityScenario<DismissibleFrameLayoutTestActivity> scenario =
+                     ActivityScenario.launch(createDismissibleLayoutIntent())) {
+            final DismissibleFrameLayout[] testLayout = new DismissibleFrameLayout[1];
+            final DismissibleFrameLayoutTestActivity[] testActivity =
+                    new DismissibleFrameLayoutTestActivity[1];
+            scenario.onActivity(activity -> {
+                testActivity[0] = activity;
+                testLayout[0] =
+                        (DismissibleFrameLayout) activity.findViewById(R.id.dismissible_root);
+                testLayout[0].registerCallback(mDismissCallback);
+                // Disable back button dismiss
+                testLayout[0].setBackButtonDismissible(false);
+            });
+
+            // CHECK the layout is not hidden
+            assertNotHidden(R.id.dismissible_root);
+            // The layout is not focused
+            assertFalse(testActivity[0].getCurrentFocus() == testLayout[0]);
+            // WHEN back button pressed
+            testActivity[0].mConsumeBackButtonUp = false;
+            sendBackKey();
+            // AND the layout is still not hidden
+            assertNotHidden(R.id.dismissible_root);
+            // Back button up event is not consumed, and continue to pass to the activity
+            assertTrue(testActivity[0].mConsumeBackButtonUp);
+
+            // Enable backButton dismiss, we have to run this on the main thread
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    testLayout[0].setBackButtonDismissible(true);
+                }
+            });
+            // CHECK the layout is not hidden
+            assertNotHidden(R.id.dismissible_root);
+            // The layout is focused
+            assertTrue(testActivity[0].getCurrentFocus() == testLayout[0]);
+            // WHEN back button pressed
+            testActivity[0].mConsumeBackButtonUp = false;
+            sendBackKey();
+            // AND the layout is hidden
+            assertHidden(R.id.dismissible_root);
+            // Back button up event is consumed without passing up to the activity
+            assertFalse(testActivity[0].mConsumeBackButtonUp);
+        }
+    }
+
 
     @Test
     public void testBackDismissWithRecyclerView() {
@@ -126,6 +186,10 @@
             sendBackKey();
             // AND hidden
             assertHidden(R.id.dismissible_root);
+            // Back button up event is consumed, and not pass to the activity
+            scenario.onActivity(activity -> {
+                assertFalse(activity.mConsumeBackButtonUp);
+            });
         }
     }
 
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTestActivity.java b/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTestActivity.java
index f9132b3..2b2158c 100644
--- a/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTestActivity.java
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/DismissibleFrameLayoutTestActivity.java
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.view.Gravity;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -29,6 +30,7 @@
 public class DismissibleFrameLayoutTestActivity extends LayoutTestActivity {
 
     public static final String EXTRA_LAYOUT_HORIZONTAL = "layout_horizontal";
+    public boolean mConsumeBackButtonUp = false;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -54,6 +56,14 @@
         recyclerView.setAdapter(new MyRecyclerViewAdapter());
     }
 
+    public boolean onKeyUp(int keyCode, KeyEvent evnet) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            mConsumeBackButtonUp = true;
+            return true;
+        }
+        return false;
+    }
+
     private static class MyRecyclerViewAdapter
             extends RecyclerView.Adapter<MyRecyclerViewAdapter.CustomViewHolder> {
         @Override
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/WearArcLayoutTest.kt b/wear/wear/src/androidTest/java/androidx/wear/widget/WearArcLayoutTest.kt
index 465ec78..57f0901 100644
--- a/wear/wear/src/androidTest/java/androidx/wear/widget/WearArcLayoutTest.kt
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/WearArcLayoutTest.kt
@@ -31,6 +31,7 @@
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import android.widget.TextView
+import androidx.core.view.children
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.espresso.Espresso
@@ -49,6 +50,9 @@
 import androidx.test.screenshot.assertAgainstGolden
 import androidx.wear.test.R
 import androidx.wear.widget.util.AsyncViewActions.waitForMatchingView
+import androidx.wear.widget.WearArcLayout.LayoutParams.VALIGN_CENTER
+import androidx.wear.widget.WearArcLayout.LayoutParams.VALIGN_OUTER
+import androidx.wear.widget.WearArcLayout.LayoutParams.VALIGN_INNER
 import org.hamcrest.CoreMatchers.allOf
 import org.hamcrest.CoreMatchers.any
 import org.hamcrest.Matcher
@@ -223,6 +227,100 @@
         )
     }
 
+    // Extension functions to make the margin test more readable.
+    fun WearArcLayout.addSeparator() {
+        addView(
+            WearCurvedTextView(ApplicationProvider.getApplicationContext()).apply {
+                text = " "
+                minSweepDegrees = 10f
+                setBackgroundColor(Color.rgb(100, 100, 100))
+                clockwise = true
+                textSize = 40f
+            }
+        )
+    }
+
+    fun WearArcLayout.addText(
+        text0: String,
+        color: Int,
+        marginLeft: Int = 0,
+        marginTop: Int = 0,
+        marginRight: Int = 0,
+        marginBottom: Int = 0,
+        vAlign: Int = VALIGN_CENTER
+    ) {
+        addView(
+            WearCurvedTextView(ApplicationProvider.getApplicationContext()).apply {
+                text = text0
+                setBackgroundColor(color)
+                clockwise = true
+                textSize = 14f
+                layoutParams = WearArcLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT
+                ).apply {
+                    setMargins(marginLeft, marginTop, marginRight, marginBottom)
+                    verticalAlignment = vAlign
+                }
+            }
+        )
+    }
+
+    private fun createArcWithMargin() =
+        WearArcLayout(ApplicationProvider.getApplicationContext()).apply {
+            anchorType = WearArcLayout.ANCHOR_CENTER
+            addSeparator()
+            addText("RI", Color.RED, marginTop = 16, vAlign = VALIGN_INNER)
+            addText("GI", Color.GREEN, marginTop = 8, marginBottom = 8, vAlign = VALIGN_INNER)
+            addText("BI", Color.BLUE, marginBottom = 16, vAlign = VALIGN_INNER)
+            addSeparator()
+            addText("Red", Color.RED, marginTop = 16)
+            addText("Green", Color.GREEN, marginTop = 8, marginBottom = 8)
+            addText("Blue", Color.BLUE, marginBottom = 16)
+            addSeparator()
+            addText("RO", Color.RED, marginTop = 16, vAlign = VALIGN_OUTER)
+            addText("GO", Color.GREEN, marginTop = 8, marginBottom = 8, vAlign = VALIGN_OUTER)
+            addText("BO", Color.BLUE, marginBottom = 16, vAlign = VALIGN_OUTER)
+            addSeparator()
+            addText("L", Color.WHITE, marginRight = 20)
+            addSeparator()
+            addText("C", Color.WHITE, marginRight = 10, marginLeft = 10)
+            addSeparator()
+            addText("R", Color.WHITE, marginLeft = 20)
+            addSeparator()
+        }
+
+    private fun createTwoArcsWithMargin() = listOf(
+        // First arc goes on top
+        createArcWithMargin(),
+
+        // Second arc in the bottom, and we change al children to go counterclockwise.
+        createArcWithMargin().apply {
+            anchorAngleDegrees = 180f
+            children.forEach {
+                (it as? WearCurvedTextView) ?.clockwise = false
+            }
+        }
+    )
+
+    @Test
+    fun testMargins() {
+        doOneTest(
+            "margin_test",
+            createTwoArcsWithMargin()
+        )
+    }
+
+    @Test
+    fun testMarginsCcw() {
+        doOneTest(
+            "margin_ccw_test",
+            createTwoArcsWithMargin().map {
+                it.apply { clockwise = false }
+            }
+        )
+    }
+
     // Generates a click in the x,y coordinates in the view's coordinate system.
     fun customClick(x: Float, y: Float) = ViewActions.actionWithAssertions(
         GeneralClickAction(
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/WearableRecyclerViewTest.java b/wear/wear/src/androidTest/java/androidx/wear/widget/WearableRecyclerViewTest.java
index b5ccb1e..840bb02 100644
--- a/wear/wear/src/androidTest/java/androidx/wear/widget/WearableRecyclerViewTest.java
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/WearableRecyclerViewTest.java
@@ -28,7 +28,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.app.Activity;
+import android.content.res.Configuration;
 import android.view.View;
 
 import androidx.annotation.IdRes;
@@ -38,10 +38,10 @@
 import androidx.test.espresso.action.GeneralSwipeAction;
 import androidx.test.espresso.action.Press;
 import androidx.test.espresso.action.Swipe;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 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.wear.test.R;
 import androidx.wear.widget.util.WakeLockRule;
 
@@ -64,8 +64,8 @@
     public final WakeLockRule wakeLock = new WakeLockRule();
 
     @Rule
-    public final ActivityTestRule<WearableRecyclerViewTestActivity> mActivityRule =
-            new ActivityTestRule<>(WearableRecyclerViewTestActivity.class, true, true);
+    public final ActivityScenarioRule<WearableRecyclerViewTestActivity> mActivityRule =
+            new ActivityScenarioRule<>(WearableRecyclerViewTestActivity.class);
 
     @Before
     public void setUp() {
@@ -74,88 +74,117 @@
 
     @Test
     public void testCaseInitState() {
-        WearableRecyclerView wrv = new WearableRecyclerView(mActivityRule.getActivity());
-        wrv.setLayoutManager(new WearableLinearLayoutManager(wrv.getContext()));
+        mActivityRule.getScenario().onActivity(activity -> {
+            WearableRecyclerView wrv = new WearableRecyclerView(activity);
+            wrv.setLayoutManager(new WearableLinearLayoutManager(wrv.getContext()));
 
-        assertFalse(wrv.isEdgeItemsCenteringEnabled());
-        assertFalse(wrv.isCircularScrollingGestureEnabled());
-        assertEquals(1.0f, wrv.getBezelFraction(), 0.01f);
-        assertEquals(180.0f, wrv.getScrollDegreesPerScreen(), 0.01f);
+            assertFalse(wrv.isEdgeItemsCenteringEnabled());
+            assertFalse(wrv.isCircularScrollingGestureEnabled());
+            assertEquals(1.0f, wrv.getBezelFraction(), 0.01f);
+            assertEquals(180.0f, wrv.getScrollDegreesPerScreen(), 0.01f);
+        });
     }
 
     @Test
     public void testEdgeItemsCenteringOnAndOff() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WearableRecyclerView wrv =
-                        (WearableRecyclerView) mActivityRule.getActivity().findViewById(R.id.wrv);
-                wrv.setEdgeItemsCenteringEnabled(true);
-            }
+
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv =
+                            (WearableRecyclerView) activity.findViewById(R.id.wrv);
+                    wrv.setEdgeItemsCenteringEnabled(true);
+                }
+            });
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv =
+                            (WearableRecyclerView) activity.findViewById(
+                                    R.id.wrv);
+                    View child = wrv.getChildAt(0);
+                    assertNotNull("child", child);
+                    Configuration configuration = activity.getResources().getConfiguration();
+                    if (configuration.isScreenRound()) {
+                        assertEquals((wrv.getHeight() - child.getHeight()) / 2, child.getTop());
+                    } else {
+                        assertEquals(0, child.getTop());
+                    }
+                }
+            });
+        });
+
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv =
+                            (WearableRecyclerView) activity.findViewById(
+                                    R.id.wrv);
+                    wrv.setEdgeItemsCenteringEnabled(false);
+                }
+            });
         });
 
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WearableRecyclerView wrv =
-                        (WearableRecyclerView) mActivityRule.getActivity().findViewById(R.id.wrv);
-                View child = wrv.getChildAt(0);
-                assertNotNull("child", child);
-                assertEquals((wrv.getHeight() - child.getHeight()) / 2, child.getTop());
-            }
-        });
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv =
+                            (WearableRecyclerView) activity.findViewById(
+                                    R.id.wrv);
+                    View child = wrv.getChildAt(0);
+                    assertNotNull("child", child);
+                    assertEquals(0, child.getTop());
 
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WearableRecyclerView wrv =
-                        (WearableRecyclerView) mActivityRule.getActivity().findViewById(R.id.wrv);
-                wrv.setEdgeItemsCenteringEnabled(false);
-            }
-        });
-
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WearableRecyclerView wrv =
-                        (WearableRecyclerView) mActivityRule.getActivity().findViewById(R.id.wrv);
-                View child = wrv.getChildAt(0);
-                assertNotNull("child", child);
-                assertEquals(0, child.getTop());
-
-            }
+                }
+            });
         });
     }
 
     @Test
     public void testEdgeItemsCenteringBeforeChildrenDrawn() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Activity activity = mActivityRule.getActivity();
-                WearableRecyclerView wrv = (WearableRecyclerView) activity.findViewById(R.id.wrv);
-                RecyclerView.Adapter<WearableRecyclerView.ViewHolder> adapter = wrv.getAdapter();
-                wrv.setAdapter(null);
-                wrv.setEdgeItemsCenteringEnabled(true);
-                wrv.setAdapter(adapter);
-            }
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv = (WearableRecyclerView) activity.findViewById(
+                            R.id.wrv);
+                    RecyclerView.Adapter<WearableRecyclerView.ViewHolder> adapter =
+                            wrv.getAdapter();
+                    wrv.setAdapter(null);
+                    wrv.setEdgeItemsCenteringEnabled(true);
+                    wrv.setAdapter(adapter);
+                }
+            });
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WearableRecyclerView wrv =
-                        (WearableRecyclerView) mActivityRule.getActivity().findViewById(R.id.wrv);
-                // Verify the first child
-                View child = wrv.getChildAt(0);
-                assertNotNull("child", child);
-                assertEquals((wrv.getHeight() - child.getHeight()) / 2, child.getTop());
-            }
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv =
+                            (WearableRecyclerView) activity.findViewById(
+                                    R.id.wrv);
+                    // Verify the first child
+                    View child = wrv.getChildAt(0);
+                    assertNotNull("child", child);
+                    Configuration configuration = activity.getResources().getConfiguration();
+                    if (configuration.isScreenRound()) {
+                        assertEquals((wrv.getHeight() - child.getHeight()) / 2, child.getTop());
+                    } else {
+                        assertEquals(0, child.getTop());
+                    }
+                }
+            });
         });
     }
 
@@ -163,20 +192,21 @@
     public void testCircularScrollingGesture() throws Throwable {
         onView(withId(R.id.wrv)).perform(swipeDownFromTopRight());
         assertNotScrolledY(R.id.wrv);
-        final WearableRecyclerView wrv =
-                (WearableRecyclerView) mActivityRule.getActivity().findViewById(
-                        R.id.wrv);
-        assertFalse(wrv.isCircularScrollingGestureEnabled());
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WearableRecyclerView wrv = (WearableRecyclerView)
-                        mActivityRule.getActivity().findViewById(R.id.wrv);
-                wrv.setCircularScrollingGestureEnabled(true);
-            }
+        mActivityRule.getScenario().onActivity(activity -> {
+            final WearableRecyclerView wrv =
+                    (WearableRecyclerView) activity.findViewById(
+                            R.id.wrv);
+            assertFalse(wrv.isCircularScrollingGestureEnabled());
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv = (WearableRecyclerView)
+                            activity.findViewById(R.id.wrv);
+                    wrv.setCircularScrollingGestureEnabled(true);
+                }
+            });
+            assertTrue(wrv.isCircularScrollingGestureEnabled());
         });
-        assertTrue(wrv.isCircularScrollingGestureEnabled());
-
         // Explicitly set the swipe to SLOW here to avoid problems with test failures on phone AVDs
         // with "Gesture navigation" enabled. This is not a particularly satisfactory fix to this
         // problem and ideally we should look to move these tests to use a watch AVD which should
@@ -187,36 +217,39 @@
 
     @Test
     public void testCurvedOffsettingHelper() throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WearableRecyclerView wrv =
-                        (WearableRecyclerView) mActivityRule.getActivity().findViewById(R.id.wrv);
-                wrv.setLayoutManager(new WearableLinearLayoutManager(wrv.getContext()));
-            }
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv =
+                            (WearableRecyclerView) activity.findViewById(
+                                    R.id.wrv);
+                    wrv.setLayoutManager(new WearableLinearLayoutManager(wrv.getContext()));
+                }
+            });
         });
-
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         onView(withId(R.id.wrv)).perform(swipeDownFromTopRight());
 
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Activity activity = mActivityRule.getActivity();
-                WearableRecyclerView wrv = (WearableRecyclerView) activity.findViewById(R.id.wrv);
-                if (activity.getResources().getConfiguration().isScreenRound()) {
-                    View child = wrv.getChildAt(0);
-                    assertTrue(child.getLeft() > 0);
-                } else {
-                    for (int i = 0; i < wrv.getChildCount(); i++) {
-                        assertEquals(0, wrv.getChildAt(i).getLeft());
+        mActivityRule.getScenario().onActivity(activity -> {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    WearableRecyclerView wrv = (WearableRecyclerView) activity.findViewById(
+                            R.id.wrv);
+                    if (activity.getResources().getConfiguration().isScreenRound()) {
+                        View child = wrv.getChildAt(0);
+                        assertTrue(child.getLeft() > 0);
+                    } else {
+                        for (int i = 0; i < wrv.getChildCount(); i++) {
+                            assertEquals(0, wrv.getChildAt(i).getLeft());
+                        }
                     }
                 }
-            }
+            });
         });
     }
-
     private static ViewAction swipeDownFromTopRightSlowly() {
         return new GeneralSwipeAction(
                 Swipe.SLOW, GeneralLocation.TOP_RIGHT,
diff --git a/wear/wear/src/main/java/androidx/wear/ambient/AmbientDelegate.java b/wear/wear/src/main/java/androidx/wear/ambient/AmbientDelegate.java
index fd6146c..74afaf3 100644
--- a/wear/wear/src/main/java/androidx/wear/ambient/AmbientDelegate.java
+++ b/wear/wear/src/main/java/androidx/wear/ambient/AmbientDelegate.java
@@ -51,7 +51,7 @@
          * method. If they do not, an exception will be thrown.</em>
          *
          * @param ambientDetails bundle containing information about the display being used.
-         *                      It includes information about low-bit color and burn-in protection.
+         *                       It includes information about low-bit color and burn-in protection.
          */
         void onEnterAmbient(Bundle ambientDetails);
 
@@ -81,15 +81,15 @@
     }
 
     AmbientDelegate(@Nullable Activity activity,
-                           @NonNull WearableControllerProvider wearableControllerProvider,
-                           @NonNull AmbientCallback callback) {
+            @NonNull WearableControllerProvider wearableControllerProvider,
+            @NonNull AmbientCallback callback) {
         mActivity = new WeakReference<>(activity);
         mCallback = callback;
         mWearableControllerProvider = wearableControllerProvider;
     }
 
     /**
-     * Receives and handles the onCreate call from the associated {@link AmbientMode}
+     * Receives and handles the onCreate call from the associated {@link AmbientModeSupport}
      */
     void onCreate() {
         Activity activity = mActivity.get();
@@ -103,7 +103,7 @@
     }
 
     /**
-     * Receives and handles the onResume call from the associated {@link AmbientMode}
+     * Receives and handles the onResume call from the associated {@link AmbientModeSupport}
      */
     void onResume() {
         if (mWearableController != null) {
@@ -112,7 +112,7 @@
     }
 
     /**
-     * Receives and handles the onPause call from the associated {@link AmbientMode}
+     * Receives and handles the onPause call from the associated {@link AmbientModeSupport}
      */
     void onPause() {
         if (mWearableController != null) {
@@ -121,7 +121,7 @@
     }
 
     /**
-     * Receives and handles the onStop call from the associated {@link AmbientMode}
+     * Receives and handles the onStop call from the associated {@link AmbientModeSupport}
      */
     void onStop() {
         if (mWearableController != null) {
@@ -130,7 +130,7 @@
     }
 
     /**
-     * Receives and handles the onDestroy call from the associated {@link AmbientMode}
+     * Receives and handles the onDestroy call from the associated {@link AmbientModeSupport}
      */
     void onDestroy() {
         if (mWearableController != null) {
diff --git a/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java b/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java
index b0eebcf..abcd52e 100644
--- a/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java
+++ b/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java
@@ -47,12 +47,12 @@
  * {@link FragmentActivity} and use the {@link AmbientController} can be found below:
  * <p>
  * <pre class="prettyprint">{@code
- *     AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
- *     boolean isAmbient =  controller.isAmbient();
+ *     AmbientModeSupport.AmbientController controller = AmbientModeSupport.attach(this);
+ *     boolean isAmbient = controller.isAmbient();
  * }</pre>
  */
 public final class AmbientModeSupport extends Fragment {
-    private static final String TAG = "AmbientMode";
+    private static final String TAG = "AmbientModeSupport";
 
     /**
      * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
@@ -82,11 +82,11 @@
 
     /**
      * Interface for any {@link Activity} that wishes to implement Ambient Mode. Use the
-     * {@link #getAmbientCallback()} method to return and {@link AmbientCallback} which can be used
+     * {@link #getAmbientCallback()} method to return an {@link AmbientCallback} which can be used
      * to bind the {@link AmbientModeSupport} to the instantiation of this interface.
      * <p>
      * <pre class="prettyprint">{@code
-     * return new AmbientMode.AmbientCallback() {
+     * return new AmbientModeSupport.AmbientCallback() {
      *     public void onEnterAmbient(Bundle ambientDetails) {...}
      *     public void onExitAmbient(Bundle ambientDetails) {...}
      *  }
@@ -101,7 +101,8 @@
     }
 
     /**
-     * Callback to receive ambient mode state changes. It must be used by all users of AmbientMode.
+     * Callback to receive ambient mode state changes. It must be used by all users of
+     * AmbientModeSupport.
      */
     public abstract static class AmbientCallback {
         /**
diff --git a/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivity.java b/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivity.java
index 6ac18bd..f2af9a3 100644
--- a/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivity.java
+++ b/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivity.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.drawable.Icon;
 import android.os.Build;
+import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 
 import androidx.annotation.DrawableRes;
@@ -83,6 +84,7 @@
         private PendingIntent mTouchIntent;
         private LocusIdCompat mLocusId;
         private int mOngoingActivityId = OngoingActivityData.DEFAULT_ID;
+        private String mCategory;
 
         /**
          * Construct a new empty {@link Builder}, associated with the given notification.
@@ -106,6 +108,8 @@
          * {@link OngoingActivity}. For example, in the WatchFace.
          * Should be white with a transparent background, preferably an AnimatedVectorDrawable.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setAnimatedIcon(@NonNull Icon animatedIcon) {
             mAnimatedIcon = animatedIcon;
@@ -117,6 +121,8 @@
          * {@link OngoingActivity}. For example, in the WatchFace.
          * Should be white with a transparent background, preferably an AnimatedVectorDrawable.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setAnimatedIcon(@DrawableRes int animatedIcon) {
             mAnimatedIcon = Icon.createWithResource(mContext, animatedIcon);
@@ -128,6 +134,8 @@
          * {@link OngoingActivity}, for example in the WatchFace in ambient mode.
          * Should be white with a transparent background, preferably an VectorDrawable.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setStaticIcon(@NonNull Icon staticIcon) {
             mStaticIcon = staticIcon;
@@ -139,6 +147,8 @@
          * {@link OngoingActivity}, for example in the WatchFace in ambient mode.
          * Should be white with a transparent background, preferably an VectorDrawable.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setStaticIcon(@DrawableRes int staticIcon) {
             mStaticIcon = Icon.createWithResource(mContext, staticIcon);
@@ -149,6 +159,8 @@
          * Set the initial status of this ongoing activity, the status may be displayed on the UI to
          * show progress of the Ongoing Activity.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setStatus(@NonNull OngoingActivityStatus status) {
             mStatus = status;
@@ -159,6 +171,8 @@
          * Set the intent to be used to go back to the activity when the user interacts with the
          * Ongoing Activity in other surfaces (for example, taps the Icon on the WatchFace)
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setTouchIntent(@NonNull PendingIntent touchIntent) {
             mTouchIntent = touchIntent;
@@ -169,6 +183,8 @@
          * Set the corresponding LocusId of this {@link OngoingActivity}, this will be used by the
          * launcher to identify the corresponding launcher item and display it accordingly.
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setLocusId(@NonNull LocusIdCompat locusId) {
             mLocusId = locusId;
@@ -179,6 +195,8 @@
          * Give an id to this {@link OngoingActivity}, as a way to reference it in
          * {@link OngoingActivity#fromExistingOngoingActivity(Context, int)}
          */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
         @NonNull
         public Builder setOngoingActivityId(int ongoingActivityId) {
             mOngoingActivityId = ongoingActivityId;
@@ -186,6 +204,18 @@
         }
 
         /**
+         * Set the category of this {@link OngoingActivity}, this may be used by the system to
+         * prioritize it.
+         */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // No getters needed on OngoingActivity - receiver will consume from OngoingActivityData.
+        @NonNull
+        public Builder setCategory(@NonNull String category) {
+            mCategory = category;
+            return this;
+        }
+
+        /**
          * Combine all options provided and the information in the notification if needed,
          * return a new {@link OngoingActivity} object.
          *
@@ -220,14 +250,18 @@
                 locusId = Api29Impl.getLocusId(notification);
             }
 
+            String category = mCategory == null ? notification.category : mCategory;
+
             return new OngoingActivity(mNotificationId, mNotificationBuilder,
                     new OngoingActivityData(
                         mAnimatedIcon,
                         staticIcon,
                         status,
                         touchIntent,
-                        locusId,
-                        mOngoingActivityId
+                        locusId == null ? null : locusId.getId(),
+                        mOngoingActivityId,
+                        category,
+                        SystemClock.elapsedRealtime()
                     ));
         }
     }
@@ -278,8 +312,8 @@
         StatusBarNotification[] notifications =
                 context.getSystemService(NotificationManager.class).getActiveNotifications();
         for (StatusBarNotification statusBarNotification : notifications) {
-            OngoingActivityData data = OngoingActivityData.create(
-                    statusBarNotification.getNotification());
+            OngoingActivityData data =
+                    OngoingActivityData.create(statusBarNotification.getNotification());
             if (data != null && filter.test(data)) {
                 return new OngoingActivity(statusBarNotification.getId(),
                         new NotificationCompat.Builder(context,
diff --git a/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityData.java b/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityData.java
index 17130b4..5b07295 100644
--- a/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityData.java
+++ b/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityData.java
@@ -18,6 +18,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.graphics.drawable.Icon;
+import android.os.SystemClock;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -35,34 +36,47 @@
 public class OngoingActivityData implements VersionedParcelable {
     @Nullable
     @ParcelField(value = 1, defaultValue = "null")
-    private final Icon mAnimatedIcon;
+    Icon mAnimatedIcon;
 
     @NonNull
     @ParcelField(value = 2)
-    private final Icon mStaticIcon;
+    Icon mStaticIcon;
 
     @Nullable
     @ParcelField(value = 3, defaultValue = "null")
-    private OngoingActivityStatus mStatus;
+    OngoingActivityStatus mStatus;
 
     @NonNull
     @ParcelField(value = 4)
-    private final PendingIntent mTouchIntent;
+    PendingIntent mTouchIntent;
 
     @Nullable
     @ParcelField(value = 5, defaultValue = "null")
-    private final LocusIdCompat mLocusId;
+    String mLocusId;
 
     @ParcelField(value = 6, defaultValue = "-1")
-    private final int mOngoingActivityId;
+    int mOngoingActivityId;
+
+    @Nullable
+    @ParcelField(value = 7, defaultValue = "null")
+    String mCategory;
+
+    @ParcelField(value = 8)
+    long mTimestamp;
+
+    // Required by VersionedParcelable
+    OngoingActivityData() {
+    }
 
     OngoingActivityData(
             @Nullable Icon animatedIcon,
             @NonNull Icon staticIcon,
             @Nullable OngoingActivityStatus status,
             @NonNull PendingIntent touchIntent,
-            @Nullable LocusIdCompat locusId,
-            int ongoingActivityId
+            @Nullable String locusId,
+            int ongoingActivityId,
+            @Nullable String category,
+            long timestamp
     ) {
         mAnimatedIcon = animatedIcon;
         mStaticIcon = staticIcon;
@@ -70,6 +84,8 @@
         mTouchIntent = touchIntent;
         mLocusId = locusId;
         mOngoingActivityId = ongoingActivityId;
+        mCategory = category;
+        mTimestamp = timestamp;
     }
 
     @NonNull
@@ -122,7 +138,8 @@
 
     /**
      * Get the static icon that can be used on some surfaces to represent this
-     * {@link OngoingActivity}. For example in the WatchFace in ambient mode.
+     * {@link OngoingActivity}. For example in the WatchFace in ambient mode. If not set, returns
+     *  the small icon of the corresponding Notification.
      */
     @NonNull
     public Icon getStaticIcon() {
@@ -131,7 +148,8 @@
 
     /**
      * Get the status of this ongoing activity, the status may be displayed on the UI to
-     * show progress of the Ongoing Activity.
+     * show progress of the Ongoing Activity. If not set, returns the content text of the
+     * corresponding Notification.
      */
     @Nullable
     public OngoingActivityStatus getStatus() {
@@ -140,7 +158,8 @@
 
     /**
      * Get the intent to be used to go back to the activity when the user interacts with the
-     * Ongoing Activity in other surfaces (for example, taps the Icon on the WatchFace)
+     * Ongoing Activity in other surfaces (for example, taps the Icon on the WatchFace). If not
+     * set, returns the touch intent of the corresponding Notification.
      */
     @NonNull
     public PendingIntent getTouchIntent() {
@@ -149,11 +168,12 @@
 
     /**
      * Get the LocusId of this {@link OngoingActivity}, this can be used by the launcher to
-     * identify the corresponding launcher item and display it accordingly.
+     * identify the corresponding launcher item and display it accordingly. If not set, returns
+     * the one in the corresponding Notification.
      */
     @Nullable
     public LocusIdCompat getLocusId() {
-        return mLocusId;
+        return new LocusIdCompat(mLocusId);
     }
 
     /**
@@ -164,6 +184,22 @@
         return mOngoingActivityId;
     }
 
+    /**
+     * Get the Category of this {@link OngoingActivity} if set, otherwise the category of the
+     * corresponding notification.
+     */
+    @Nullable
+    public String getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * Get the time (in {@link SystemClock#elapsedRealtime()} time) the OngoingActivity was built.
+     */
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
     // Status is mutable, by the library.
     void setStatus(@NonNull OngoingActivityStatus status) {
         mStatus = status;
diff --git a/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityStatus.java b/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityStatus.java
index fc37c9d..2dc53a7 100644
--- a/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityStatus.java
+++ b/wear/wear/src/main/java/androidx/wear/ongoingactivity/OngoingActivityStatus.java
@@ -21,6 +21,8 @@
 import androidx.versionedparcelable.VersionedParcelable;
 import androidx.versionedparcelable.VersionedParcelize;
 
+import kotlin.NotImplementedError;
+
 /**
  * Base class to serialize / deserialize {@link OngoingActivityStatus} into / from a Notification
  *
@@ -29,7 +31,12 @@
  * {@link android.os.SystemClock#elapsedRealtime()}
  */
 @VersionedParcelize
-public abstract class OngoingActivityStatus implements VersionedParcelable {
+public class OngoingActivityStatus implements VersionedParcelable {
+
+    // Required by VersionedParcelable
+    OngoingActivityStatus() {
+    }
+
     /**
      * Returns a textual representation of the ongoing activity status at the given time
      * represented as milliseconds timestamp
@@ -43,7 +50,9 @@
      *                      returned by {@link android.os.SystemClock#elapsedRealtime()}.
      */
     @NonNull
-    public abstract CharSequence getText(@NonNull Context context, long timeNowMillis);
+    public CharSequence getText(@NonNull Context context, long timeNowMillis) {
+        throw new NotImplementedError();
+    }
 
     /**
      * Returns the timestamp of the next time when the display will be different from the current
@@ -56,7 +65,9 @@
      * @return the first point in time after {@code fromTimeMillis} when the displayed value of
      * this status will change. returns Long.MAX_VALUE if the display will never change.
      */
-    public abstract long getNextChangeTimeMillis(long fromTimeMillis);
+    public long getNextChangeTimeMillis(long fromTimeMillis) {
+        throw new NotImplementedError();
+    }
 
     // Invalid value to use for paused_at and duration, as suggested by api guidelines 5.15
     static final long LONG_DEFAULT = -1L;
diff --git a/wear/wear/src/main/java/androidx/wear/ongoingactivity/TextOngoingActivityStatus.java b/wear/wear/src/main/java/androidx/wear/ongoingactivity/TextOngoingActivityStatus.java
index bceaded..d31e814 100644
--- a/wear/wear/src/main/java/androidx/wear/ongoingactivity/TextOngoingActivityStatus.java
+++ b/wear/wear/src/main/java/androidx/wear/ongoingactivity/TextOngoingActivityStatus.java
@@ -31,7 +31,11 @@
 public class TextOngoingActivityStatus extends OngoingActivityStatus {
     @NonNull
     @ParcelField(value = 1, defaultValue = "")
-    private String mStr = "";
+    String mStr = "";
+
+    // Required by VersionedParcelable
+    TextOngoingActivityStatus() {
+    }
 
     public TextOngoingActivityStatus(@NonNull String str) {
         this.mStr = str;
diff --git a/wear/wear/src/main/java/androidx/wear/ongoingactivity/TimerOngoingActivityStatus.java b/wear/wear/src/main/java/androidx/wear/ongoingactivity/TimerOngoingActivityStatus.java
index 524423e..79d76df 100644
--- a/wear/wear/src/main/java/androidx/wear/ongoingactivity/TimerOngoingActivityStatus.java
+++ b/wear/wear/src/main/java/androidx/wear/ongoingactivity/TimerOngoingActivityStatus.java
@@ -20,6 +20,7 @@
 import android.text.format.DateUtils;
 
 import androidx.annotation.NonNull;
+import androidx.versionedparcelable.NonParcelField;
 import androidx.versionedparcelable.ParcelField;
 import androidx.versionedparcelable.VersionedParcelize;
 
@@ -31,20 +32,26 @@
 @VersionedParcelize
 public class TimerOngoingActivityStatus extends OngoingActivityStatus {
     @ParcelField(value = 1, defaultValue = "0")
-    private long mTimeZeroMillis;
+    long mTimeZeroMillis;
 
     @ParcelField(value = 2, defaultValue = "false")
-    private boolean mCountDown = false;
+    boolean mCountDown = false;
 
     @ParcelField(value = 3, defaultValue = "-1")
-    private long mPausedAtMillis = LONG_DEFAULT;
+    long mPausedAtMillis = LONG_DEFAULT;
 
     @ParcelField(value = 4, defaultValue = "-1")
-    private long mTotalDurationMillis = LONG_DEFAULT;
+    long mTotalDurationMillis = LONG_DEFAULT;
 
+    @NonParcelField
     private final StringBuilder mStringBuilder = new StringBuilder(8);
+
     private static final String NEGATIVE_DURATION_PREFIX = "-";
 
+    // Required by VersionedParcelable
+    TimerOngoingActivityStatus() {
+    }
+
     /**
      * Create a Status representing a timer or stopwatch.
      *
diff --git a/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java b/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java
index 2a78640..7648800 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/BackButtonDismissController.java
@@ -25,6 +25,8 @@
 import androidx.annotation.UiThread;
 import androidx.wear.utils.ActivityAnimationUtil;
 
+import org.jetbrains.annotations.NotNull;
+
 /**
  * Controller that handles the back button click for dismiss the frame layout
  *
@@ -37,22 +39,26 @@
     BackButtonDismissController(Context context, DismissibleFrameLayout layout) {
         super(context, layout);
 
-        // Dismiss upon back button press
+        // set this to true will also ensure that this view is focusable
         layout.setFocusableInTouchMode(true);
+        // Dismiss upon back button press
         layout.requestFocus();
         layout.setOnKeyListener(
-                (view, keyCode, event) -> {
-                    if (keyCode == KeyEvent.KEYCODE_BACK
-                            && event.getAction() == KeyEvent.ACTION_UP) {
-                        dismiss();
-                        return true;
-                    }
-                    return false;
-                });
+                (view, keyCode, event) -> keyCode == KeyEvent.KEYCODE_BACK
+                        && event.getAction() == KeyEvent.ACTION_UP
+                        && dismiss());
     }
 
-    private void dismiss() {
-        if (mDismissListener == null) return;
+    void disable(@NotNull DismissibleFrameLayout layout) {
+        setOnDismissListener(null);
+        layout.setOnKeyListener(null);
+        // setting this to false will also ensure that this view is not focusable in touch mode
+        layout.setFocusable(false);
+        layout.clearFocus();
+    }
+
+    private boolean dismiss() {
+        if (mDismissListener == null) return false;
 
         Animation exitAnimation = ActivityAnimationUtil.getStandardActivityAnimation(
                 mContext, ActivityAnimationUtil.CLOSE_EXIT,
@@ -79,5 +85,6 @@
             mDismissListener.onDismissStarted();
             mDismissListener.onDismissed();
         }
+        return true;
     }
 }
diff --git a/wear/wear/src/main/java/androidx/wear/widget/DismissibleFrameLayout.java b/wear/wear/src/main/java/androidx/wear/widget/DismissibleFrameLayout.java
index ce76639..448df50 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/DismissibleFrameLayout.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/DismissibleFrameLayout.java
@@ -192,7 +192,7 @@
                 mBackButtonDismissController.setOnDismissListener(mDismissListener);
             }
         } else if (mBackButtonDismissController != null) {
-            mBackButtonDismissController.setOnDismissListener(null);
+            mBackButtonDismissController.disable(this);
             mBackButtonDismissController = null;
         }
     }
diff --git a/wear/wear/src/main/java/androidx/wear/widget/WearArcLayout.java b/wear/wear/src/main/java/androidx/wear/widget/WearArcLayout.java
index 896d15c..3a5cc6c 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/WearArcLayout.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/WearArcLayout.java
@@ -111,7 +111,7 @@
      *
      * <p>Note that the {@code rotate} parameter is ignored when drawing "Fullscreen" elements.
      */
-    public static class LayoutParams extends ViewGroup.LayoutParams {
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
 
         /** Vertical alignment of elements within the arc. */
         /** @hide */
@@ -256,6 +256,8 @@
     // Temporary variables using during a draw cycle.
     private float mCurrentCumulativeAngle = 0;
     private int mAnglesIndex = 0;
+    @SuppressWarnings("SyntheticAccessor")
+    private final ChildArcAngles mChildArcAngles = new ChildArcAngles();
 
     public WearArcLayout(@NonNull Context context) {
         this(context, null);
@@ -349,18 +351,22 @@
 
             // ArcLayoutWidget is a special case. Because of how it draws, fit it to the size
             // of the whole widget.
+            int childMeasuredHeight;
             if (child instanceof ArcLayoutWidget) {
-                ArcLayoutWidget widget = (ArcLayoutWidget) child;
-                maxChildHeightPx = max(maxChildHeightPx, widget.getThicknessPx());
+                childMeasuredHeight = ((ArcLayoutWidget) child).getThicknessPx();
             } else {
                 measureChild(
                         child,
                         getChildMeasureSpec(childMeasureSpec, 0, child.getLayoutParams().width),
                         getChildMeasureSpec(childMeasureSpec, 0, child.getLayoutParams().height)
                 );
-                maxChildHeightPx = max(maxChildHeightPx, child.getMeasuredHeight());
+                childMeasuredHeight = child.getMeasuredHeight();
                 childState = combineMeasuredStates(childState, child.getMeasuredState());
+
             }
+            LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
+            maxChildHeightPx = max(maxChildHeightPx, childMeasuredHeight
+                    + childLayoutParams.topMargin +  childLayoutParams.bottomMargin);
         }
 
         mThicknessPx = maxChildHeightPx;
@@ -374,23 +380,14 @@
             }
 
             if (child instanceof ArcLayoutWidget) {
-                ArcLayoutWidget curvedContainerChild = (ArcLayoutWidget) child;
                 LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
 
-                int childThicknessPx = curvedContainerChild.getThicknessPx();
-                int thicknessDiffPx = mThicknessPx - childThicknessPx;
-
-                float insetPx = 0;
-
-                if (childLayoutParams.getVerticalAlignment() == LayoutParams.VALIGN_CENTER) {
-                    insetPx = thicknessDiffPx / 2f;
-                } else if (childLayoutParams.getVerticalAlignment() == LayoutParams.VALIGN_INNER) {
-                    insetPx = thicknessDiffPx;
-                }
+                float insetPx = getChildTopInset(child);
 
                 int innerChildMeasureSpec =
                         MeasureSpec.makeMeasureSpec(
                                 maxChildDimension * 2 - round(insetPx * 2), MeasureSpec.EXACTLY);
+
                 measureChild(
                         child,
                         getChildMeasureSpec(innerChildMeasureSpec, 0, childLayoutParams.width),
@@ -435,7 +432,7 @@
                 // vertical position.
                 int leftPx =
                         round((getMeasuredWidth() / 2f) - (child.getMeasuredWidth() / 2f));
-                int topPx = getChildTopInset(child);
+                int topPx = round(getChildTopInset(child));
 
                 child.layout(
                         leftPx,
@@ -542,12 +539,13 @@
         // Rotate the canvas to make the children render in the right place.
         canvas.save();
 
-        float arcAngle = calculateArcAngle(child);
-        float preRotation = arcAngle / 2f;
+        calculateArcAngle(child, mChildArcAngles);
+        float preRotation = mChildArcAngles.leftMarginAsAngle
+                + mChildArcAngles.actualChildAngle / 2f;
         float multiplier = mClockwise ? 1f : -1f;
 
         // Store the center angle of each child to handle touch events.
-        float middleAngle = multiplier * (mCurrentCumulativeAngle + arcAngle / 2);
+        float middleAngle = multiplier * (mCurrentCumulativeAngle + preRotation);
         mAngles[mAnglesIndex++] = middleAngle;
         if (child == mTouchedView) {
             // We keep this updated, in case the view has changed angle.
@@ -583,7 +581,7 @@
 
             // Do the actual rotation. Note that the strange rotation center is because the child
             // view is x-centered but at the top of this container.
-            int childInset = getChildTopInset(child);
+            float childInset = getChildTopInset(child);
             canvas.rotate(
                     angleToRotate,
                     getMeasuredWidth() / 2f,
@@ -591,7 +589,7 @@
             );
         }
 
-        mCurrentCumulativeAngle += arcAngle;
+        mCurrentCumulativeAngle += mChildArcAngles.getTotalAngle();
 
         boolean wasInvalidateIssued = super.drawChild(canvas, child, drawingTime);
 
@@ -609,7 +607,8 @@
         float totalArcAngle = 0;
 
         for (int i = 0; i < getChildCount(); i++) {
-            totalArcAngle += calculateArcAngle(getChildAt(i));
+            calculateArcAngle(getChildAt(i), mChildArcAngles);
+            totalArcAngle += mChildArcAngles.getTotalAngle();
         }
 
         if (mAnchorType == ANCHOR_CENTER) {
@@ -621,31 +620,55 @@
         return 0;
     }
 
-    private float calculateArcAngle(@NonNull View view) {
+    private static float widthToAngleDegrees(float widthPx, float radiusPx) {
+        return (float) Math.toDegrees(2 * asin(widthPx / radiusPx / 2f));
+    }
+
+    private void calculateArcAngle(@NonNull View view, @NonNull ChildArcAngles childAngles) {
         if (view.getVisibility() == GONE) {
-            return 0f;
+            childAngles.leftMarginAsAngle = 0;
+            childAngles.rightMarginAsAngle = 0;
+            childAngles.actualChildAngle = 0;
+            return;
         }
 
+        float radiusPx = (getMeasuredWidth() / 2f) - mThicknessPx;
+
+        LayoutParams childLayoutParams = (LayoutParams) view.getLayoutParams();
+
+        childAngles.leftMarginAsAngle =
+                widthToAngleDegrees(childLayoutParams.leftMargin, radiusPx);
+        childAngles.rightMarginAsAngle =
+                widthToAngleDegrees(childLayoutParams.rightMargin, radiusPx);
+
         if (view instanceof ArcLayoutWidget) {
-            return ((ArcLayoutWidget) view).getSweepAngleDegrees();
+            childAngles.actualChildAngle = ((ArcLayoutWidget) view).getSweepAngleDegrees();
         } else {
-            float radiusPx = (getMeasuredWidth() / 2f) - mThicknessPx;
-            return (float) Math.toDegrees(2 * asin(view.getMeasuredWidth() / radiusPx / 2f));
+            childAngles.actualChildAngle =
+                    widthToAngleDegrees(view.getMeasuredWidth(), radiusPx);
         }
     }
 
-    private int getChildTopInset(@NonNull View child) {
+    private float getChildTopInset(@NonNull View child) {
         LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
 
-        int thicknessDiffPx = mThicknessPx - child.getMeasuredHeight();
+        int childHeight = child instanceof ArcLayoutWidget
+                ? ((ArcLayoutWidget) child).getThicknessPx()
+                : child.getMeasuredHeight();
+
+        int thicknessDiffPx =
+                mThicknessPx - childLayoutParams.topMargin - childLayoutParams.bottomMargin
+                        - childHeight;
+
+        int margin = mClockwise ? childLayoutParams.topMargin : childLayoutParams.bottomMargin;
 
         switch (childLayoutParams.getVerticalAlignment()) {
             case LayoutParams.VALIGN_OUTER:
-                return 0;
+                return margin;
             case LayoutParams.VALIGN_CENTER:
-                return round(thicknessDiffPx / 2f);
+                return margin + thicknessDiffPx / 2f;
             case LayoutParams.VALIGN_INNER:
-                return thicknessDiffPx;
+                return margin + thicknessDiffPx;
             default:
                 // Nortmally unreachable...
                 return 0;
@@ -713,4 +736,14 @@
         mClockwise = clockwise;
         invalidate();
     }
+
+    private static class ChildArcAngles {
+        public float leftMarginAsAngle;
+        public float rightMarginAsAngle;
+        public float actualChildAngle;
+
+        public float getTotalAngle() {
+            return leftMarginAsAngle + rightMarginAsAngle + actualChildAngle;
+        }
+    }
 }
diff --git a/wear/wear/src/main/java/androidx/wear/widget/WearableRecyclerView.java b/wear/wear/src/main/java/androidx/wear/widget/WearableRecyclerView.java
index 07da857..a81a4d7c 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/WearableRecyclerView.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/WearableRecyclerView.java
@@ -232,15 +232,20 @@
     }
 
     /**
-     * Use this method to configure the {@link WearableRecyclerView} to always align the first and
-     * last items with the vertical center of the screen. This effectively moves the start and end
-     * of the list to the middle of the screen if the user has scrolled so far. It takes the height
-     * of the children into account so that they are correctly centered.
+     * Use this method to configure the {@link WearableRecyclerView} on round watches to always
+     * align the first and last items with the vertical center of the screen. This effectively moves
+     * the start and end of the list to the middle of the screen if the user has scrolled so far.
+     * It takes the height of the children into account so that they are correctly centered.
+     * On nonRound watches, this method has no effect and original padding is used.
      *
      * @param isEnabled set to true if you wish to align the edge children (first and last)
      *                        with the center of the screen.
      */
     public void setEdgeItemsCenteringEnabled(boolean isEnabled) {
+        if (!getResources().getConfiguration().isScreenRound()) {
+            mEdgeItemsCenteringEnabled = false;
+            return;
+        }
         mEdgeItemsCenteringEnabled = isEnabled;
         if (mEdgeItemsCenteringEnabled) {
             if (getChildCount() > 0) {
diff --git a/window/window-extensions/api/current.txt b/window/window-extensions/api/current.txt
index 01608ca..78bdc7f 100644
--- a/window/window-extensions/api/current.txt
+++ b/window/window-extensions/api/current.txt
@@ -11,10 +11,18 @@
     field public static final int POSTURE_UNKNOWN = 0; // 0x0
   }
 
-  public class ExtensionDisplayFeature {
-    ctor public ExtensionDisplayFeature(android.graphics.Rect, int);
+  public interface ExtensionDisplayFeature {
     method public android.graphics.Rect getBounds();
+  }
+
+  public class ExtensionFoldingFeature implements androidx.window.extensions.ExtensionDisplayFeature {
+    ctor public ExtensionFoldingFeature(android.graphics.Rect, int, int);
+    method public android.graphics.Rect getBounds();
+    method public int getState();
     method public int getType();
+    field public static final int STATE_FLAT = 1; // 0x1
+    field public static final int STATE_FLIPPED = 3; // 0x3
+    field public static final int STATE_HALF_OPENED = 2; // 0x2
     field public static final int TYPE_FOLD = 1; // 0x1
     field public static final int TYPE_HINGE = 2; // 0x2
   }
diff --git a/window/window-extensions/api/public_plus_experimental_current.txt b/window/window-extensions/api/public_plus_experimental_current.txt
index 01608ca..78bdc7f 100644
--- a/window/window-extensions/api/public_plus_experimental_current.txt
+++ b/window/window-extensions/api/public_plus_experimental_current.txt
@@ -11,10 +11,18 @@
     field public static final int POSTURE_UNKNOWN = 0; // 0x0
   }
 
-  public class ExtensionDisplayFeature {
-    ctor public ExtensionDisplayFeature(android.graphics.Rect, int);
+  public interface ExtensionDisplayFeature {
     method public android.graphics.Rect getBounds();
+  }
+
+  public class ExtensionFoldingFeature implements androidx.window.extensions.ExtensionDisplayFeature {
+    ctor public ExtensionFoldingFeature(android.graphics.Rect, int, int);
+    method public android.graphics.Rect getBounds();
+    method public int getState();
     method public int getType();
+    field public static final int STATE_FLAT = 1; // 0x1
+    field public static final int STATE_FLIPPED = 3; // 0x3
+    field public static final int STATE_HALF_OPENED = 2; // 0x2
     field public static final int TYPE_FOLD = 1; // 0x1
     field public static final int TYPE_HINGE = 2; // 0x2
   }
diff --git a/window/window-extensions/api/restricted_current.txt b/window/window-extensions/api/restricted_current.txt
index 01608ca..78bdc7f 100644
--- a/window/window-extensions/api/restricted_current.txt
+++ b/window/window-extensions/api/restricted_current.txt
@@ -11,10 +11,18 @@
     field public static final int POSTURE_UNKNOWN = 0; // 0x0
   }
 
-  public class ExtensionDisplayFeature {
-    ctor public ExtensionDisplayFeature(android.graphics.Rect, int);
+  public interface ExtensionDisplayFeature {
     method public android.graphics.Rect getBounds();
+  }
+
+  public class ExtensionFoldingFeature implements androidx.window.extensions.ExtensionDisplayFeature {
+    ctor public ExtensionFoldingFeature(android.graphics.Rect, int, int);
+    method public android.graphics.Rect getBounds();
+    method public int getState();
     method public int getType();
+    field public static final int STATE_FLAT = 1; // 0x1
+    field public static final int STATE_FLIPPED = 3; // 0x3
+    field public static final int STATE_HALF_OPENED = 2; // 0x2
     field public static final int TYPE_FOLD = 1; // 0x1
     field public static final int TYPE_HINGE = 2; // 0x2
   }
diff --git a/window/window-extensions/build.gradle b/window/window-extensions/build.gradle
index 82c95ec..6e84dbc 100644
--- a/window/window-extensions/build.gradle
+++ b/window/window-extensions/build.gradle
@@ -19,6 +19,13 @@
 import androidx.build.Publish
 import androidx.build.RunApiTasks
 
+import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_EXT_JUNIT
+import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RULES
+import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RUNNER
+import static androidx.build.dependencies.DependenciesKt.DEXMAKER_MOCKITO
+import static androidx.build.dependencies.DependenciesKt.MOCKITO_CORE
+import static androidx.build.dependencies.DependenciesKt.TRUTH
+
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
@@ -32,6 +39,12 @@
 
 dependencies {
     implementation("androidx.annotation:annotation:1.1.0")
+
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_RUNNER)
+    androidTestImplementation(ANDROIDX_TEST_RULES)
+    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
+    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy)
 }
 
 androidx {
diff --git a/car/app/app/src/androidTest/AndroidManifest.xml b/window/window-extensions/src/androidTest/AndroidManifest.xml
similarity index 68%
copy from car/app/app/src/androidTest/AndroidManifest.xml
copy to window/window-extensions/src/androidTest/AndroidManifest.xml
index 3bc2684..d85d231 100644
--- a/car/app/app/src/androidTest/AndroidManifest.xml
+++ b/window/window-extensions/src/androidTest/AndroidManifest.xml
@@ -15,5 +15,11 @@
   limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="androidx.car.app">
+    package="androidx.window.extensions.test">
+
+    <application>
+        <activity android:name="androidx.window.extensions.TestActivity" />
+        <activity android:name="androidx.window.extensions.TestConfigChangeHandlingActivity"
+            android:configChanges="orientation|screenLayout|screenSize"/>
+    </application>
 </manifest>
diff --git a/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionDeviceStateTest.java b/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionDeviceStateTest.java
new file mode 100644
index 0000000..3299a08
--- /dev/null
+++ b/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionDeviceStateTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.window.extensions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link ExtensionDeviceState} class. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ExtensionDeviceStateTest {
+
+    @Test
+    public void testEquals_samePosture() {
+        ExtensionDeviceState original = new ExtensionDeviceState(0);
+        ExtensionDeviceState copy = new ExtensionDeviceState(0);
+
+        assertEquals(original, copy);
+    }
+
+    @Test
+    public void testEquals_differentPosture() {
+        ExtensionDeviceState original = new ExtensionDeviceState(0);
+        ExtensionDeviceState different = new ExtensionDeviceState(1);
+
+        assertNotEquals(original, different);
+    }
+
+    @Test
+    public void testHashCode_matchesIfEqual() {
+        int posture = 111;
+        ExtensionDeviceState original = new ExtensionDeviceState(posture);
+        ExtensionDeviceState matching = new ExtensionDeviceState(posture);
+
+        assertEquals(original, matching);
+        assertEquals(original.hashCode(), matching.hashCode());
+    }
+}
diff --git a/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionDisplayFeatureTest.java b/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionDisplayFeatureTest.java
new file mode 100644
index 0000000..fdd42fa
--- /dev/null
+++ b/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionDisplayFeatureTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.window.extensions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Rect;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link ExtensionFoldingFeature} class. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ExtensionDisplayFeatureTest {
+
+    @Test
+    public void testEquals_sameAttributes() {
+        Rect bounds = new Rect(1, 0, 1, 10);
+        int type = ExtensionFoldingFeature.TYPE_FOLD;
+        int state = ExtensionFoldingFeature.STATE_FLAT;
+
+        ExtensionFoldingFeature original = new ExtensionFoldingFeature(bounds, type, state);
+        ExtensionFoldingFeature copy = new ExtensionFoldingFeature(bounds, type, state);
+
+        assertEquals(original, copy);
+    }
+
+    @Test
+    public void testEquals_differentRect() {
+        Rect originalRect = new Rect(1, 0, 1, 10);
+        Rect otherRect = new Rect(2, 0, 2, 10);
+        int type = ExtensionFoldingFeature.TYPE_FOLD;
+        int state = ExtensionFoldingFeature.STATE_FLAT;
+
+        ExtensionFoldingFeature original = new ExtensionFoldingFeature(originalRect, type,
+                state);
+        ExtensionFoldingFeature other = new ExtensionFoldingFeature(otherRect, type, state);
+
+        assertNotEquals(original, other);
+    }
+
+    @Test
+    public void testEquals_differentType() {
+        Rect rect = new Rect(1, 0, 1, 10);
+        int originalType = ExtensionFoldingFeature.TYPE_FOLD;
+        int otherType = ExtensionFoldingFeature.TYPE_HINGE;
+        int state = ExtensionFoldingFeature.STATE_FLAT;
+
+        ExtensionFoldingFeature original = new ExtensionFoldingFeature(rect, originalType,
+                state);
+        ExtensionFoldingFeature other = new ExtensionFoldingFeature(rect, otherType, state);
+
+        assertNotEquals(original, other);
+    }
+
+    @Test
+    public void testEquals_differentState() {
+        Rect rect = new Rect(1, 0, 1, 10);
+        int type = ExtensionFoldingFeature.TYPE_FOLD;
+        int originalState = ExtensionFoldingFeature.STATE_FLAT;
+        int otherState = ExtensionFoldingFeature.STATE_FLIPPED;
+
+        ExtensionFoldingFeature original = new ExtensionFoldingFeature(rect, type,
+                originalState);
+        ExtensionFoldingFeature other = new ExtensionFoldingFeature(rect, type, otherState);
+
+        assertNotEquals(original, other);
+    }
+
+    @Test
+    public void testHashCode_matchesIfEqual() {
+        Rect originalRect = new Rect(1, 0, 1, 10);
+        Rect matchingRect = new Rect(1, 0, 1, 10);
+        int type = ExtensionFoldingFeature.TYPE_FOLD;
+        int state = ExtensionFoldingFeature.STATE_FLAT;
+
+        ExtensionFoldingFeature original = new ExtensionFoldingFeature(originalRect, type,
+                state);
+        ExtensionFoldingFeature matching = new ExtensionFoldingFeature(matchingRect, type,
+                state);
+
+        assertEquals(original, matching);
+        assertEquals(original.hashCode(), matching.hashCode());
+    }
+}
diff --git a/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionWindowLayoutInfoTest.java b/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionWindowLayoutInfoTest.java
new file mode 100644
index 0000000..bb87c3b
--- /dev/null
+++ b/window/window-extensions/src/androidTest/java/androidx/window/extensions/ExtensionWindowLayoutInfoTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.window.extensions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Rect;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Tests for {@link ExtensionWindowLayoutInfo} class. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ExtensionWindowLayoutInfoTest {
+
+    @Test
+    public void testEquals_sameFeatures() {
+        List<ExtensionDisplayFeature> features = new ArrayList<>();
+
+        ExtensionWindowLayoutInfo original = new ExtensionWindowLayoutInfo(features);
+        ExtensionWindowLayoutInfo copy = new ExtensionWindowLayoutInfo(features);
+
+        assertEquals(original, copy);
+    }
+
+    @Test
+    public void testEquals_differentFeatures() {
+        List<ExtensionDisplayFeature> originalFeatures = new ArrayList<>();
+        List<ExtensionDisplayFeature> differentFeatures = new ArrayList<>();
+        Rect rect = new Rect(1, 0, 1, 10);
+        differentFeatures.add(new ExtensionFoldingFeature(
+                rect, ExtensionFoldingFeature.TYPE_HINGE,
+                ExtensionFoldingFeature.STATE_FLAT));
+
+        ExtensionWindowLayoutInfo original = new ExtensionWindowLayoutInfo(originalFeatures);
+        ExtensionWindowLayoutInfo different = new ExtensionWindowLayoutInfo(differentFeatures);
+
+        assertNotEquals(original, different);
+    }
+
+    @Test
+    public void testHashCode_matchesIfEqual() {
+        List<ExtensionDisplayFeature> firstFeatures = new ArrayList<>();
+        List<ExtensionDisplayFeature> secondFeatures = new ArrayList<>();
+        ExtensionWindowLayoutInfo first = new ExtensionWindowLayoutInfo(firstFeatures);
+        ExtensionWindowLayoutInfo second = new ExtensionWindowLayoutInfo(secondFeatures);
+
+        assertEquals(first, second);
+        assertEquals(first.hashCode(), second.hashCode());
+    }
+
+    @Test
+    public void testHashCode_matchesIfEqualFeatures() {
+        ExtensionDisplayFeature originalFeature = new ExtensionFoldingFeature(
+                new Rect(0, 0, 100, 0),
+                ExtensionFoldingFeature.TYPE_HINGE,
+                ExtensionFoldingFeature.STATE_FLAT
+        );
+        ExtensionDisplayFeature matchingFeature = new ExtensionFoldingFeature(
+                new Rect(0, 0, 100, 0),
+                ExtensionFoldingFeature.TYPE_HINGE,
+                ExtensionFoldingFeature.STATE_FLAT
+        );
+        List<ExtensionDisplayFeature> firstFeatures = Collections.singletonList(originalFeature);
+        List<ExtensionDisplayFeature> secondFeatures = Collections.singletonList(matchingFeature);
+        ExtensionWindowLayoutInfo first = new ExtensionWindowLayoutInfo(firstFeatures);
+        ExtensionWindowLayoutInfo second = new ExtensionWindowLayoutInfo(secondFeatures);
+
+        assertEquals(first, second);
+        assertEquals(first.hashCode(), second.hashCode());
+    }
+}
diff --git a/window/window-extensions/src/androidTest/java/androidx/window/extensions/TestActivity.java b/window/window-extensions/src/androidTest/java/androidx/window/extensions/TestActivity.java
new file mode 100644
index 0000000..1c36baf
--- /dev/null
+++ b/window/window-extensions/src/androidTest/java/androidx/window/extensions/TestActivity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.window.extensions;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+
+public class TestActivity extends Activity implements View.OnLayoutChangeListener {
+
+    private int mRootViewId;
+    private CountDownLatch mLayoutLatch = new CountDownLatch(1);
+    private static CountDownLatch sResumeLatch = new CountDownLatch(1);
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final View contentView = new View(this);
+        mRootViewId = View.generateViewId();
+        contentView.setId(mRootViewId);
+        setContentView(contentView);
+
+        getWindow().getDecorView().addOnLayoutChangeListener(this);
+    }
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        mLayoutLatch.countDown();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        sResumeLatch.countDown();
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ExperimentalPointerInput.kt b/window/window-extensions/src/androidTest/java/androidx/window/extensions/TestConfigChangeHandlingActivity.java
similarity index 74%
copy from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ExperimentalPointerInput.kt
copy to window/window-extensions/src/androidTest/java/androidx/window/extensions/TestConfigChangeHandlingActivity.java
index 0bfd44c..f0a2871 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ExperimentalPointerInput.kt
+++ b/window/window-extensions/src/androidTest/java/androidx/window/extensions/TestConfigChangeHandlingActivity.java
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.gesture
+package androidx.window.extensions;
 
-@RequiresOptIn(
-    "This pointer input API is experimental and is likely to change before becoming " +
-        "stable."
-)
-annotation class ExperimentalPointerInput
\ No newline at end of file
+/** Activity that handles orientation configuration change. */
+public final class TestConfigChangeHandlingActivity extends TestActivity {
+}
diff --git a/window/window-extensions/src/androidTest/java/androidx/window/extensions/WindowTestBase.java b/window/window-extensions/src/androidTest/java/androidx/window/extensions/WindowTestBase.java
new file mode 100644
index 0000000..b68fc23f
--- /dev/null
+++ b/window/window-extensions/src/androidTest/java/androidx/window/extensions/WindowTestBase.java
@@ -0,0 +1,40 @@
+/*
+ * 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.window.extensions;
+
+import android.app.Activity;
+import android.os.IBinder;
+
+import androidx.test.core.app.ActivityScenario;
+
+import org.junit.Before;
+
+/**
+ * Base class for all tests in the module.
+ */
+class WindowTestBase {
+    ActivityScenario<TestActivity> mActivityTestRule;
+
+    @Before
+    public void setUp() {
+        mActivityTestRule = ActivityScenario.launch(TestActivity.class);
+    }
+
+    static IBinder getActivityWindowToken(Activity activity) {
+        return activity.getWindow().getAttributes().token;
+    }
+}
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDeviceState.java b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDeviceState.java
index be7ce85..f2f1481 100644
--- a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDeviceState.java
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDeviceState.java
@@ -43,8 +43,6 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
-            POSTURE_UNKNOWN,
-            POSTURE_CLOSED,
             POSTURE_HALF_OPENED,
             POSTURE_OPENED,
             POSTURE_FLIPPED
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDisplayFeature.java b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDisplayFeature.java
index 8bbd56d..59794d9 100644
--- a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDisplayFeature.java
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionDisplayFeature.java
@@ -18,110 +18,19 @@
 
 import android.graphics.Rect;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 
 /**
  * Description of a physical feature on the display.
  */
-public class ExtensionDisplayFeature {
+public interface ExtensionDisplayFeature {
+
     /**
-     * The bounding rectangle of the feature within the application window in the window
-     * coordinate space.
+     * The bounding rectangle of the feature within the application window
+     * in the window coordinate space.
      *
-     * <p>The bounds for features of type {@link #TYPE_FOLD fold} must be zero-high (for
-     * horizontal folds) or zero-wide (for vertical folds) and span the entire window.
-     *
-     * <p>The bounds for features of type {@link #TYPE_HINGE hinge} must span the entire window
-     * but, unlike folds, can have a non-zero area which represents the region that is occluded by
-     * the hinge and not visible to the user.
+     * @return bounds of display feature.
      */
     @NonNull
-    private final Rect mBounds;
-
-    /**
-     * The physical type of the feature.
-     */
-    @Type
-    private final int mType;
-
-    /**
-     * A fold in the flexible screen without a physical gap.
-     */
-    public static final int TYPE_FOLD = 1;
-
-    /**
-     * A physical separation with a hinge that allows two display panels to fold.
-     */
-    public static final int TYPE_HINGE = 2;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            TYPE_FOLD,
-            TYPE_HINGE,
-    })
-    @interface Type{}
-
-    public ExtensionDisplayFeature(@NonNull Rect bounds, @Type int type) {
-        mBounds = new Rect(bounds);
-        mType = type;
-    }
-
-    /** Gets the bounding rect of the display feature in window coordinate space. */
-    @NonNull
-    public Rect getBounds() {
-        return new Rect(mBounds);
-    }
-
-    /** Gets the type of the display feature. */
-    @Type
-    public int getType() {
-        return mType;
-    }
-
-    @NonNull
-    private static String typeToString(int type) {
-        switch (type) {
-            case TYPE_FOLD:
-                return "FOLD";
-            case TYPE_HINGE:
-                return "HINGE";
-            default:
-                return "Unknown feature type (" + type + ")";
-        }
-    }
-
-    @NonNull
-    @Override
-    public String toString() {
-        return "ExtensionDisplayFeature { bounds=" + mBounds + ", type=" + typeToString(getType())
-                + " }";
-    }
-
-    @Override
-    public boolean equals(@Nullable Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof ExtensionDisplayFeature)) {
-            return false;
-        }
-        final ExtensionDisplayFeature
-                other = (ExtensionDisplayFeature) obj;
-        if (mType != other.mType) {
-            return false;
-        }
-        return mBounds.equals(other.mBounds);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mType;
-        result = 31 * result + mBounds.centerX() + mBounds.centerY();
-        return result;
-    }
+    Rect getBounds();
 }
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionFoldingFeature.java b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionFoldingFeature.java
new file mode 100644
index 0000000..e39e5ca
--- /dev/null
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionFoldingFeature.java
@@ -0,0 +1,213 @@
+/*
+ * 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.window.extensions;
+
+import android.graphics.Rect;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A feature that describes a fold in a flexible display
+ * or a hinge between two physical display panels.
+ */
+public class ExtensionFoldingFeature implements ExtensionDisplayFeature {
+
+    /**
+     * A fold in the flexible screen without a physical gap.
+     */
+    public static final int TYPE_FOLD = 1;
+
+    /**
+     * A physical separation with a hinge that allows two display panels to fold.
+     */
+    public static final int TYPE_HINGE = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            TYPE_FOLD,
+            TYPE_HINGE,
+    })
+    @interface Type{}
+
+    /**
+     * The foldable device's hinge is completely open, the screen space that is presented to the
+     * user is flat. See the
+     * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a>
+     * section in the official documentation for visual samples and references.
+     */
+    public static final int STATE_FLAT = 1;
+
+    /**
+     * The foldable device's hinge is in an intermediate position between opened and closed state,
+     * there is a non-flat angle between parts of the flexible screen or between physical screen
+     * panels. See the
+     * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a>
+     * section in the official documentation for visual samples and references.
+     */
+    public static final int STATE_HALF_OPENED = 2;
+
+    /**
+     * The foldable device's hinge is flipped with the flexible screen parts or physical screens
+     * facing opposite directions. See the
+     * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a>
+     * section in the official documentation for visual samples and references.
+     */
+    public static final int STATE_FLIPPED = 3;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            STATE_HALF_OPENED,
+            STATE_FLAT,
+            STATE_FLIPPED
+    })
+    @interface State {}
+
+    /**
+     * The bounding rectangle of the feature within the application window in the window
+     * coordinate space.
+     */
+    @NonNull
+    private final Rect mBounds;
+
+    /**
+     * The physical type of the feature.
+     */
+    @Type
+    private final int mType;
+
+    /**
+     * The state of the feature.
+     */
+    @State
+    private final int mState;
+
+    public ExtensionFoldingFeature(@NonNull Rect bounds, @Type int type, @State int state) {
+        validateFeatureBounds(bounds, type);
+        mBounds = new Rect(bounds);
+        mType = type;
+        mState = state;
+    }
+
+    /** Gets the bounding rect of the display feature in window coordinate space. */
+    @NonNull
+    @Override
+    public Rect getBounds() {
+        return new Rect(mBounds);
+    }
+
+    /** Gets the type of the display feature. */
+    @Type
+    public int getType() {
+        return mType;
+    }
+
+    /** Gets the state of the display feature. */
+    @State
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Verifies the bounds of the folding feature.
+     */
+    private static void validateFeatureBounds(@NonNull Rect bounds, int type) {
+        if (bounds.width() == 0 && bounds.height() == 0) {
+            throw new IllegalArgumentException("Bounds must be non zero");
+        }
+        if (type == TYPE_FOLD) {
+            if (bounds.width() != 0 && bounds.height() != 0) {
+                throw new IllegalArgumentException("Bounding rectangle must be either zero-wide "
+                        + "or zero-high for features of type " + typeToString(type));
+            }
+
+            if ((bounds.width() != 0 && bounds.left != 0)
+                    || (bounds.height() != 0 && bounds.top != 0)) {
+                throw new IllegalArgumentException("Bounding rectangle must span the entire "
+                        + "window space for features of type " + typeToString(type));
+            }
+        } else if (type == TYPE_HINGE) {
+            if (bounds.left != 0 && bounds.top != 0) {
+                throw new IllegalArgumentException("Bounding rectangle must span the entire "
+                        + "window space for features of type " + typeToString(type));
+            }
+        }
+    }
+
+    @NonNull
+    private static String typeToString(int type) {
+        switch (type) {
+            case TYPE_FOLD:
+                return "FOLD";
+            case TYPE_HINGE:
+                return "HINGE";
+            default:
+                return "Unknown feature type (" + type + ")";
+        }
+    }
+
+    @NonNull
+    private static String stateToString(int state) {
+        switch (state) {
+            case STATE_FLAT:
+                return "FLAT";
+            case STATE_FLIPPED:
+                return "FLIPPED";
+            case STATE_HALF_OPENED:
+                return "HALF_OPENED";
+            default:
+                return "Unknown feature state (" + state + ")";
+        }
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "ExtensionDisplayFoldFeature { " + mBounds
+                + ", type=" + typeToString(getType()) + ", state=" + stateToString(mState) + " }";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof ExtensionFoldingFeature)) {
+            return false;
+        }
+        final ExtensionFoldingFeature other = (ExtensionFoldingFeature) obj;
+        if (mType != other.mType) {
+            return false;
+        }
+        if (mState != other.mState) {
+            return false;
+        }
+        return mBounds.equals(other.mBounds);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mBounds.hashCode();
+        result = 31 * result + mType;
+        result = 31 * result + mState;
+        return result;
+    }
+}
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionWindowLayoutInfo.java b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionWindowLayoutInfo.java
index 69e7238..0afefc7 100644
--- a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionWindowLayoutInfo.java
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionWindowLayoutInfo.java
@@ -16,8 +16,6 @@
 
 package androidx.window.extensions;
 
-import android.content.Context;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -33,7 +31,6 @@
      * List of display features within the window.
      * <p>NOTE: All display features returned with this container must be cropped to the application
      * window and reported within the coordinate space of the window that was provided by the app.
-     * @see ExtensionInterface#getWindowLayoutInfo(Context)
      */
     @NonNull
     private List<ExtensionDisplayFeature> mDisplayFeatures;
diff --git a/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesActivity.kt b/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesActivity.kt
index 77bc4de..5710f79 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesActivity.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/DisplayFeaturesActivity.kt
@@ -22,8 +22,7 @@
 import android.widget.FrameLayout
 import android.widget.TextView
 import androidx.core.util.Consumer
-import androidx.window.DeviceState
-import androidx.window.DisplayFeature
+import androidx.window.FoldingFeature
 import androidx.window.WindowLayoutInfo
 import androidx.window.WindowManager
 import java.text.SimpleDateFormat
@@ -53,20 +52,12 @@
 
     override fun onStart() {
         super.onStart()
-        windowManager.registerDeviceStateChangeCallback(
-            mainThreadExecutor,
-            stateContainer.stateConsumer
-        )
-        windowManager.registerLayoutChangeCallback(
-            mainThreadExecutor,
-            stateContainer.layoutConsumer
-        )
+        windowManager.registerLayoutChangeCallback(mainThreadExecutor, stateContainer)
     }
 
     override fun onStop() {
         super.onStop()
-        windowManager.unregisterDeviceStateChangeCallback(stateContainer.stateConsumer)
-        windowManager.unregisterLayoutChangeCallback(stateContainer.layoutConsumer)
+        windowManager.unregisterLayoutChangeCallback(stateContainer)
     }
 
     /** Updates the device state and display feature positions. */
@@ -80,13 +71,6 @@
 
         // Update the UI with the current state
         val stateStringBuilder = StringBuilder()
-        // Update the current state string
-        stateContainer.lastState?.let { deviceState ->
-            stateStringBuilder.append(getString(R.string.deviceState))
-                .append(": ")
-                .append(deviceState)
-                .append("\n")
-        }
 
         stateContainer.lastLayoutInfo?.let { windowLayoutInfo ->
             stateStringBuilder.append(getString(R.string.windowLayout))
@@ -107,9 +91,10 @@
                 }
 
                 val featureView = View(this)
-                val color = when (displayFeature.type) {
-                    DisplayFeature.TYPE_FOLD -> getColor(R.color.colorFeatureFold)
-                    DisplayFeature.TYPE_HINGE -> getColor(R.color.colorFeatureHinge)
+                val foldFeature = displayFeature as? FoldingFeature
+                val color = when (foldFeature?.type) {
+                    FoldingFeature.TYPE_FOLD -> getColor(R.color.colorFeatureFold)
+                    FoldingFeature.TYPE_HINGE -> getColor(R.color.colorFeatureHinge)
                     else -> getColor(R.color.colorFeatureUnknown)
                 }
                 featureView.foreground = ColorDrawable(color)
@@ -139,25 +124,10 @@
         return currentDate.toString()
     }
 
-    inner class StateContainer {
-        var lastState: DeviceState? = null
+    inner class StateContainer : Consumer<WindowLayoutInfo> {
         var lastLayoutInfo: WindowLayoutInfo? = null
 
-        val stateConsumer: Consumer<DeviceState>
-        val layoutConsumer: Consumer<WindowLayoutInfo>
-
-        init {
-            stateConsumer = Consumer { state: DeviceState -> update(state) }
-            layoutConsumer = Consumer { layout: WindowLayoutInfo -> update(layout) }
-        }
-
-        fun update(newDeviceState: DeviceState) {
-            updateStateLog(newDeviceState)
-            lastState = newDeviceState
-            updateCurrentState()
-        }
-
-        fun update(newLayoutInfo: WindowLayoutInfo) {
+        override fun accept(newLayoutInfo: WindowLayoutInfo) {
             updateStateLog(newLayoutInfo)
             lastLayoutInfo = newLayoutInfo
             updateCurrentState()
diff --git a/window/window-samples/src/main/java/androidx/window/sample/PresentationActivity.kt b/window/window-samples/src/main/java/androidx/window/sample/PresentationActivity.kt
index 5e43a73..2cc38db 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/PresentationActivity.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/PresentationActivity.kt
@@ -28,7 +28,8 @@
 import android.widget.TextView
 import android.widget.Toast
 import androidx.core.util.Consumer
-import androidx.window.DeviceState
+import androidx.window.FoldingFeature
+import androidx.window.WindowLayoutInfo
 import androidx.window.WindowManager
 
 /**
@@ -39,7 +40,7 @@
     private val TAG = "FoldablePresentation"
 
     private lateinit var windowManager: WindowManager
-    private val deviceStateChangeCallback = DeviceStateChangeCallback()
+    private val deviceStateChangeCallback = WindowLayoutInfoChangeCallback()
     private var presentation: DemoPresentation? = null
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -48,7 +49,7 @@
 
         windowManager = getTestBackend()?.let { backend -> WindowManager(this, backend) }
             ?: WindowManager(this)
-        windowManager.registerDeviceStateChangeCallback(
+        windowManager.registerLayoutChangeCallback(
             mainThreadExecutor,
             deviceStateChangeCallback
         )
@@ -56,7 +57,7 @@
 
     override fun onDestroy() {
         super.onDestroy()
-        windowManager.unregisterDeviceStateChangeCallback(deviceStateChangeCallback)
+        windowManager.unregisterLayoutChangeCallback(deviceStateChangeCallback)
     }
 
     internal fun startPresentation(context: Context) {
@@ -137,26 +138,36 @@
     }
 
     /**
-     * Updates the display of the current device state.
+     * Updates the display of the current fold feature state.
      */
-    internal fun updateCurrentState(deviceState: DeviceState) {
+    internal fun updateCurrentState(info: WindowLayoutInfo) {
         val stateStringBuilder = StringBuilder()
+
         stateStringBuilder.append(getString(R.string.deviceState))
             .append(": ")
-            .append(deviceState)
-            .append("\n")
+
+        info.displayFeatures
+            .mapNotNull { it as? FoldingFeature }
+            .forEach { feature ->
+                stateStringBuilder.append(feature.stateString())
+                    .append("\n")
+            }
 
         findViewById<TextView>(R.id.currentState).text = stateStringBuilder.toString()
     }
 
-    inner class DeviceStateChangeCallback : Consumer<DeviceState> {
-        override fun accept(newDeviceState: DeviceState) {
-            updateCurrentState(newDeviceState)
-            if (newDeviceState.posture == DeviceState.POSTURE_CLOSED) {
-                startPresentation(this@PresentationActivity)
-            } else {
-                stopPresentation(null)
-            }
+    private fun FoldingFeature.stateString(): String {
+        return when (state) {
+            FoldingFeature.STATE_FLAT -> "FLAT"
+            FoldingFeature.STATE_FLIPPED -> "FLIPPED"
+            FoldingFeature.STATE_HALF_OPENED -> "HALF_OPENED"
+            else -> "Unknown feature state ($state)"
+        }
+    }
+
+    inner class WindowLayoutInfoChangeCallback : Consumer<WindowLayoutInfo> {
+        override fun accept(info: WindowLayoutInfo) {
+            updateCurrentState(info)
         }
     }
 }
diff --git a/window/window-samples/src/main/java/androidx/window/sample/SplitLayout.kt b/window/window-samples/src/main/java/androidx/window/sample/SplitLayout.kt
index 954c798..cc0b341 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/SplitLayout.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/SplitLayout.kt
@@ -23,8 +23,10 @@
 import android.view.View.MeasureSpec.AT_MOST
 import android.view.View.MeasureSpec.EXACTLY
 import android.widget.FrameLayout
-import androidx.window.DisplayFeature.TYPE_FOLD
-import androidx.window.DisplayFeature.TYPE_HINGE
+import androidx.window.DisplayFeature
+import androidx.window.FoldingFeature
+import androidx.window.FoldingFeature.TYPE_FOLD
+import androidx.window.FoldingFeature.TYPE_HINGE
 import androidx.window.WindowLayoutInfo
 
 /**
@@ -41,9 +43,11 @@
     private var lastHeightMeasureSpec: Int = 0
 
     constructor(context: Context) : super(context)
+
     constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
         setAttributes(attrs)
     }
+
     constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
         context,
         attrs,
@@ -125,46 +129,43 @@
         val paddedWidth = width - paddingLeft - paddingRight
         val paddedHeight = height - paddingTop - paddingBottom
 
-        for (feature in windowLayoutInfo?.displayFeatures!!) {
-            // Only a hinge or a fold can split the area in two
-            if (feature.type != TYPE_FOLD && feature.type != TYPE_HINGE) {
-                continue
-            }
+        windowLayoutInfo?.displayFeatures
+            ?.firstOrNull { feature -> isValidFoldFeature(feature) }
+            ?.let { feature ->
+                getFeaturePositionInViewRect(feature, this)?.let {
+                    if (feature.bounds.left == 0) { // Horizontal layout
+                        val topRect = Rect(
+                            paddingLeft, paddingTop,
+                            paddingLeft + paddedWidth, it.top
+                        )
+                        val bottomRect = Rect(
+                            paddingLeft, it.bottom,
+                            paddingLeft + paddedWidth, paddingTop + paddedHeight
+                        )
 
-            val splitRect = getFeaturePositionInViewRect(feature, this) ?: continue
+                        if (measureAndCheckMinSize(topRect, startView) &&
+                            measureAndCheckMinSize(bottomRect, endView)
+                        ) {
+                            return arrayOf(topRect, bottomRect)
+                        }
+                    } else if (feature.bounds.top == 0) { // Vertical layout
+                        val leftRect = Rect(
+                            paddingLeft, paddingTop,
+                            it.left, paddingTop + paddedHeight
+                        )
+                        val rightRect = Rect(
+                            it.right, paddingTop,
+                            paddingLeft + paddedWidth, paddingTop + paddedHeight
+                        )
 
-            if (feature.bounds.left == 0) { // Horizontal layout
-                val topRect = Rect(
-                    paddingLeft, paddingTop,
-                    paddingLeft + paddedWidth, splitRect.top
-                )
-                val bottomRect = Rect(
-                    paddingLeft, splitRect.bottom,
-                    paddingLeft + paddedWidth, paddingTop + paddedHeight
-                )
-
-                if (measureAndCheckMinSize(topRect, startView) &&
-                    measureAndCheckMinSize(bottomRect, endView)
-                ) {
-                    return arrayOf(topRect, bottomRect)
-                }
-            } else if (feature.bounds.top == 0) { // Vertical layout
-                val leftRect = Rect(
-                    paddingLeft, paddingTop,
-                    splitRect.left, paddingTop + paddedHeight
-                )
-                val rightRect = Rect(
-                    splitRect.right, paddingTop,
-                    paddingLeft + paddedWidth, paddingTop + paddedHeight
-                )
-
-                if (measureAndCheckMinSize(leftRect, startView) &&
-                    measureAndCheckMinSize(rightRect, endView)
-                ) {
-                    return arrayOf(leftRect, rightRect)
+                        if (measureAndCheckMinSize(leftRect, startView) &&
+                            measureAndCheckMinSize(rightRect, endView)
+                        ) {
+                            return arrayOf(leftRect, rightRect)
+                        }
+                    }
                 }
             }
-        }
 
         // We have tried to fit the children and measured them previously. Since they didn't fit,
         // we need to measure again to update the stored values.
@@ -191,4 +192,10 @@
         return childView.measuredWidthAndState and MEASURED_STATE_TOO_SMALL == 0 &&
             childView.measuredHeightAndState and MEASURED_STATE_TOO_SMALL == 0
     }
+
+    private fun isValidFoldFeature(displayFeature: DisplayFeature): Boolean {
+        val feature = displayFeature as? FoldingFeature ?: return false
+        return (feature.type == TYPE_FOLD || feature.type == TYPE_HINGE) &&
+            getFeaturePositionInViewRect(feature, this) != null
+    }
 }
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt b/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt
index 5ee6df2..27754f4 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("DEPRECATION") // TODO(b/173739071) Remove DeviceState
+
 package androidx.window.sample.backend
 
 import android.app.Activity
@@ -22,7 +24,7 @@
 import androidx.core.util.Consumer
 import androidx.window.DeviceState
 import androidx.window.DisplayFeature
-import androidx.window.DisplayFeature.TYPE_FOLD
+import androidx.window.FoldingFeature
 import androidx.window.WindowBackend
 import androidx.window.WindowLayoutInfo
 import java.util.concurrent.Executor
@@ -53,18 +55,16 @@
         SHORT_DIMENSION
     }
 
-    private fun getDeviceState(): DeviceState {
-        return DeviceState.Builder().setPosture(DeviceState.POSTURE_OPENED).build()
-    }
-
     private fun getWindowLayoutInfo(activity: Activity): WindowLayoutInfo {
         val windowSize = activity.calculateWindowSizeExt()
         val featureRect = foldRect(windowSize)
 
-        val displayFeature = DisplayFeature.Builder()
-            .setBounds(featureRect)
-            .setType(TYPE_FOLD)
-            .build()
+        val displayFeature =
+            FoldingFeature(
+                featureRect,
+                FoldingFeature.TYPE_FOLD,
+                FoldingFeature.STATE_FLAT
+            )
         val featureList = ArrayList<DisplayFeature>()
         featureList.add(displayFeature)
         return WindowLayoutInfo.Builder().setDisplayFeatures(featureList).build()
@@ -96,9 +96,7 @@
     override fun registerDeviceStateChangeCallback(
         executor: Executor,
         callback: Consumer<DeviceState>
-    ) {
-        executor.execute { callback.accept(getDeviceState()) }
-    }
+    ) {}
 
     override fun unregisterDeviceStateChangeCallback(callback: Consumer<DeviceState>) {
     }
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 3abcf8b..3055076 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -1,35 +1,37 @@
 // Signature format: 4.0
 package androidx.window {
 
-  public final class DeviceState {
-    method public int getPosture();
-    field public static final int POSTURE_CLOSED = 1; // 0x1
-    field public static final int POSTURE_FLIPPED = 4; // 0x4
-    field public static final int POSTURE_HALF_OPENED = 2; // 0x2
-    field public static final int POSTURE_OPENED = 3; // 0x3
-    field public static final int POSTURE_UNKNOWN = 0; // 0x0
+  @Deprecated public final class DeviceState {
+    method @Deprecated public int getPosture();
+    field @Deprecated public static final int POSTURE_CLOSED = 1; // 0x1
+    field @Deprecated public static final int POSTURE_FLIPPED = 4; // 0x4
+    field @Deprecated public static final int POSTURE_HALF_OPENED = 2; // 0x2
+    field @Deprecated public static final int POSTURE_OPENED = 3; // 0x3
+    field @Deprecated public static final int POSTURE_UNKNOWN = 0; // 0x0
   }
 
-  public static final class DeviceState.Builder {
-    ctor public DeviceState.Builder();
-    method public androidx.window.DeviceState build();
-    method public androidx.window.DeviceState.Builder setPosture(int);
+  @Deprecated public static final class DeviceState.Builder {
+    ctor @Deprecated public DeviceState.Builder();
+    method @Deprecated public androidx.window.DeviceState build();
+    method @Deprecated public androidx.window.DeviceState.Builder setPosture(int);
   }
 
-  public final class DisplayFeature {
+  public interface DisplayFeature {
     method public android.graphics.Rect getBounds();
+  }
+
+  public class FoldingFeature implements androidx.window.DisplayFeature {
+    ctor public FoldingFeature(android.graphics.Rect, int, int);
+    method public android.graphics.Rect getBounds();
+    method public int getState();
     method public int getType();
+    field public static final int STATE_FLAT = 1; // 0x1
+    field public static final int STATE_FLIPPED = 3; // 0x3
+    field public static final int STATE_HALF_OPENED = 2; // 0x2
     field public static final int TYPE_FOLD = 1; // 0x1
     field public static final int TYPE_HINGE = 2; // 0x2
   }
 
-  public static final class DisplayFeature.Builder {
-    ctor public DisplayFeature.Builder();
-    method public androidx.window.DisplayFeature build();
-    method public androidx.window.DisplayFeature.Builder setBounds(android.graphics.Rect);
-    method public androidx.window.DisplayFeature.Builder setType(int);
-  }
-
   public interface WindowBackend {
     method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(android.app.Activity, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
@@ -52,9 +54,9 @@
     ctor public WindowManager(android.content.Context, androidx.window.WindowBackend);
     method public androidx.window.WindowMetrics getCurrentWindowMetrics();
     method public androidx.window.WindowMetrics getMaximumWindowMetrics();
-    method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
+    method @Deprecated public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
-    method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
+    method @Deprecated public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
   }
 
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 3abcf8b..3055076 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -1,35 +1,37 @@
 // Signature format: 4.0
 package androidx.window {
 
-  public final class DeviceState {
-    method public int getPosture();
-    field public static final int POSTURE_CLOSED = 1; // 0x1
-    field public static final int POSTURE_FLIPPED = 4; // 0x4
-    field public static final int POSTURE_HALF_OPENED = 2; // 0x2
-    field public static final int POSTURE_OPENED = 3; // 0x3
-    field public static final int POSTURE_UNKNOWN = 0; // 0x0
+  @Deprecated public final class DeviceState {
+    method @Deprecated public int getPosture();
+    field @Deprecated public static final int POSTURE_CLOSED = 1; // 0x1
+    field @Deprecated public static final int POSTURE_FLIPPED = 4; // 0x4
+    field @Deprecated public static final int POSTURE_HALF_OPENED = 2; // 0x2
+    field @Deprecated public static final int POSTURE_OPENED = 3; // 0x3
+    field @Deprecated public static final int POSTURE_UNKNOWN = 0; // 0x0
   }
 
-  public static final class DeviceState.Builder {
-    ctor public DeviceState.Builder();
-    method public androidx.window.DeviceState build();
-    method public androidx.window.DeviceState.Builder setPosture(int);
+  @Deprecated public static final class DeviceState.Builder {
+    ctor @Deprecated public DeviceState.Builder();
+    method @Deprecated public androidx.window.DeviceState build();
+    method @Deprecated public androidx.window.DeviceState.Builder setPosture(int);
   }
 
-  public final class DisplayFeature {
+  public interface DisplayFeature {
     method public android.graphics.Rect getBounds();
+  }
+
+  public class FoldingFeature implements androidx.window.DisplayFeature {
+    ctor public FoldingFeature(android.graphics.Rect, int, int);
+    method public android.graphics.Rect getBounds();
+    method public int getState();
     method public int getType();
+    field public static final int STATE_FLAT = 1; // 0x1
+    field public static final int STATE_FLIPPED = 3; // 0x3
+    field public static final int STATE_HALF_OPENED = 2; // 0x2
     field public static final int TYPE_FOLD = 1; // 0x1
     field public static final int TYPE_HINGE = 2; // 0x2
   }
 
-  public static final class DisplayFeature.Builder {
-    ctor public DisplayFeature.Builder();
-    method public androidx.window.DisplayFeature build();
-    method public androidx.window.DisplayFeature.Builder setBounds(android.graphics.Rect);
-    method public androidx.window.DisplayFeature.Builder setType(int);
-  }
-
   public interface WindowBackend {
     method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(android.app.Activity, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
@@ -52,9 +54,9 @@
     ctor public WindowManager(android.content.Context, androidx.window.WindowBackend);
     method public androidx.window.WindowMetrics getCurrentWindowMetrics();
     method public androidx.window.WindowMetrics getMaximumWindowMetrics();
-    method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
+    method @Deprecated public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
-    method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
+    method @Deprecated public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
   }
 
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 3abcf8b..3055076 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -1,35 +1,37 @@
 // Signature format: 4.0
 package androidx.window {
 
-  public final class DeviceState {
-    method public int getPosture();
-    field public static final int POSTURE_CLOSED = 1; // 0x1
-    field public static final int POSTURE_FLIPPED = 4; // 0x4
-    field public static final int POSTURE_HALF_OPENED = 2; // 0x2
-    field public static final int POSTURE_OPENED = 3; // 0x3
-    field public static final int POSTURE_UNKNOWN = 0; // 0x0
+  @Deprecated public final class DeviceState {
+    method @Deprecated public int getPosture();
+    field @Deprecated public static final int POSTURE_CLOSED = 1; // 0x1
+    field @Deprecated public static final int POSTURE_FLIPPED = 4; // 0x4
+    field @Deprecated public static final int POSTURE_HALF_OPENED = 2; // 0x2
+    field @Deprecated public static final int POSTURE_OPENED = 3; // 0x3
+    field @Deprecated public static final int POSTURE_UNKNOWN = 0; // 0x0
   }
 
-  public static final class DeviceState.Builder {
-    ctor public DeviceState.Builder();
-    method public androidx.window.DeviceState build();
-    method public androidx.window.DeviceState.Builder setPosture(int);
+  @Deprecated public static final class DeviceState.Builder {
+    ctor @Deprecated public DeviceState.Builder();
+    method @Deprecated public androidx.window.DeviceState build();
+    method @Deprecated public androidx.window.DeviceState.Builder setPosture(int);
   }
 
-  public final class DisplayFeature {
+  public interface DisplayFeature {
     method public android.graphics.Rect getBounds();
+  }
+
+  public class FoldingFeature implements androidx.window.DisplayFeature {
+    ctor public FoldingFeature(android.graphics.Rect, int, int);
+    method public android.graphics.Rect getBounds();
+    method public int getState();
     method public int getType();
+    field public static final int STATE_FLAT = 1; // 0x1
+    field public static final int STATE_FLIPPED = 3; // 0x3
+    field public static final int STATE_HALF_OPENED = 2; // 0x2
     field public static final int TYPE_FOLD = 1; // 0x1
     field public static final int TYPE_HINGE = 2; // 0x2
   }
 
-  public static final class DisplayFeature.Builder {
-    ctor public DisplayFeature.Builder();
-    method public androidx.window.DisplayFeature build();
-    method public androidx.window.DisplayFeature.Builder setBounds(android.graphics.Rect);
-    method public androidx.window.DisplayFeature.Builder setType(int);
-  }
-
   public interface WindowBackend {
     method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(android.app.Activity, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
@@ -52,9 +54,9 @@
     ctor public WindowManager(android.content.Context, androidx.window.WindowBackend);
     method public androidx.window.WindowMetrics getCurrentWindowMetrics();
     method public androidx.window.WindowMetrics getMaximumWindowMetrics();
-    method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
+    method @Deprecated public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void registerLayoutChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
-    method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
+    method @Deprecated public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
   }
 
diff --git a/window/window/build.gradle b/window/window/build.gradle
index 30a2fa6..4b98a67 100644
--- a/window/window/build.gradle
+++ b/window/window/build.gradle
@@ -18,12 +18,16 @@
 import androidx.build.LibraryVersions
 import androidx.build.Publish
 
+import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_CORE
 import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_EXT_JUNIT
 import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RULES
 import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RUNNER
 import static androidx.build.dependencies.DependenciesKt.DEXMAKER_MOCKITO
+import static androidx.build.dependencies.DependenciesKt.JUNIT
 import static androidx.build.dependencies.DependenciesKt.MOCKITO_CORE
+import static androidx.build.dependencies.DependenciesKt.ROBOLECTRIC
 import static androidx.build.dependencies.DependenciesKt.TRUTH
+import static androidx.build.dependencies.DependenciesKt.getKOTLIN_STDLIB
 
 plugins {
     id("AndroidXPlugin")
@@ -47,6 +51,16 @@
     compileOnly(project(":window:window-extensions"))
     compileOnly(project(":window:window-sidecar"))
 
+    testImplementation(KOTLIN_STDLIB)
+    testImplementation(ANDROIDX_TEST_CORE)
+    testImplementation(ANDROIDX_TEST_RUNNER)
+    testImplementation(JUNIT)
+    testImplementation(TRUTH)
+    testImplementation(ROBOLECTRIC)
+    testImplementation(MOCKITO_CORE)
+    testImplementation(compileOnly(project(":window:window-extensions")))
+    testImplementation(compileOnly(project(":window:window-sidecar")))
+
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
     androidTestImplementation(ANDROIDX_TEST_RULES)
diff --git a/window/window/src/androidTest/java/androidx/window/DisplayFeatureTest.java b/window/window/src/androidTest/java/androidx/window/DisplayFeatureTest.java
deleted file mode 100644
index 93033ae..0000000
--- a/window/window/src/androidTest/java/androidx/window/DisplayFeatureTest.java
+++ /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.window;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-import android.graphics.Rect;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Tests for {@link DisplayFeature} class. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayFeatureTest {
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBuilder_empty() {
-        new DisplayFeature.Builder().build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBuilder_foldWithNonZeroArea() {
-        DisplayFeature feature = new DisplayFeature.Builder()
-                .setBounds(new Rect(10, 0, 20, 30))
-                .setType(DisplayFeature.TYPE_FOLD).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBuilder_horizontalHingeWithNonZeroOrigin() {
-        DisplayFeature horizontalHinge = new DisplayFeature.Builder()
-                .setBounds(new Rect(1, 10, 20, 10))
-                .setType(DisplayFeature.TYPE_HINGE).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBuilder_verticalHingeWithNonZeroOrigin() {
-        DisplayFeature verticalHinge = new DisplayFeature.Builder()
-                .setBounds(new Rect(10, 1, 10, 20))
-                .setType(DisplayFeature.TYPE_HINGE).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBuilder_horizontalFoldWithNonZeroOrigin() {
-        DisplayFeature horizontalFold = new DisplayFeature.Builder()
-                .setBounds(new Rect(1, 10, 20, 10))
-                .setType(DisplayFeature.TYPE_FOLD).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBuilder_verticalFoldWithNonZeroOrigin() {
-        DisplayFeature verticalFold = new DisplayFeature.Builder()
-                .setBounds(new Rect(10, 1, 10, 20))
-                .setType(DisplayFeature.TYPE_FOLD).build();
-    }
-
-    @Test
-    public void testBuilder_setBoundsAndType() {
-        DisplayFeature.Builder builder = new DisplayFeature.Builder();
-        Rect bounds = new Rect(0, 10, 30, 10);
-        builder.setBounds(bounds);
-        builder.setType(DisplayFeature.TYPE_HINGE);
-        DisplayFeature feature = builder.build();
-
-        assertEquals(bounds, feature.getBounds());
-        assertEquals(DisplayFeature.TYPE_HINGE, feature.getType());
-    }
-
-    @Test
-    public void testEquals_sameAttributes() {
-        Rect bounds = new Rect(1, 0, 1, 10);
-        int type = DisplayFeature.TYPE_FOLD;
-
-        DisplayFeature original = new DisplayFeature(bounds, type);
-        DisplayFeature copy = new DisplayFeature(bounds, type);
-
-        assertEquals(original, copy);
-    }
-
-    @Test
-    public void testEquals_differentRect() {
-        Rect originalRect = new Rect(1, 0, 1, 10);
-        Rect otherRect = new Rect(2, 0, 2, 10);
-        int type = DisplayFeature.TYPE_FOLD;
-
-        DisplayFeature original = new DisplayFeature(originalRect, type);
-        DisplayFeature other = new DisplayFeature(otherRect, type);
-
-        assertNotEquals(original, other);
-    }
-
-    @Test
-    public void testEquals_differentType() {
-        Rect rect = new Rect(1, 0, 1, 10);
-        int originalType = DisplayFeature.TYPE_FOLD;
-        int otherType = DisplayFeature.TYPE_HINGE;
-
-        DisplayFeature original = new DisplayFeature(rect, originalType);
-        DisplayFeature other = new DisplayFeature(rect, otherType);
-
-        assertNotEquals(original, other);
-    }
-
-    @Test
-    public void testHashCode_matchesIfEqual() {
-        Rect originalRect = new Rect(1, 0, 1, 10);
-        Rect matchingRect = new Rect(1, 0, 1, 10);
-        int type = DisplayFeature.TYPE_FOLD;
-
-        DisplayFeature original = new DisplayFeature(originalRect, type);
-        DisplayFeature matching = new DisplayFeature(matchingRect, type);
-
-        assertEquals(original, matching);
-        assertEquals(original.hashCode(), matching.hashCode());
-    }
-}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java
index c6e6348..9932500 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java
@@ -25,6 +25,7 @@
 
 import androidx.window.extensions.ExtensionDeviceState;
 import androidx.window.extensions.ExtensionDisplayFeature;
+import androidx.window.extensions.ExtensionFoldingFeature;
 import androidx.window.extensions.ExtensionWindowLayoutInfo;
 
 import org.junit.After;
@@ -64,8 +65,8 @@
     public void testTranslate_validFeature() {
         Activity mockActivity = mock(Activity.class);
         Rect bounds = new Rect(WINDOW_BOUNDS.left, 0, WINDOW_BOUNDS.right, 0);
-        ExtensionDisplayFeature foldFeature = new ExtensionDisplayFeature(bounds,
-                ExtensionDisplayFeature.TYPE_FOLD);
+        ExtensionDisplayFeature foldFeature = new ExtensionFoldingFeature(bounds,
+                ExtensionFoldingFeature.TYPE_FOLD, ExtensionFoldingFeature.STATE_FLAT);
 
         List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
         extensionDisplayFeatures.add(foldFeature);
@@ -73,7 +74,8 @@
                 new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
 
         List<DisplayFeature> expectedFeatures = new ArrayList<>();
-        expectedFeatures.add(new DisplayFeature(foldFeature.getBounds(), DisplayFeature.TYPE_FOLD));
+        expectedFeatures.add(new FoldingFeature(foldFeature.getBounds(), FoldingFeature.TYPE_FOLD,
+                FoldingFeature.STATE_FLAT));
         WindowLayoutInfo expected = new WindowLayoutInfo(expectedFeatures);
 
         ExtensionAdapter adapter = new ExtensionAdapter();
@@ -85,59 +87,17 @@
 
     @Test
     @Override
-    public void testTranslateWindowLayoutInfo_filterRemovesEmptyBoundsFeature() {
-        List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
-        extensionDisplayFeatures.add(
-                new ExtensionDisplayFeature(new Rect(), ExtensionDisplayFeature.TYPE_FOLD));
-
-        ExtensionAdapter adapter = new ExtensionAdapter();
-        ExtensionWindowLayoutInfo windowLayoutInfo =
-                new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
-        Activity mockActivity = mock(Activity.class);
-
-        WindowLayoutInfo actual = adapter.translate(mockActivity, windowLayoutInfo);
-
-        assertTrue("Remove empty bounds feature", actual.getDisplayFeatures().isEmpty());
-    }
-
-
-    @Test
-    @Override
-    public void testTranslateWindowLayoutInfo_filterRemovesNonEmptyAreaFoldFeature() {
-        List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
-        Rect fullWidthBounds = new Rect(0, 1, WINDOW_BOUNDS.width(), 2);
-        Rect fullHeightBounds = new Rect(1, 0, 2, WINDOW_BOUNDS.height());
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullWidthBounds,
-                ExtensionDisplayFeature.TYPE_FOLD));
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullHeightBounds,
-                ExtensionDisplayFeature.TYPE_FOLD));
-
-        ExtensionAdapter extensionCallbackAdapter = new ExtensionAdapter();
-        ExtensionWindowLayoutInfo windowLayoutInfo =
-                new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
-        Activity mockActivity = mock(Activity.class);
-
-        WindowLayoutInfo actual = extensionCallbackAdapter.translate(mockActivity,
-                windowLayoutInfo);
-
-        assertTrue("Remove non empty area fold feature", actual.getDisplayFeatures().isEmpty());
-    }
-
-    @Test
-    @Override
     public void testTranslateWindowLayoutInfo_filterRemovesHingeFeatureNotSpanningFullDimension() {
         List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
         Rect fullWidthBounds = new Rect(WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
                 WINDOW_BOUNDS.right / 2, 2);
         Rect fullHeightBounds = new Rect(WINDOW_BOUNDS.left, WINDOW_BOUNDS.top, 2,
                 WINDOW_BOUNDS.bottom / 2);
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullWidthBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullHeightBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullWidthBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullHeightBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
 
-        ExtensionInterfaceCompat.ExtensionCallbackInterface mockCallback = mock(
-                ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
         ExtensionAdapter extensionCallbackAdapter = new ExtensionAdapter();
         ExtensionWindowLayoutInfo windowLayoutInfo =
                 new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
@@ -159,10 +119,10 @@
                 WINDOW_BOUNDS.right / 2, WINDOW_BOUNDS.top);
         Rect fullHeightBounds = new Rect(WINDOW_BOUNDS.left, WINDOW_BOUNDS.top, WINDOW_BOUNDS.left,
                 WINDOW_BOUNDS.bottom / 2);
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullWidthBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullHeightBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullWidthBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullHeightBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
 
         ExtensionAdapter adapter = new ExtensionAdapter();
         ExtensionWindowLayoutInfo windowLayoutInfo =
@@ -183,20 +143,14 @@
         List<DeviceState> values = new ArrayList<>();
 
         values.add(extensionCallbackAdapter.translate(new ExtensionDeviceState(
-                ExtensionDeviceState.POSTURE_UNKNOWN)));
-        values.add(extensionCallbackAdapter.translate(new ExtensionDeviceState(
-                ExtensionDeviceState.POSTURE_CLOSED)));
-        values.add(extensionCallbackAdapter.translate(new ExtensionDeviceState(
                 ExtensionDeviceState.POSTURE_HALF_OPENED)));
         values.add(extensionCallbackAdapter.translate(new ExtensionDeviceState(
                 ExtensionDeviceState.POSTURE_OPENED)));
         values.add(extensionCallbackAdapter.translate(new ExtensionDeviceState(
                 ExtensionDeviceState.POSTURE_FLIPPED)));
 
-        assertEquals(DeviceState.POSTURE_UNKNOWN, values.get(0).getPosture());
-        assertEquals(DeviceState.POSTURE_CLOSED, values.get(1).getPosture());
-        assertEquals(DeviceState.POSTURE_HALF_OPENED, values.get(2).getPosture());
-        assertEquals(DeviceState.POSTURE_OPENED, values.get(3).getPosture());
-        assertEquals(DeviceState.POSTURE_FLIPPED, values.get(4).getPosture());
+        assertEquals(DeviceState.POSTURE_HALF_OPENED, values.get(0).getPosture());
+        assertEquals(DeviceState.POSTURE_OPENED, values.get(1).getPosture());
+        assertEquals(DeviceState.POSTURE_FLIPPED, values.get(2).getPosture());
     }
 }
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
index f2d1f92..c0d318e 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
@@ -17,11 +17,12 @@
 package androidx.window;
 
 import static androidx.window.ExtensionInterfaceCompat.ExtensionCallbackInterface;
-import static androidx.window.TestBoundUtil.invalidFoldBounds;
-import static androidx.window.TestBoundUtil.invalidHingeBounds;
-import static androidx.window.TestBoundUtil.validFoldBound;
+import static androidx.window.TestBoundsUtil.invalidFoldBounds;
+import static androidx.window.TestBoundsUtil.invalidHingeBounds;
+import static androidx.window.TestBoundsUtil.validFoldBound;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -37,6 +38,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.window.extensions.ExtensionDeviceState;
 import androidx.window.extensions.ExtensionDisplayFeature;
+import androidx.window.extensions.ExtensionFoldingFeature;
 import androidx.window.extensions.ExtensionInterface;
 import androidx.window.extensions.ExtensionWindowLayoutInfo;
 
@@ -62,19 +64,17 @@
     private static final Rect WINDOW_BOUNDS = new Rect(0, 0, 50, 100);
 
     ExtensionCompat mExtensionCompat;
-    private ExtensionInterface mMockExtensionInterface;
     private Activity mActivity;
 
     @Before
     public void setUp() {
-        mMockExtensionInterface = mock(ExtensionInterface.class);
-        mExtensionCompat = new ExtensionCompat(mMockExtensionInterface, new ExtensionAdapter());
+        mExtensionCompat = new ExtensionCompat(mock(ExtensionInterface.class),
+                new ExtensionAdapter());
         mActivity = mock(Activity.class);
 
         TestWindowBoundsHelper mWindowBoundsHelper = new TestWindowBoundsHelper();
         mWindowBoundsHelper.setCurrentBounds(WINDOW_BOUNDS);
         WindowBoundsHelper.setForTesting(mWindowBoundsHelper);
-
     }
 
     @After
@@ -141,7 +141,8 @@
         mExtensionCompat.onWindowLayoutChangeListenerAdded(mActivity);
         Rect bounds = new Rect(WINDOW_BOUNDS.left, WINDOW_BOUNDS.top, WINDOW_BOUNDS.width(), 1);
         ExtensionDisplayFeature extensionDisplayFeature =
-                new ExtensionDisplayFeature(bounds, ExtensionDisplayFeature.TYPE_HINGE);
+                new ExtensionFoldingFeature(bounds, ExtensionFoldingFeature.TYPE_HINGE,
+                        ExtensionFoldingFeature.STATE_FLIPPED);
         List<ExtensionDisplayFeature> displayFeatures = new ArrayList<>();
         displayFeatures.add(extensionDisplayFeature);
         ExtensionWindowLayoutInfo extensionWindowLayoutInfo =
@@ -156,7 +157,10 @@
         WindowLayoutInfo capturedLayout = windowLayoutInfoCaptor.getValue();
         assertEquals(1, capturedLayout.getDisplayFeatures().size());
         DisplayFeature capturedDisplayFeature = capturedLayout.getDisplayFeatures().get(0);
-        assertEquals(DisplayFeature.TYPE_HINGE, capturedDisplayFeature.getType());
+
+        FoldingFeature foldingFeature = (FoldingFeature) capturedDisplayFeature;
+        assertNotNull(foldingFeature);
+        assertEquals(FoldingFeature.TYPE_HINGE, foldingFeature.getType());
         assertEquals(bounds, capturedDisplayFeature.getBounds());
     }
 
@@ -263,13 +267,15 @@
             List<ExtensionDisplayFeature> malformedFeatures = new ArrayList<>();
 
             for (Rect malformedBound : invalidFoldBounds(WINDOW_BOUNDS)) {
-                malformedFeatures.add(new ExtensionDisplayFeature(malformedBound,
-                        ExtensionDisplayFeature.TYPE_FOLD));
+                malformedFeatures.add(new ExtensionFoldingFeature(malformedBound,
+                        ExtensionFoldingFeature.TYPE_FOLD,
+                        ExtensionFoldingFeature.STATE_FLAT));
             }
 
             for (Rect malformedBound : invalidHingeBounds(WINDOW_BOUNDS)) {
-                malformedFeatures.add(new ExtensionDisplayFeature(malformedBound,
-                        ExtensionDisplayFeature.TYPE_HINGE));
+                malformedFeatures.add(new ExtensionFoldingFeature(malformedBound,
+                        ExtensionFoldingFeature.TYPE_HINGE,
+                        ExtensionFoldingFeature.STATE_FLAT));
             }
 
             return new ExtensionWindowLayoutInfo(malformedFeatures);
@@ -278,8 +284,8 @@
         private ExtensionWindowLayoutInfo validWindowLayoutInfo() {
             List<ExtensionDisplayFeature> validFeatures = new ArrayList<>();
 
-            validFeatures.add(new ExtensionDisplayFeature(validFoldBound(WINDOW_BOUNDS),
-                    ExtensionDisplayFeature.TYPE_FOLD));
+            validFeatures.add(new ExtensionFoldingFeature(validFoldBound(WINDOW_BOUNDS),
+                    ExtensionFoldingFeature.TYPE_FOLD, ExtensionFoldingFeature.STATE_FLAT));
 
             return new ExtensionWindowLayoutInfo(validFeatures);
         }
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionTest.java
index 3e89816..a231b94 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionTest.java
@@ -20,8 +20,6 @@
 import static androidx.window.ExtensionWindowBackend.initAndVerifyExtension;
 import static androidx.window.Version.VERSION_0_1;
 import static androidx.window.Version.VERSION_1_0;
-import static androidx.window.extensions.ExtensionDisplayFeature.TYPE_FOLD;
-import static androidx.window.extensions.ExtensionDisplayFeature.TYPE_HINGE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -35,6 +33,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -42,7 +41,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.window.extensions.ExtensionDeviceState;
-import androidx.window.extensions.ExtensionDisplayFeature;
+import androidx.window.extensions.ExtensionFoldingFeature;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,8 +78,6 @@
     public void testDeviceStateCallback() {
         assumeExtensionV10_V01();
         final Set<Integer> validValues = new HashSet<>();
-        validValues.add(ExtensionDeviceState.POSTURE_UNKNOWN);
-        validValues.add(ExtensionDeviceState.POSTURE_CLOSED);
         validValues.add(ExtensionDeviceState.POSTURE_FLIPPED);
         validValues.add(ExtensionDeviceState.POSTURE_HALF_OPENED);
         validValues.add(ExtensionDeviceState.POSTURE_OPENED);
@@ -89,8 +86,7 @@
         extension.setExtensionCallback(callbackInterface);
         extension.onDeviceStateListenersChanged(false);
 
-        verify(callbackInterface).onDeviceStateChanged(argThat(
-                deviceState -> validValues.contains(deviceState.getPosture())));
+        verify(callbackInterface).onDeviceStateChanged(any());
     }
 
     @Test
@@ -106,15 +102,16 @@
     public void testDisplayFeatureDataClass() {
         assumeExtensionV10_V01();
 
-        Rect rect = new Rect(1, 2, 3, 4);
+        Rect rect = new Rect(0, 100, 100, 100);
         int type = 1;
-        ExtensionDisplayFeature displayFeature = new ExtensionDisplayFeature(rect, type);
+        int state = 1;
+        ExtensionFoldingFeature displayFeature =
+                new ExtensionFoldingFeature(rect, type, state);
         assertEquals(rect, displayFeature.getBounds());
-        assertEquals(type, displayFeature.getType());
     }
 
     @Test
-    public void testWindowLayoutInfoCallback() {
+    public void testWindowLayoutCallback() {
         assumeExtensionV10_V01();
         ExtensionInterfaceCompat extension = initAndVerifyExtension(mContext);
         ExtensionCallbackInterface callbackInterface = mock(ExtensionCallbackInterface.class);
@@ -125,7 +122,7 @@
 
         assertTrue("Layout must happen after launch", activity.waitForLayout());
 
-        verify(callbackInterface).onWindowLayoutChanged(any(), argThat(
+        verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(any(), argThat(
                 new WindowLayoutInfoValidator(activity)));
     }
 
@@ -155,11 +152,22 @@
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
 
         activity.waitForLayout();
+        if (activity.getResources().getConfiguration().orientation
+                != Configuration.ORIENTATION_PORTRAIT) {
+            // Orientation change did not occur on this device config. Skipping the test.
+            return;
+        }
 
         activity.resetLayoutCounter();
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
 
-        assertTrue("Layout must happen after orientation change", activity.waitForLayout());
+        boolean layoutHappened = activity.waitForLayout();
+        if (activity.getResources().getConfiguration().orientation
+                != Configuration.ORIENTATION_LANDSCAPE) {
+            // Orientation change did not occur on this device config. Skipping the test.
+            return;
+        }
+        assertTrue("Layout must happen after orientation change", layoutHappened);
 
         verify(callbackInterface, atLeastOnce())
                 .onWindowLayoutChanged(any(), argThat(new DistinctWindowLayoutInfoMatcher()));
@@ -181,6 +189,11 @@
         activity = mActivityTestRule.getActivity();
 
         activity.waitForLayout();
+        if (activity.getResources().getConfiguration().orientation
+                != Configuration.ORIENTATION_PORTRAIT) {
+            // Orientation change did not occur on this device config. Skipping the test.
+            return;
+        }
 
         TestActivity.resetResumeCounter();
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
@@ -189,6 +202,11 @@
         activity = mActivityTestRule.getActivity();
 
         activity.waitForLayout();
+        if (activity.getResources().getConfiguration().orientation
+                != Configuration.ORIENTATION_LANDSCAPE) {
+            // Orientation change did not occur on this device config. Skipping the test.
+            return;
+        }
 
         verify(callbackInterface, atLeastOnce())
                 .onWindowLayoutChanged(any(), argThat(new DistinctWindowLayoutInfoMatcher()));
@@ -253,26 +271,38 @@
                 return true;
             }
 
-            for (DisplayFeature displayFeature :
-                    windowLayoutInfo.getDisplayFeatures()) {
-                int featureType = displayFeature.getType();
-                if (featureType != TYPE_FOLD && featureType != TYPE_HINGE) {
-                    return false;
-                }
-
-                Rect featureRect = displayFeature.getBounds();
-
-                if (featureRect.isEmpty() || featureRect.left < 0 || featureRect.top < 0) {
-                    return false;
-                }
-                if (featureRect.right < 1 || featureRect.right > mActivity.getWidth()) {
-                    return false;
-                }
-                if (featureRect.bottom < 1 || featureRect.bottom > mActivity.getHeight()) {
+            for (DisplayFeature displayFeature : windowLayoutInfo.getDisplayFeatures()) {
+                if (!isValid(mActivity, displayFeature)) {
                     return false;
                 }
             }
             return true;
         }
     }
+
+    private static boolean isValid(TestActivity activity, DisplayFeature displayFeature) {
+        if (!(displayFeature instanceof FoldingFeature)) {
+            return false;
+        }
+        FoldingFeature feature = (FoldingFeature) displayFeature;
+        int featureType = feature.getType();
+        if (featureType != FoldingFeature.TYPE_FOLD && featureType != FoldingFeature.TYPE_HINGE) {
+            return false;
+        }
+
+        Rect featureRect = feature.getBounds();
+        WindowMetrics windowMetrics = new WindowManager(activity).getCurrentWindowMetrics();
+
+        if ((featureRect.height() == 0 && featureRect.width() == 0) || featureRect.left < 0
+                || featureRect.top < 0) {
+            return false;
+        }
+        if (featureRect.right < 1 || featureRect.right > windowMetrics.getBounds().width()) {
+            return false;
+        }
+        if (featureRect.bottom < 1 || featureRect.bottom > windowMetrics.getBounds().height()) {
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java
index 09a9305..fee07ed 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java
@@ -30,6 +30,7 @@
 
 import androidx.window.extensions.ExtensionDeviceState;
 import androidx.window.extensions.ExtensionDisplayFeature;
+import androidx.window.extensions.ExtensionFoldingFeature;
 import androidx.window.extensions.ExtensionWindowLayoutInfo;
 
 import org.junit.After;
@@ -62,8 +63,8 @@
     public void testOnWindowLayoutChange_validFeature() {
         Activity mockActivity = mock(Activity.class);
         Rect bounds = new Rect(WINDOW_BOUNDS.left, 0, WINDOW_BOUNDS.right, 0);
-        ExtensionDisplayFeature foldFeature = new ExtensionDisplayFeature(bounds,
-                ExtensionDisplayFeature.TYPE_FOLD);
+        ExtensionDisplayFeature foldFeature = new ExtensionFoldingFeature(bounds,
+                ExtensionFoldingFeature.TYPE_FOLD, ExtensionFoldingFeature.STATE_FLAT);
 
         List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
         extensionDisplayFeatures.add(foldFeature);
@@ -71,7 +72,8 @@
                 new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
 
         List<DisplayFeature> expectedFeatures = new ArrayList<>();
-        expectedFeatures.add(new DisplayFeature(foldFeature.getBounds(), DisplayFeature.TYPE_FOLD));
+        expectedFeatures.add(new FoldingFeature(foldFeature.getBounds(), FoldingFeature.TYPE_FOLD,
+                FoldingFeature.STATE_FLAT));
         WindowLayoutInfo expected = new WindowLayoutInfo(expectedFeatures);
 
         ExtensionCallbackInterface mockCallback = mock(ExtensionCallbackInterface.class);
@@ -86,59 +88,16 @@
     }
 
     @Test
-    public void testOnWindowLayoutChange_filterRemovesEmptyBoundsFeature() {
-        List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
-        extensionDisplayFeatures.add(
-                new ExtensionDisplayFeature(new Rect(), ExtensionDisplayFeature.TYPE_FOLD));
-
-        ExtensionCallbackInterface mockCallback = mock(ExtensionCallbackInterface.class);
-        ExtensionTranslatingCallback extensionTranslatingCallback =
-                new ExtensionTranslatingCallback(mockCallback, new ExtensionAdapter());
-        ExtensionWindowLayoutInfo windowLayoutInfo =
-                new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
-        Activity mockActivity = mock(Activity.class);
-
-        extensionTranslatingCallback.onWindowLayoutChanged(mockActivity, windowLayoutInfo);
-
-        verify(mockCallback).onWindowLayoutChanged(eq(mockActivity),
-                argThat((layoutInfo) -> layoutInfo.getDisplayFeatures().isEmpty()));
-    }
-
-
-    @Test
-    public void testOnWindowLayoutChange_filterRemovesNonEmptyAreaFoldFeature() {
-        List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
-        Rect fullWidthBounds = new Rect(0, 1, WINDOW_BOUNDS.width(), 2);
-        Rect fullHeightBounds = new Rect(1, 0, 2, WINDOW_BOUNDS.height());
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullWidthBounds,
-                ExtensionDisplayFeature.TYPE_FOLD));
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullHeightBounds,
-                ExtensionDisplayFeature.TYPE_FOLD));
-
-        ExtensionCallbackInterface mockCallback = mock(ExtensionCallbackInterface.class);
-        ExtensionTranslatingCallback extensionTranslatingCallback =
-                new ExtensionTranslatingCallback(mockCallback, new ExtensionAdapter());
-        ExtensionWindowLayoutInfo windowLayoutInfo =
-                new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
-        Activity mockActivity = mock(Activity.class);
-
-        extensionTranslatingCallback.onWindowLayoutChanged(mockActivity, windowLayoutInfo);
-
-        verify(mockCallback).onWindowLayoutChanged(eq(mockActivity),
-                argThat((layoutInfo) -> layoutInfo.getDisplayFeatures().isEmpty()));
-    }
-
-    @Test
     public void testOnWindowLayoutChange_filterRemovesHingeFeatureNotSpanningFullDimension() {
         List<ExtensionDisplayFeature> extensionDisplayFeatures = new ArrayList<>();
         Rect fullWidthBounds = new Rect(WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
                 WINDOW_BOUNDS.right / 2, 2);
         Rect fullHeightBounds = new Rect(WINDOW_BOUNDS.left, WINDOW_BOUNDS.top, 2,
                 WINDOW_BOUNDS.bottom / 2);
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullWidthBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullHeightBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullWidthBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullHeightBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
 
         ExtensionCallbackInterface mockCallback = mock(ExtensionCallbackInterface.class);
         ExtensionTranslatingCallback extensionTranslatingCallback =
@@ -161,10 +120,10 @@
                 WINDOW_BOUNDS.right / 2, WINDOW_BOUNDS.top);
         Rect fullHeightBounds = new Rect(WINDOW_BOUNDS.left, WINDOW_BOUNDS.top, WINDOW_BOUNDS.left,
                 WINDOW_BOUNDS.bottom / 2);
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullWidthBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
-        extensionDisplayFeatures.add(new ExtensionDisplayFeature(fullHeightBounds,
-                ExtensionDisplayFeature.TYPE_HINGE));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullWidthBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
+        extensionDisplayFeatures.add(new ExtensionFoldingFeature(fullHeightBounds,
+                ExtensionFoldingFeature.TYPE_HINGE, ExtensionFoldingFeature.STATE_FLAT));
 
         ExtensionCallbackInterface mockCallback = mock(ExtensionCallbackInterface.class);
         ExtensionTranslatingCallback extensionTranslatingCallback =
@@ -187,10 +146,6 @@
                 new ExtensionTranslatingCallback(mockCallback, new ExtensionAdapter());
 
         extensionTranslatingCallback.onDeviceStateChanged(new ExtensionDeviceState(
-                ExtensionDeviceState.POSTURE_UNKNOWN));
-        extensionTranslatingCallback.onDeviceStateChanged(new ExtensionDeviceState(
-                ExtensionDeviceState.POSTURE_CLOSED));
-        extensionTranslatingCallback.onDeviceStateChanged(new ExtensionDeviceState(
                 ExtensionDeviceState.POSTURE_HALF_OPENED));
         extensionTranslatingCallback.onDeviceStateChanged(new ExtensionDeviceState(
                 ExtensionDeviceState.POSTURE_OPENED));
@@ -201,11 +156,9 @@
         verify(mockCallback, atLeastOnce()).onDeviceStateChanged(captor.capture());
 
         List<DeviceState> values = captor.getAllValues();
-        assertEquals(DeviceState.POSTURE_UNKNOWN, values.get(0).getPosture());
-        assertEquals(DeviceState.POSTURE_CLOSED, values.get(1).getPosture());
-        assertEquals(DeviceState.POSTURE_HALF_OPENED, values.get(2).getPosture());
-        assertEquals(DeviceState.POSTURE_OPENED, values.get(3).getPosture());
-        assertEquals(DeviceState.POSTURE_FLIPPED, values.get(4).getPosture());
-        assertEquals(5, values.size());
+        assertEquals(DeviceState.POSTURE_HALF_OPENED, values.get(0).getPosture());
+        assertEquals(DeviceState.POSTURE_OPENED, values.get(1).getPosture());
+        assertEquals(DeviceState.POSTURE_FLIPPED, values.get(2).getPosture());
+        assertEquals(3, values.size());
     }
 }
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
index 2361ed1..97fbd20 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
@@ -52,6 +52,7 @@
 import java.util.List;
 
 /** Tests for {@link ExtensionWindowBackend} class. */
+@SuppressWarnings("deprecation") // TODO(b/173739071) remove DeviceState
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public final class ExtensionWindowBackendTest extends WindowTestBase {
@@ -132,7 +133,7 @@
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
 
         // Check registering the layout change callback
-        Consumer<WindowLayoutInfo> consumer = mock(Consumer.class);
+        Consumer<WindowLayoutInfo> consumer = mock(WindowLayoutInfoConsumer.class);
         TestActivity activity = mActivityTestRule.launchActivity(new Intent());
         backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
 
@@ -152,10 +153,11 @@
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
 
         // Check registering the layout change callback
-        Consumer<WindowLayoutInfo> consumer = mock(Consumer.class);
+        Consumer<WindowLayoutInfo> consumer = mock(WindowLayoutInfoConsumer.class);
         TestActivity activity = mActivityTestRule.launchActivity(new Intent());
         backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
-        backend.registerLayoutChangeCallback(activity, Runnable::run, mock(Consumer.class));
+        backend.registerLayoutChangeCallback(activity, Runnable::run,
+                mock(WindowLayoutInfoConsumer.class));
 
         assertEquals(2, backend.mWindowLayoutChangeCallbacks.size());
         verify(backend.mWindowExtension).onWindowLayoutChangeListenerAdded(activity);
@@ -174,8 +176,8 @@
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
 
         // Check registering the layout change callback
-        Consumer<WindowLayoutInfo> firstConsumer = mock(Consumer.class);
-        Consumer<WindowLayoutInfo> secondConsumer = mock(Consumer.class);
+        Consumer<WindowLayoutInfo> firstConsumer = mock(WindowLayoutInfoConsumer.class);
+        Consumer<WindowLayoutInfo> secondConsumer = mock(WindowLayoutInfoConsumer.class);
         TestActivity activity = mActivityTestRule.launchActivity(new Intent());
         backend.registerLayoutChangeCallback(activity, Runnable::run, firstConsumer);
         backend.registerLayoutChangeCallback(activity, Runnable::run, secondConsumer);
@@ -192,9 +194,10 @@
     public void testRegisterLayoutChangeCallback_relayLastEmittedValue() {
         WindowLayoutInfo expectedWindowLayoutInfo = newTestWindowLayoutInfo();
         ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
-        Consumer<WindowLayoutInfo> consumer = mock(Consumer.class);
+        Consumer<WindowLayoutInfo> consumer = mock(WindowLayoutInfoConsumer.class);
         TestActivity activity = mActivityTestRule.launchActivity(new Intent());
-        backend.registerLayoutChangeCallback(activity, Runnable::run, mock(Consumer.class));
+        backend.registerLayoutChangeCallback(activity, Runnable::run,
+                mock(WindowLayoutInfoConsumer.class));
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
         backend.mLastReportedWindowLayouts.put(activity, expectedWindowLayoutInfo);
 
@@ -209,7 +212,7 @@
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
 
         // Check that callbacks from the extension are propagated correctly
-        Consumer<WindowLayoutInfo> consumer = mock(Consumer.class);
+        Consumer<WindowLayoutInfo> consumer = mock(WindowLayoutInfoConsumer.class);
         TestActivity activity = mActivityTestRule.launchActivity(new Intent());
 
         backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
@@ -230,13 +233,13 @@
 
     @Test
     public void testRegisterDeviceChangeCallback() {
-        DeviceState expectedState = newTestDeviceState();
-        ExtensionInterfaceCompat mockInterface = mock(ExtensionInterfaceCompat.class);
+        ExtensionInterfaceCompat mockInterface = mock(
+                ExtensionInterfaceCompat.class);
         ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
         backend.mWindowExtension = mockInterface;
 
         // Check registering the device state change callback
-        Consumer<DeviceState> consumer = mock(Consumer.class);
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
         backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
 
         assertEquals(1, backend.mDeviceStateChangeCallbacks.size());
@@ -255,7 +258,7 @@
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
 
         // Check that callbacks from the extension are propagated correctly
-        Consumer<DeviceState> consumer = mock(Consumer.class);
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
 
         backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
         DeviceState deviceState = newTestDeviceState();
@@ -278,10 +281,10 @@
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
 
         // Check registering the layout change callback
-        Consumer<DeviceState> consumer = mock(Consumer.class);
-        TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
+        mActivityTestRule.launchActivity(new Intent());
         backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
-        backend.registerDeviceStateChangeCallback(Runnable::run, mock(Consumer.class));
+        backend.registerDeviceStateChangeCallback(Runnable::run, mock(DeviceStateConsumer.class));
 
         assertEquals(2, backend.mDeviceStateChangeCallbacks.size());
         verify(backend.mWindowExtension).onDeviceStateListenersChanged(false);
@@ -300,9 +303,9 @@
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
 
         // Check registering the layout change callback
-        Consumer<DeviceState> firstConsumer = mock(Consumer.class);
-        Consumer<DeviceState> secondConsumer = mock(Consumer.class);
-        TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+        Consumer<DeviceState> firstConsumer = mock(DeviceStateConsumer.class);
+        Consumer<DeviceState> secondConsumer = mock(DeviceStateConsumer.class);
+        mActivityTestRule.launchActivity(new Intent());
         backend.registerDeviceStateChangeCallback(Runnable::run, firstConsumer);
         backend.registerDeviceStateChangeCallback(Runnable::run, secondConsumer);
 
@@ -318,7 +321,7 @@
     public void testDeviceChangeCallback_relayLastEmittedValue() {
         DeviceState expectedState = newTestDeviceState();
         ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
-        Consumer<DeviceState> consumer = mock(Consumer.class);
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
         backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
         backend.mLastReportedDeviceState = expectedState;
 
@@ -330,7 +333,7 @@
     @Test
     public void testDeviceChangeCallback_clearLastEmittedValue() {
         ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
-        Consumer<DeviceState> consumer = mock(Consumer.class);
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
 
         backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
         backend.unregisterDeviceStateChangeCallback(consumer);
@@ -345,14 +348,10 @@
 
         assertTrue(windowLayoutInfo.getDisplayFeatures().isEmpty());
 
-        DisplayFeature.Builder featureBuilder = new DisplayFeature.Builder();
-        featureBuilder.setType(DisplayFeature.TYPE_HINGE);
-        featureBuilder.setBounds(new Rect(0, 2, 3, 4));
-        DisplayFeature feature1 = featureBuilder.build();
-
-        featureBuilder = new DisplayFeature.Builder();
-        featureBuilder.setBounds(new Rect(0, 1, 5, 1));
-        DisplayFeature feature2 = featureBuilder.build();
+        DisplayFeature feature1 = new FoldingFeature(new Rect(0, 2, 3, 4),
+                FoldingFeature.TYPE_HINGE, FoldingFeature.STATE_FLAT);
+        DisplayFeature feature2 = new FoldingFeature(new Rect(0, 1, 5, 1),
+                FoldingFeature.TYPE_HINGE, FoldingFeature.STATE_FLAT);
 
         List<DisplayFeature> displayFeatures = new ArrayList<>();
         displayFeatures.add(feature1);
@@ -369,8 +368,12 @@
         return builder.build();
     }
 
+    private interface DeviceStateConsumer extends Consumer<DeviceState> { }
+
+    private interface WindowLayoutInfoConsumer extends Consumer<WindowLayoutInfo> { }
+
     private static class SimpleConsumer<T> implements Consumer<T> {
-        private List<T> mValues;
+        private final List<T> mValues;
 
         SimpleConsumer() {
             mValues = new ArrayList<>();
diff --git a/window/window/src/androidTest/java/androidx/window/FoldingFeatureTest.java b/window/window/src/androidTest/java/androidx/window/FoldingFeatureTest.java
new file mode 100644
index 0000000..9339492
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/FoldingFeatureTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.window;
+
+import static androidx.window.FoldingFeature.STATE_FLAT;
+import static androidx.window.FoldingFeature.STATE_FLIPPED;
+import static androidx.window.FoldingFeature.STATE_HALF_OPENED;
+import static androidx.window.FoldingFeature.TYPE_FOLD;
+import static androidx.window.FoldingFeature.TYPE_HINGE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Rect;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link FoldingFeature} class. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FoldingFeatureTest {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void tesEmptyRect() {
+        new FoldingFeature(new Rect(), TYPE_HINGE, STATE_HALF_OPENED);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testFoldWithNonZeroArea() {
+        new FoldingFeature(new Rect(0, 0, 20, 30), TYPE_FOLD, STATE_FLIPPED);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHorizontalHingeWithNonZeroOrigin() {
+        new FoldingFeature(new Rect(1, 10, 20, 10), TYPE_HINGE, STATE_FLIPPED);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testVerticalHingeWithNonZeroOrigin() {
+        new FoldingFeature(new Rect(10, 1, 19, 29), TYPE_HINGE, STATE_FLIPPED);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHorizontalFoldWithNonZeroOrigin() {
+        new FoldingFeature(new Rect(1, 10, 20, 10), TYPE_FOLD, STATE_FLIPPED);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testVerticalFoldWithNonZeroOrigin() {
+        new FoldingFeature(new Rect(10, 1, 10, 20), TYPE_FOLD, STATE_FLIPPED);
+    }
+
+    @Test
+    public void testSetBoundsAndType() {
+        Rect bounds = new Rect(0, 10, 30, 10);
+        int type = TYPE_HINGE;
+        int state = STATE_HALF_OPENED;
+        FoldingFeature feature = new FoldingFeature(bounds, type, state);
+
+        assertEquals(bounds, feature.getBounds());
+        assertEquals(type, feature.getType());
+        assertEquals(state, feature.getState());
+    }
+
+    @Test
+    public void testEquals_sameAttributes() {
+        Rect bounds = new Rect(1, 0, 1, 10);
+        int type = TYPE_FOLD;
+        int state = STATE_FLAT;
+
+        FoldingFeature original = new FoldingFeature(bounds, type, state);
+        FoldingFeature copy = new FoldingFeature(bounds, type, state);
+
+        assertEquals(original, copy);
+    }
+
+    @Test
+    public void testEquals_differentRect() {
+        Rect originalRect = new Rect(1, 0, 1, 10);
+        Rect otherRect = new Rect(2, 0, 2, 10);
+        int type = TYPE_FOLD;
+        int state = STATE_FLAT;
+
+        FoldingFeature original = new FoldingFeature(originalRect, type, state);
+        FoldingFeature other = new FoldingFeature(otherRect, type, state);
+
+        assertNotEquals(original, other);
+    }
+
+    @Test
+    public void testEquals_differentType() {
+        Rect rect = new Rect(1, 0, 1, 10);
+        int originalType = TYPE_FOLD;
+        int otherType = TYPE_HINGE;
+        int state = STATE_FLAT;
+
+        FoldingFeature original = new FoldingFeature(rect, originalType, state);
+        FoldingFeature other = new FoldingFeature(rect, otherType, state);
+
+        assertNotEquals(original, other);
+    }
+
+    @Test
+    public void testEquals_differentState() {
+        Rect rect = new Rect(1, 0, 1, 10);
+        int type = TYPE_FOLD;
+        int originalState = STATE_FLAT;
+        int otherState = STATE_FLIPPED;
+
+        FoldingFeature original = new FoldingFeature(rect, type, originalState);
+        FoldingFeature other = new FoldingFeature(rect, type, otherState);
+
+        assertNotEquals(original, other);
+    }
+
+    @Test
+    public void testHashCode_matchesIfEqual() {
+        Rect originalRect = new Rect(1, 0, 1, 10);
+        Rect matchingRect = new Rect(1, 0, 1, 10);
+        int type = TYPE_FOLD;
+        int state = STATE_FLAT;
+
+        FoldingFeature original = new FoldingFeature(originalRect, type, state);
+        FoldingFeature matching = new FoldingFeature(matchingRect, type, state);
+
+        assertEquals(original, matching);
+        assertEquals(original.hashCode(), matching.hashCode());
+    }
+}
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java b/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
index 65e2903..8272c3c 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
+++ b/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
@@ -16,6 +16,9 @@
 
 package androidx.window;
 
+import static androidx.window.SidecarAdapter.setSidecarDevicePosture;
+import static androidx.window.SidecarAdapter.setSidecarDisplayFeatures;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -62,14 +65,13 @@
     private static SidecarWindowLayoutInfo sidecarWindowLayoutInfo(
             List<SidecarDisplayFeature> features) {
         SidecarWindowLayoutInfo layoutInfo = new SidecarWindowLayoutInfo();
-        layoutInfo.displayFeatures = new ArrayList<>();
-        layoutInfo.displayFeatures.addAll(features);
+        setSidecarDisplayFeatures(layoutInfo, features);
         return layoutInfo;
     }
 
     private static SidecarDeviceState sidecarDeviceState(int posture) {
         SidecarDeviceState deviceState = new SidecarDeviceState();
-        deviceState.posture = posture;
+        setSidecarDevicePosture(deviceState, posture);
         return deviceState;
     }
 
@@ -85,19 +87,21 @@
         sidecarDisplayFeatures.add(foldFeature);
         SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(sidecarDisplayFeatures);
 
+        SidecarDeviceState state = sidecarDeviceState(SidecarDeviceState.POSTURE_OPENED);
+
         List<DisplayFeature> expectedFeatures = new ArrayList<>();
-        expectedFeatures.add(new DisplayFeature(foldFeature.getRect(), DisplayFeature.TYPE_FOLD));
+        expectedFeatures.add(new FoldingFeature(foldFeature.getRect(), FoldingFeature.TYPE_FOLD,
+                FoldingFeature.STATE_FLAT));
         WindowLayoutInfo expected = new WindowLayoutInfo(expectedFeatures);
 
         SidecarAdapter sidecarAdapter = new SidecarAdapter();
 
-        WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo);
+        WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo, state);
 
         assertEquals(expected, actual);
     }
 
     @Test
-    @Override
     public void testTranslateWindowLayoutInfo_filterRemovesEmptyBoundsFeature() {
         List<SidecarDisplayFeature> sidecarDisplayFeatures = new ArrayList<>();
         sidecarDisplayFeatures.add(
@@ -106,15 +110,15 @@
         SidecarAdapter sidecarAdapter = new SidecarAdapter();
         SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(sidecarDisplayFeatures);
         Activity mockActivity = mock(Activity.class);
+        SidecarDeviceState state = sidecarDeviceState(SidecarDeviceState.POSTURE_OPENED);
 
-        WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo);
+        WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo, state);
 
         assertTrue(actual.getDisplayFeatures().isEmpty());
     }
 
 
     @Test
-    @Override
     public void testTranslateWindowLayoutInfo_filterRemovesNonEmptyAreaFoldFeature() {
         List<SidecarDisplayFeature> sidecarDisplayFeatures = new ArrayList<>();
         Rect fullWidthBounds = new Rect(0, 1, WINDOW_BOUNDS.width(), 2);
@@ -130,7 +134,10 @@
         SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(sidecarDisplayFeatures);
         Activity mockActivity = mock(Activity.class);
 
-        WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockActivity, windowLayoutInfo);
+        SidecarDeviceState state = sidecarDeviceState(SidecarDeviceState.POSTURE_OPENED);
+
+        WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockActivity, windowLayoutInfo,
+                state);
 
         assertTrue(actual.getDisplayFeatures().isEmpty());
     }
@@ -151,10 +158,11 @@
 
         SidecarAdapter sidecarAdapter = new SidecarAdapter();
         SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(sidecarDisplayFeatures);
+        SidecarDeviceState state = sidecarDeviceState(SidecarDeviceState.POSTURE_OPENED);
 
         Activity mockActivity = mock(Activity.class);
 
-        WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo);
+        WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo, state);
 
         assertTrue(actual.getDisplayFeatures().isEmpty());
     }
@@ -175,10 +183,12 @@
         SidecarAdapter sidecarCallbackAdapter = new SidecarAdapter();
         SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(
                 extensionDisplayFeatures);
+        SidecarDeviceState state = sidecarDeviceState(SidecarDeviceState.POSTURE_OPENED);
 
         Activity mockActivity = mock(Activity.class);
 
-        WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockActivity, windowLayoutInfo);
+        WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockActivity, windowLayoutInfo,
+                state);
 
         assertTrue(actual.getDisplayFeatures().isEmpty());
     }
@@ -186,8 +196,6 @@
     @Test
     @Override
     public void testTranslateDeviceState() {
-        ExtensionInterfaceCompat.ExtensionCallbackInterface mockCallback = mock(
-                ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
         SidecarAdapter sidecarCallbackAdapter = new SidecarAdapter();
         List<DeviceState> values = new ArrayList<>();
 
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java
index 954ed4a..cfc6286 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java
+++ b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java
@@ -17,12 +17,15 @@
 package androidx.window;
 
 import static androidx.window.ExtensionInterfaceCompat.ExtensionCallbackInterface;
+import static androidx.window.SidecarAdapter.getSidecarDevicePosture;
+import static androidx.window.SidecarAdapter.getSidecarDisplayFeatures;
 import static androidx.window.Version.VERSION_0_1;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
@@ -42,6 +45,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
 
+import java.util.List;
+
 /**
  * Tests for {@link SidecarCompat} implementation of {@link ExtensionInterfaceCompat} that are
  * executed with Sidecar implementation provided on the device (and only if one is available).
@@ -66,8 +71,8 @@
         mSidecarCompat.onDeviceStateListenersChanged(false);
 
 
-        verify(callbackInterface).onDeviceStateChanged(argThat(
-                deviceState -> deviceState.getPosture() == sidecarDeviceState.posture));
+        verify(callbackInterface, atLeastOnce()).onDeviceStateChanged(argThat(deviceState ->
+                deviceState.getPosture() == getSidecarDevicePosture(sidecarDeviceState)));
     }
 
     @Test
@@ -83,7 +88,7 @@
         SidecarWindowLayoutInfo sidecarWindowLayoutInfo =
                 mSidecarCompat.mSidecar.getWindowLayoutInfo(windowToken);
 
-        verify(callbackInterface).onWindowLayoutChanged(any(),
+        verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(any(),
                 argThat(new SidecarMatcher(sidecarWindowLayoutInfo)));
     }
 
@@ -102,14 +107,16 @@
 
         @Override
         public boolean matches(WindowLayoutInfo windowLayoutInfo) {
-            if (windowLayoutInfo.getDisplayFeatures().size()
-                    != mSidecarWindowLayoutInfo.displayFeatures.size()) {
+            List<SidecarDisplayFeature> sidecarDisplayFeatures =
+                    getSidecarDisplayFeatures(mSidecarWindowLayoutInfo);
+            if (windowLayoutInfo.getDisplayFeatures().size() != sidecarDisplayFeatures.size()) {
                 return false;
             }
             for (int i = 0; i < windowLayoutInfo.getDisplayFeatures().size(); i++) {
-                DisplayFeature feature = windowLayoutInfo.getDisplayFeatures().get(i);
-                SidecarDisplayFeature sidecarDisplayFeature =
-                        mSidecarWindowLayoutInfo.displayFeatures.get(i);
+                // Sidecar only has folding features
+                FoldingFeature feature = (FoldingFeature) windowLayoutInfo.getDisplayFeatures()
+                        .get(i);
+                SidecarDisplayFeature sidecarDisplayFeature = sidecarDisplayFeatures.get(i);
 
                 if (feature.getType() != sidecarDisplayFeature.getType()) {
                     return false;
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java b/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java
index 05f6c7d..5acba49 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java
+++ b/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java
@@ -16,16 +16,23 @@
 
 package androidx.window;
 
-import static androidx.window.TestBoundUtil.invalidFoldBounds;
-import static androidx.window.TestBoundUtil.invalidHingeBounds;
-import static androidx.window.TestBoundUtil.validFoldBound;
+import static androidx.window.SidecarAdapter.getSidecarDisplayFeatures;
+import static androidx.window.SidecarAdapter.setSidecarDevicePosture;
+import static androidx.window.SidecarAdapter.setSidecarDisplayFeatures;
+import static androidx.window.TestBoundsUtil.invalidFoldBounds;
+import static androidx.window.TestBoundsUtil.invalidHingeBounds;
+import static androidx.window.TestBoundsUtil.validFoldBound;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -54,6 +61,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -89,14 +97,15 @@
 
         // Setup mocked sidecar responses
         SidecarDeviceState defaultDeviceState = new SidecarDeviceState();
-        defaultDeviceState.posture = SidecarDeviceState.POSTURE_HALF_OPENED;
+        setSidecarDevicePosture(defaultDeviceState, SidecarDeviceState.POSTURE_HALF_OPENED);
         when(mSidecarCompat.mSidecar.getDeviceState()).thenReturn(defaultDeviceState);
 
         SidecarDisplayFeature sidecarDisplayFeature = newDisplayFeature(
                 new Rect(0, 1, WINDOW_BOUNDS.width(), 1), SidecarDisplayFeature.TYPE_HINGE);
         SidecarWindowLayoutInfo sidecarWindowLayoutInfo = new SidecarWindowLayoutInfo();
-        sidecarWindowLayoutInfo.displayFeatures = new ArrayList<>();
-        sidecarWindowLayoutInfo.displayFeatures.add(sidecarDisplayFeature);
+        List<SidecarDisplayFeature> displayFeatures = new ArrayList<>();
+        displayFeatures.add(sidecarDisplayFeature);
+        setSidecarDisplayFeatures(sidecarWindowLayoutInfo, displayFeatures);
         when(mSidecarCompat.mSidecar.getWindowLayoutInfo(any()))
                 .thenReturn(sidecarWindowLayoutInfo);
     }
@@ -109,23 +118,27 @@
     @Test
     @Override
     public void testGetDeviceState() {
-        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp();
+        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp(
+                newDeviceState(SidecarDeviceState.POSTURE_OPENED),
+                newWindowLayoutInfo(Collections.emptyList()));
         SidecarCompat compat = new SidecarCompat(fakeSidecarImp, new SidecarAdapter());
         ExtensionInterfaceCompat.ExtensionCallbackInterface mockCallback = mock(
                 ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
         compat.setExtensionCallback(mockCallback);
         compat.onDeviceStateListenersChanged(false);
-        SidecarDeviceState deviceState = newDeviceState(SidecarDeviceState.POSTURE_OPENED);
+        SidecarDeviceState deviceState = newDeviceState(SidecarDeviceState.POSTURE_HALF_OPENED);
 
         fakeSidecarImp.triggerDeviceState(deviceState);
 
-        verify(mockCallback).onDeviceStateChanged(any());
+        verify(mockCallback, atLeastOnce()).onDeviceStateChanged(any());
     }
 
     @Test
     @Override
     public void testGetWindowLayout() {
-        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp();
+        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp(
+                newDeviceState(SidecarDeviceState.POSTURE_OPENED),
+                newWindowLayoutInfo(Collections.emptyList()));
         SidecarCompat compat = new SidecarCompat(fakeSidecarImp, new SidecarAdapter());
         ExtensionInterfaceCompat.ExtensionCallbackInterface mockCallback = mock(
                 ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
@@ -134,7 +147,8 @@
 
         fakeSidecarImp.triggerGoodSignal();
 
-        verify(mockCallback).onWindowLayoutChanged(any(), any());
+        verify(mockCallback, atLeastOnce()).onWindowLayoutChanged(eq(mActivity),
+                any(WindowLayoutInfo.class));
     }
 
     @Test
@@ -143,7 +157,7 @@
         SidecarWindowLayoutInfo originalWindowLayoutInfo =
                 mSidecarCompat.mSidecar.getWindowLayoutInfo(getActivityWindowToken(mActivity));
         List<SidecarDisplayFeature> sidecarDisplayFeatures =
-                originalWindowLayoutInfo.displayFeatures;
+                getSidecarDisplayFeatures(originalWindowLayoutInfo);
         SidecarDisplayFeature newFeature = new SidecarDisplayFeature();
         newFeature.setRect(new Rect());
         sidecarDisplayFeatures.add(newFeature);
@@ -160,7 +174,7 @@
         SidecarWindowLayoutInfo originalWindowLayoutInfo =
                 mSidecarCompat.mSidecar.getWindowLayoutInfo(mock(IBinder.class));
         List<SidecarDisplayFeature> sidecarDisplayFeatures =
-                originalWindowLayoutInfo.displayFeatures;
+                getSidecarDisplayFeatures(originalWindowLayoutInfo);
         // Horizontal fold.
         sidecarDisplayFeatures.add(
                 newDisplayFeature(new Rect(0, 1, WINDOW_BOUNDS.width(), 2),
@@ -183,7 +197,7 @@
         SidecarWindowLayoutInfo originalWindowLayoutInfo =
                 mSidecarCompat.mSidecar.getWindowLayoutInfo(mock(IBinder.class));
         List<SidecarDisplayFeature> sidecarDisplayFeatures =
-                originalWindowLayoutInfo.displayFeatures;
+                getSidecarDisplayFeatures(originalWindowLayoutInfo);
         // Horizontal hinge.
         sidecarDisplayFeatures.add(
                 newDisplayFeature(new Rect(0, 1, WINDOW_BOUNDS.width() - 1, 2),
@@ -206,7 +220,7 @@
         SidecarWindowLayoutInfo originalWindowLayoutInfo =
                 mSidecarCompat.mSidecar.getWindowLayoutInfo(mock(IBinder.class));
         List<SidecarDisplayFeature> sidecarDisplayFeatures =
-                originalWindowLayoutInfo.displayFeatures;
+                getSidecarDisplayFeatures(originalWindowLayoutInfo);
         // Horizontal fold.
         sidecarDisplayFeatures.add(
                 newDisplayFeature(new Rect(0, 1, WINDOW_BOUNDS.width() - 1, 2),
@@ -226,7 +240,9 @@
 
     @Override
     public void testExtensionCallback_filterRemovesInvalidValues() {
-        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp();
+        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp(
+                newDeviceState(SidecarDeviceState.POSTURE_OPENED),
+                newWindowLayoutInfo(Collections.emptyList()));
         SidecarCompat compat = new SidecarCompat(fakeSidecarImp, new SidecarAdapter());
         ExtensionInterfaceCompat.ExtensionCallbackInterface mockCallback = mock(
                 ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
@@ -254,7 +270,7 @@
 
         // Verify that the callback set for sidecar propagates the device state callback
         SidecarDeviceState sidecarDeviceState = new SidecarDeviceState();
-        sidecarDeviceState.posture = SidecarDeviceState.POSTURE_HALF_OPENED;
+        setSidecarDevicePosture(sidecarDeviceState, SidecarDeviceState.POSTURE_HALF_OPENED);
 
         sidecarCallbackCaptor.getValue().onDeviceStateChanged(sidecarDeviceState);
         ArgumentCaptor<DeviceState> deviceStateCaptor = ArgumentCaptor.forClass(DeviceState.class);
@@ -268,8 +284,9 @@
         SidecarDisplayFeature sidecarDisplayFeature = newDisplayFeature(bounds,
                 SidecarDisplayFeature.TYPE_HINGE);
         SidecarWindowLayoutInfo sidecarWindowLayoutInfo = new SidecarWindowLayoutInfo();
-        sidecarWindowLayoutInfo.displayFeatures = new ArrayList<>();
-        sidecarWindowLayoutInfo.displayFeatures.add(sidecarDisplayFeature);
+        List<SidecarDisplayFeature> displayFeatures = new ArrayList<>();
+        displayFeatures.add(sidecarDisplayFeature);
+        setSidecarDisplayFeatures(sidecarWindowLayoutInfo, displayFeatures);
 
         sidecarCallbackCaptor.getValue().onWindowLayoutChanged(getActivityWindowToken(mActivity),
                 sidecarWindowLayoutInfo);
@@ -281,7 +298,9 @@
         WindowLayoutInfo capturedLayout = windowLayoutInfoCaptor.getValue();
         assertEquals(1, capturedLayout.getDisplayFeatures().size());
         DisplayFeature capturedDisplayFeature = capturedLayout.getDisplayFeatures().get(0);
-        assertEquals(DisplayFeature.TYPE_HINGE, capturedDisplayFeature.getType());
+        FoldingFeature foldingFeature = (FoldingFeature) capturedDisplayFeature;
+        assertNotNull(foldingFeature);
+        assertEquals(FoldingFeature.TYPE_HINGE, foldingFeature.getType());
         assertEquals(bounds, capturedDisplayFeature.getBounds());
     }
 
@@ -304,8 +323,9 @@
         Rect bounds = new Rect(1, 2, 3, 4);
         sidecarDisplayFeature.setRect(bounds);
         SidecarWindowLayoutInfo sidecarWindowLayoutInfo = new SidecarWindowLayoutInfo();
-        sidecarWindowLayoutInfo.displayFeatures = new ArrayList<>();
-        sidecarWindowLayoutInfo.displayFeatures.add(sidecarDisplayFeature);
+        List<SidecarDisplayFeature> displayFeatures = new ArrayList<>();
+        displayFeatures.add(sidecarDisplayFeature);
+        setSidecarDisplayFeatures(sidecarWindowLayoutInfo, displayFeatures);
 
         IBinder windowToken = mock(IBinder.class);
         sidecarCallbackCaptor.getValue().onWindowLayoutChanged(windowToken,
@@ -345,9 +365,8 @@
         when(mSidecarCompat.mSidecar.getWindowLayoutInfo(any())).thenReturn(layoutInfo);
         View fakeView = mock(View.class);
         doAnswer(invocation -> {
-            View.OnAttachStateChangeListener stateChangeListener =
-                    (View.OnAttachStateChangeListener) invocation.getArgument(0);
-            stateChangeListener.onViewAttachedToWindow((View) invocation.getMock());
+            View.OnAttachStateChangeListener stateChangeListener = invocation.getArgument(0);
+            stateChangeListener.onViewAttachedToWindow(fakeView);
             return null;
         }).when(fakeView).addOnAttachStateChangeListener(any());
         Window fakeWindow = new TestWindow(mActivity, fakeView);
@@ -387,6 +406,47 @@
         verify(listener).onDeviceStateChanged(expectedDeviceState);
     }
 
+    @Test
+    public void testOnDeviceStateChangedUpdatesWindowLayout() {
+        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp(
+                newDeviceState(SidecarDeviceState.POSTURE_CLOSED),
+                validWindowLayoutInfo());
+        SidecarCompat compat = new SidecarCompat(fakeSidecarImp, new SidecarAdapter());
+        ExtensionInterfaceCompat.ExtensionCallbackInterface mockCallback = mock(
+                ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
+        compat.setExtensionCallback(mockCallback);
+        compat.onWindowLayoutChangeListenerAdded(mActivity);
+        ArgumentCaptor<WindowLayoutInfo> windowLayoutCaptor = ArgumentCaptor.forClass(
+                WindowLayoutInfo.class);
+
+        reset(mockCallback);
+        fakeSidecarImp.triggerDeviceState(newDeviceState(SidecarDeviceState.POSTURE_OPENED));
+        verify(mockCallback).onWindowLayoutChanged(eq(mActivity), windowLayoutCaptor.capture());
+        FoldingFeature capturedFoldingFeature = (FoldingFeature) windowLayoutCaptor.getValue()
+                .getDisplayFeatures().get(0);
+        assertEquals(FoldingFeature.STATE_FLAT, capturedFoldingFeature.getState());
+
+        reset(mockCallback);
+        fakeSidecarImp.triggerDeviceState(newDeviceState(SidecarDeviceState.POSTURE_HALF_OPENED));
+        verify(mockCallback).onWindowLayoutChanged(eq(mActivity), windowLayoutCaptor.capture());
+        capturedFoldingFeature = (FoldingFeature) windowLayoutCaptor.getValue().getDisplayFeatures()
+                .get(0);
+        assertEquals(FoldingFeature.STATE_HALF_OPENED, capturedFoldingFeature.getState());
+
+        reset(mockCallback);
+        fakeSidecarImp.triggerDeviceState(newDeviceState(SidecarDeviceState.POSTURE_FLIPPED));
+        verify(mockCallback).onWindowLayoutChanged(eq(mActivity), windowLayoutCaptor.capture());
+        capturedFoldingFeature = (FoldingFeature) windowLayoutCaptor.getValue().getDisplayFeatures()
+                .get(0);
+        assertEquals(FoldingFeature.STATE_FLIPPED, capturedFoldingFeature.getState());
+
+        // No display features must be reported in closed state
+        reset(mockCallback);
+        fakeSidecarImp.triggerDeviceState(newDeviceState(SidecarDeviceState.POSTURE_CLOSED));
+        verify(mockCallback).onWindowLayoutChanged(eq(mActivity), windowLayoutCaptor.capture());
+        assertTrue(windowLayoutCaptor.getValue().getDisplayFeatures().isEmpty());
+    }
+
     private static SidecarDisplayFeature newDisplayFeature(Rect rect, int type) {
         SidecarDisplayFeature feature = new SidecarDisplayFeature();
         feature.setRect(rect);
@@ -397,23 +457,36 @@
     private static SidecarWindowLayoutInfo newWindowLayoutInfo(
             List<SidecarDisplayFeature> features) {
         SidecarWindowLayoutInfo info = new SidecarWindowLayoutInfo();
-        info.displayFeatures = new ArrayList<>();
-        info.displayFeatures.addAll(features);
+        setSidecarDisplayFeatures(info, features);
         return info;
     }
 
+    private static SidecarWindowLayoutInfo validWindowLayoutInfo() {
+        List<SidecarDisplayFeature> goodFeatures = new ArrayList<>();
+
+        goodFeatures.add(newDisplayFeature(validFoldBound(WINDOW_BOUNDS),
+                SidecarDisplayFeature.TYPE_FOLD));
+
+        return newWindowLayoutInfo(goodFeatures);
+    }
+
     private static SidecarDeviceState newDeviceState(int posture) {
         SidecarDeviceState state = new SidecarDeviceState();
-        state.posture = posture;
+        setSidecarDevicePosture(state, posture);
         return state;
     }
 
     private static final class FakeExtensionImp implements SidecarInterface {
 
+        private SidecarDeviceState mDeviceState;
+        private SidecarWindowLayoutInfo mInfo;
         private SidecarInterface.SidecarCallback mCallback;
         private List<IBinder> mTokens = new ArrayList<>();
 
-        FakeExtensionImp() {
+        FakeExtensionImp(@NonNull SidecarDeviceState deviceState,
+                @NonNull SidecarWindowLayoutInfo info) {
+            mDeviceState = deviceState;
+            mInfo = info;
             mCallback = new SidecarInterface.SidecarCallback() {
                 @Override
                 public void onDeviceStateChanged(@NonNull SidecarDeviceState newDeviceState) {
@@ -430,29 +503,29 @@
 
         @Override
         public void setSidecarCallback(@NonNull SidecarCallback callback) {
-
+            mCallback = callback;
         }
 
         @NonNull
         @Override
         public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
-            return null;
+            return mInfo;
         }
 
         @Override
         public void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken) {
-
+            mTokens.add(windowToken);
         }
 
         @Override
         public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken) {
-
+            mTokens.remove(windowToken);
         }
 
         @NonNull
         @Override
         public SidecarDeviceState getDeviceState() {
-            return null;
+            return mDeviceState;
         }
 
         @Override
@@ -469,14 +542,15 @@
         }
 
         void triggerSignal(SidecarWindowLayoutInfo info) {
-            for (IBinder token: mTokens) {
+            mInfo = info;
+            for (IBinder token : mTokens) {
                 mCallback.onWindowLayoutChanged(token, info);
             }
         }
 
         public void triggerDeviceState(SidecarDeviceState state) {
+            mDeviceState = state;
             mCallback.onDeviceStateChanged(state);
-
         }
 
         private SidecarWindowLayoutInfo malformedWindowLayoutInfo() {
@@ -494,14 +568,5 @@
 
             return newWindowLayoutInfo(malformedFeatures);
         }
-
-        private SidecarWindowLayoutInfo validWindowLayoutInfo() {
-            List<SidecarDisplayFeature> goodFeatures = new ArrayList<>();
-
-            goodFeatures.add(newDisplayFeature(validFoldBound(WINDOW_BOUNDS),
-                    SidecarDisplayFeature.TYPE_FOLD));
-
-            return newWindowLayoutInfo(goodFeatures);
-        }
     }
 }
diff --git a/window/window/src/androidTest/java/androidx/window/TestActivity.java b/window/window/src/androidTest/java/androidx/window/TestActivity.java
index ee73aac..65d1a81 100644
--- a/window/window/src/androidTest/java/androidx/window/TestActivity.java
+++ b/window/window/src/androidTest/java/androidx/window/TestActivity.java
@@ -28,7 +28,7 @@
 public class TestActivity extends Activity implements View.OnLayoutChangeListener {
 
     private int mRootViewId;
-    private CountDownLatch mLayoutLatch;
+    private CountDownLatch mLayoutLatch = new CountDownLatch(1);
     private static CountDownLatch sResumeLatch = new CountDownLatch(1);
 
     @Override
@@ -39,18 +39,9 @@
         contentView.setId(mRootViewId);
         setContentView(contentView);
 
-        resetLayoutCounter();
         getWindow().getDecorView().addOnLayoutChangeListener(this);
     }
 
-    int getWidth() {
-        return findViewById(mRootViewId).getWidth();
-    }
-
-    int getHeight() {
-        return findViewById(mRootViewId).getHeight();
-    }
-
     @Override
     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
             int oldTop, int oldRight, int oldBottom) {
diff --git a/window/window/src/androidTest/java/androidx/window/TestBoundUtil.java b/window/window/src/androidTest/java/androidx/window/TestBoundsUtil.java
similarity index 79%
copy from window/window/src/androidTest/java/androidx/window/TestBoundUtil.java
copy to window/window/src/androidTest/java/androidx/window/TestBoundsUtil.java
index 18447f5..ff353db 100644
--- a/window/window/src/androidTest/java/androidx/window/TestBoundUtil.java
+++ b/window/window/src/androidTest/java/androidx/window/TestBoundsUtil.java
@@ -24,10 +24,11 @@
 /**
  * A utility class to provide bounds for a display feature
  */
-class TestBoundUtil {
+class TestBoundsUtil {
 
     public static Rect validFoldBound(Rect windowBounds) {
-        return new Rect(windowBounds.left, windowBounds.top, windowBounds.right, 0);
+        int verticalMid = windowBounds.top + windowBounds.height() / 2;
+        return new Rect(0, verticalMid, windowBounds.width(), verticalMid);
     }
 
     public static Rect invalidZeroBound() {
@@ -35,11 +36,11 @@
     }
 
     public static Rect invalidBoundShortWidth(Rect windowBounds) {
-        return new Rect(windowBounds.left, windowBounds.top, windowBounds.right / 2, 2);
+        return new Rect(0, 0, windowBounds.width() / 2, 2);
     }
 
-    public static Rect invalidBoundShortHeightHeight(Rect windowBounds) {
-        return new Rect(windowBounds.left, windowBounds.top, 2, windowBounds.bottom / 2);
+    public static Rect invalidBoundShortHeight(Rect windowBounds) {
+        return new Rect(0, 0, 2, windowBounds.height() / 2);
     }
 
     private static List<Rect> coreInvalidBounds(Rect windowBounds) {
@@ -47,7 +48,7 @@
 
         badBounds.add(invalidZeroBound());
         badBounds.add(invalidBoundShortWidth(windowBounds));
-        badBounds.add(invalidBoundShortHeightHeight(windowBounds));
+        badBounds.add(invalidBoundShortHeight(windowBounds));
 
         return badBounds;
     }
diff --git a/window/window/src/androidTest/java/androidx/window/TranslatorTestInterface.java b/window/window/src/androidTest/java/androidx/window/TranslatorTestInterface.java
index 90eb92b..958063b 100644
--- a/window/window/src/androidTest/java/androidx/window/TranslatorTestInterface.java
+++ b/window/window/src/androidTest/java/androidx/window/TranslatorTestInterface.java
@@ -23,8 +23,6 @@
 public interface TranslatorTestInterface {
     void testTranslate_validFeature();
     void testTranslateDeviceState();
-    void testTranslateWindowLayoutInfo_filterRemovesEmptyBoundsFeature();
-    void testTranslateWindowLayoutInfo_filterRemovesNonEmptyAreaFoldFeature();
     void testTranslateWindowLayoutInfo_filterRemovesHingeFeatureNotSpanningFullDimension();
     void testTranslateWindowLayoutInfo_filterRemovesFoldFeatureNotSpanningFullDimension();
 }
diff --git a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java
index 6378d7a..d807de5 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java
+++ b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java
@@ -65,8 +65,9 @@
 
     private WindowLayoutInfo newTestWindowLayout() {
         List<DisplayFeature> displayFeatureList = new ArrayList<>();
-        DisplayFeature displayFeature = new DisplayFeature(
-                new Rect(10, 0, 10, 100), DisplayFeature.TYPE_HINGE);
+        DisplayFeature displayFeature = new FoldingFeature(
+                new Rect(10, 0, 10, 100), FoldingFeature.TYPE_HINGE,
+                FoldingFeature.STATE_FLAT);
         displayFeatureList.add(displayFeature);
         return new WindowLayoutInfo(displayFeatureList);
     }
diff --git a/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java b/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
index cf6eab3..5db5039 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
+++ b/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
@@ -33,6 +33,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,10 +49,9 @@
     @Test
     public void testGetCurrentWindowBounds_matchParentWindowSize_avoidCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = WindowManager.LayoutParams.MATCH_PARENT;
             lp.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -66,10 +66,9 @@
     @Test
     public void testGetCurrentWindowBounds_fixedWindowSize_avoidCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = 100;
             lp.height = 100;
@@ -84,10 +83,9 @@
     @Test
     public void testGetCurrentWindowBounds_matchParentWindowSize_layoutBehindCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = WindowManager.LayoutParams.MATCH_PARENT;
             lp.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -102,10 +100,9 @@
     @Test
     public void testGetCurrentWindowBounds_fixedWindowSize_layoutBehindCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetCurrentWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = 100;
             lp.height = 100;
@@ -132,10 +129,9 @@
     @Test
     public void testGetMaximumWindowBounds_matchParentWindowSize_avoidCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = WindowManager.LayoutParams.MATCH_PARENT;
             lp.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -150,10 +146,9 @@
     @Test
     public void testGetMaximumWindowBounds_fixedWindowSize_avoidCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = 100;
             lp.height = 100;
@@ -168,10 +163,9 @@
     @Test
     public void testGetMaximumWindowBounds_matchParentWindowSize_layoutBehindCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = WindowManager.LayoutParams.MATCH_PARENT;
             lp.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -186,10 +180,9 @@
     @Test
     public void testGetMaximumWindowBounds_fixedWindowSize_layoutBehindCutouts_preR() {
         assumePlatformBeforeR();
+        assumeNotMultiWindow();
 
         testGetMaximumWindowBoundsMatchesRealDisplaySize(activity -> {
-            assumeFalse(isInMultiWindowMode(activity));
-
             WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
             lp.width = 100;
             lp.height = 100;
@@ -250,6 +243,20 @@
         scenario.onActivity(verifyAction);
     }
 
+    private void assumeNotMultiWindow() {
+        ActivityScenario<TestActivity> scenario = mActivityScenarioRule.getScenario();
+        try {
+            scenario.onActivity(activity -> assumeFalse(isInMultiWindowMode(activity)));
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof AssumptionViolatedException) {
+                AssumptionViolatedException failedAssumption =
+                        (AssumptionViolatedException) e.getCause();
+                throw failedAssumption;
+            }
+            throw e;
+        }
+    }
+
     private static boolean isInMultiWindowMode(Activity activity) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             return activity.isInMultiWindowMode();
diff --git a/window/window/src/androidTest/java/androidx/window/WindowLayoutInfoTest.java b/window/window/src/androidTest/java/androidx/window/WindowLayoutInfoTest.java
index 8c97849..62154b1 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowLayoutInfoTest.java
+++ b/window/window/src/androidTest/java/androidx/window/WindowLayoutInfoTest.java
@@ -48,14 +48,11 @@
 
     @Test
     public void testBuilder_setDisplayFeatures() {
-        DisplayFeature.Builder featureBuilder = new DisplayFeature.Builder();
-        featureBuilder.setType(DisplayFeature.TYPE_HINGE);
-        featureBuilder.setBounds(new Rect(1, 0, 3, 4));
-        DisplayFeature feature1 = featureBuilder.build();
+        DisplayFeature feature1 = new FoldingFeature(new Rect(1, 0, 3, 4),
+                FoldingFeature.TYPE_HINGE, FoldingFeature.STATE_FLAT);
 
-        featureBuilder = new DisplayFeature.Builder();
-        featureBuilder.setBounds(new Rect(1, 0, 1, 4));
-        DisplayFeature feature2 = featureBuilder.build();
+        DisplayFeature feature2 = new FoldingFeature(new Rect(1, 0, 1, 4),
+                FoldingFeature.STATE_FLAT, FoldingFeature.STATE_FLAT);
 
         List<DisplayFeature> displayFeatures = new ArrayList<>();
         displayFeatures.add(feature1);
@@ -83,7 +80,8 @@
         List<DisplayFeature> originalFeatures = new ArrayList<>();
         List<DisplayFeature> differentFeatures = new ArrayList<>();
         Rect rect = new Rect(1, 0, 1, 10);
-        differentFeatures.add(new DisplayFeature(rect, 1));
+        differentFeatures.add(new FoldingFeature(rect, FoldingFeature.TYPE_HINGE,
+                FoldingFeature.STATE_FLAT));
 
         WindowLayoutInfo original = new WindowLayoutInfo(originalFeatures);
         WindowLayoutInfo different = new WindowLayoutInfo(differentFeatures);
@@ -104,13 +102,15 @@
 
     @Test
     public void testHashCode_matchesIfEqualFeatures() {
-        DisplayFeature originalFeature = new DisplayFeature(
-                new Rect(-1, 1, 1, -1),
-                0
+        DisplayFeature originalFeature = new FoldingFeature(
+                new Rect(0, 0, 100, 0),
+                FoldingFeature.TYPE_HINGE,
+                FoldingFeature.STATE_FLAT
         );
-        DisplayFeature matchingFeature = new DisplayFeature(
-                new Rect(-1, 1, 1, -1),
-                0
+        DisplayFeature matchingFeature = new FoldingFeature(
+                new Rect(0, 0, 100, 0),
+                FoldingFeature.TYPE_HINGE,
+                FoldingFeature.STATE_FLAT
         );
         List<DisplayFeature> firstFeatures = Collections.singletonList(originalFeature);
         List<DisplayFeature> secondFeatures = Collections.singletonList(matchingFeature);
diff --git a/window/window/src/main/java/androidx/window/DeviceState.java b/window/window/src/main/java/androidx/window/DeviceState.java
index d031629..5584f71 100644
--- a/window/window/src/main/java/androidx/window/DeviceState.java
+++ b/window/window/src/main/java/androidx/window/DeviceState.java
@@ -23,9 +23,12 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
+ * @deprecated Use {@link FoldingFeature} to get the state of the hinge instead. Will be removed in
+ * alpha03.
  * Information about the state of the device.
  * <p>Currently only includes the description of the state for foldable devices.
  */
+@Deprecated
 public final class DeviceState {
 
     @Posture
diff --git a/window/window/src/main/java/androidx/window/DisplayFeature.java b/window/window/src/main/java/androidx/window/DisplayFeature.java
index efa0ceb..09d9491 100644
--- a/window/window/src/main/java/androidx/window/DisplayFeature.java
+++ b/window/window/src/main/java/androidx/window/DisplayFeature.java
@@ -18,12 +18,8 @@
 
 import android.graphics.Rect;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Description of a physical feature on the display.
  *
@@ -31,162 +27,15 @@
  * the device. It can intrude into the application window space and create a visual distortion,
  * visual or touch discontinuity, make some area invisible or create a logical divider or separation
  * in the screen space.
- *
- * @see #TYPE_FOLD
- * @see #TYPE_HINGE
  */
-public final class DisplayFeature {
-    private final Rect mBounds;
-    @Type
-    private int mType;
-
-    DisplayFeature(@NonNull Rect bounds, @Type int type) {
-        assertValidBounds(bounds, type);
-        mBounds = new Rect(bounds);
-        mType = type;
-    }
+public interface DisplayFeature {
 
     /**
-     * Gets bounding rectangle of the physical display feature in the coordinate space of the
-     * window. The rectangle provides information about the location of the feature in the window
-     * and its size.
+     * The bounding rectangle of the feature within the application window
+     * in the window coordinate space.
      *
-     * <p>The bounds for features of type {@link #TYPE_FOLD fold} are guaranteed to be zero-high
-     * (for horizontal folds) or zero-wide (for vertical folds) and span the entire window.
-     *
-     * <p>The bounds for features of type {@link #TYPE_HINGE hinge} are guaranteed to span the
-     * entire window but, unlike folds, can have a non-zero area.
+     * @return bounds of display feature.
      */
     @NonNull
-    public Rect getBounds() {
-        return new Rect(mBounds);
-    }
-
-    /**
-     * Gets type of the physical display feature.
-     */
-    @Type
-    public int getType() {
-        return mType;
-    }
-
-    /**
-     * A fold in the flexible screen without a physical gap.
-     */
-    public static final int TYPE_FOLD = 1;
-
-    /**
-     * A physical separation with a hinge that allows two display panels to fold.
-     */
-    public static final int TYPE_HINGE = 2;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            TYPE_FOLD,
-            TYPE_HINGE,
-    })
-    @interface Type{}
-
-    private static String typeToString(@Type int type) {
-        switch (type) {
-            case TYPE_FOLD:
-                return "FOLD";
-            case TYPE_HINGE:
-                return "HINGE";
-            default:
-                return "Unknown feature type (" + type + ")";
-        }
-    }
-
-    @NonNull
-    @Override
-    public String toString() {
-        return "DisplayFeature{ bounds=" + mBounds + ", type=" + typeToString(mType) + " }";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        DisplayFeature that = (DisplayFeature) o;
-
-        return mType == that.mType && mBounds.equals(that.mBounds);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mBounds.hashCode();
-        result = 31 * result + mType;
-        return result;
-    }
-
-    /**
-     * Builder for {@link DisplayFeature} objects.
-     */
-    public static final class Builder {
-        private Rect mBounds = new Rect();
-        @Type
-        private int mType = TYPE_FOLD;
-
-        /**
-         * Creates an initially empty builder.
-         */
-        public Builder() {
-        }
-
-        /**
-         * Sets the bounds for the {@link DisplayFeature} instance.
-         */
-        @NonNull
-        public Builder setBounds(@NonNull Rect bounds) {
-            mBounds = bounds;
-            return this;
-        }
-
-        /**
-         * Sets the type for the {@link DisplayFeature} instance.
-         */
-        @NonNull
-        public Builder setType(@Type int type) {
-            mType = type;
-            return this;
-        }
-
-        /**
-         * Creates a {@link DisplayFeature} instance with the specified fields.
-         * @return A DisplayFeature instance.
-         */
-        @NonNull
-        public DisplayFeature build() {
-            return new DisplayFeature(mBounds, mType);
-        }
-    }
-
-    /**
-     * Throws runtime exceptions if the bounds are invalid or incompatible with the supplied type.
-     */
-    private static void assertValidBounds(Rect bounds, @Type int type) {
-        if (bounds.height() == 0 && bounds.width() == 0) {
-            throw new IllegalArgumentException("Bounding rectangle must not be empty: " + bounds);
-        }
-
-        if (type == TYPE_FOLD) {
-            if (bounds.width() != 0 && bounds.height() != 0) {
-                throw new IllegalArgumentException("Bounding rectangle must be either zero-wide "
-                        + "or zero-high for features of type " + typeToString(type));
-            }
-
-            if ((bounds.width() != 0 && bounds.left != 0)
-                    || (bounds.height() != 0 && bounds.top != 0)) {
-                throw new IllegalArgumentException("Bounding rectangle must span the entire "
-                        + "window space for features of type " + typeToString(type));
-            }
-        } else if (type == TYPE_HINGE) {
-            if (bounds.left != 0 && bounds.top != 0) {
-                throw new IllegalArgumentException("Bounding rectangle must span the entire "
-                        + "window space for features of type " + typeToString(type));
-            }
-        }
-    }
+    Rect getBounds();
 }
diff --git a/window/window/src/main/java/androidx/window/ExtensionAdapter.java b/window/window/src/main/java/androidx/window/ExtensionAdapter.java
index c2e5549..d3150e2 100644
--- a/window/window/src/main/java/androidx/window/ExtensionAdapter.java
+++ b/window/window/src/main/java/androidx/window/ExtensionAdapter.java
@@ -23,6 +23,7 @@
 import androidx.annotation.Nullable;
 import androidx.window.extensions.ExtensionDeviceState;
 import androidx.window.extensions.ExtensionDisplayFeature;
+import androidx.window.extensions.ExtensionFoldingFeature;
 import androidx.window.extensions.ExtensionWindowLayoutInfo;
 
 import java.util.ArrayList;
@@ -37,9 +38,6 @@
     DeviceState translate(ExtensionDeviceState deviceState) {
         final int posture;
         switch (deviceState.getPosture()) {
-            case ExtensionDeviceState.POSTURE_CLOSED:
-                posture = DeviceState.POSTURE_CLOSED;
-                break;
             case ExtensionDeviceState.POSTURE_FLIPPED:
                 posture = DeviceState.POSTURE_FLIPPED;
                 break;
@@ -49,7 +47,6 @@
             case ExtensionDeviceState.POSTURE_OPENED:
                 posture = DeviceState.POSTURE_OPENED;
                 break;
-            case ExtensionDeviceState.POSTURE_UNKNOWN:
             default:
                 posture = DeviceState.POSTURE_UNKNOWN;
         }
@@ -79,39 +76,62 @@
     }
 
     @Nullable
-    DisplayFeature translate(Activity activity, ExtensionDisplayFeature feature) {
-        final Rect windowBounds = WindowBoundsHelper.getInstance()
-                .computeCurrentWindowBounds(activity);
-        if (!isValid(feature, windowBounds)) {
+    DisplayFeature translate(Activity activity, ExtensionDisplayFeature displayFeature) {
+        if (!(displayFeature instanceof ExtensionFoldingFeature)) {
             return null;
         }
-        int type = DisplayFeature.TYPE_FOLD;
-        switch (feature.getType()) {
-            case ExtensionDisplayFeature.TYPE_FOLD:
-                type = DisplayFeature.TYPE_FOLD;
-                break;
-            case ExtensionDisplayFeature.TYPE_HINGE:
-                type = DisplayFeature.TYPE_HINGE;
-                break;
-        }
-        return new DisplayFeature(feature.getBounds(), type);
+        ExtensionFoldingFeature feature = (ExtensionFoldingFeature) displayFeature;
+        final Rect windowBounds = WindowBoundsHelper.getInstance()
+                .computeCurrentWindowBounds(activity);
+        return translateFoldFeature(windowBounds, feature);
     }
 
-    boolean isValid(ExtensionDisplayFeature feature, Rect windowBounds) {
+    @Nullable
+    private static DisplayFeature translateFoldFeature(@NonNull Rect windowBounds,
+            @NonNull ExtensionFoldingFeature feature) {
+        if (!isValid(windowBounds, feature)) {
+            return null;
+        }
+        int type = FoldingFeature.TYPE_FOLD;
+        switch (feature.getType()) {
+            case ExtensionFoldingFeature.TYPE_FOLD:
+                type = FoldingFeature.TYPE_FOLD;
+                break;
+            case ExtensionFoldingFeature.TYPE_HINGE:
+                type = FoldingFeature.TYPE_HINGE;
+                break;
+        }
+        int state = FoldingFeature.STATE_FLAT;
+        switch (feature.getState()) {
+            case ExtensionFoldingFeature.STATE_FLAT:
+                state = FoldingFeature.STATE_FLAT;
+                break;
+            case ExtensionFoldingFeature.STATE_FLIPPED:
+                state = FoldingFeature.STATE_FLIPPED;
+                break;
+            case ExtensionFoldingFeature.STATE_HALF_OPENED:
+                state = FoldingFeature.STATE_HALF_OPENED;
+                break;
+        }
+        return new FoldingFeature(feature.getBounds(), type, state);
+    }
+
+    private static boolean isValid(Rect windowBounds, ExtensionFoldingFeature feature) {
         if (feature.getBounds().width() == 0 && feature.getBounds().height() == 0) {
             return false;
         }
-        if (feature.getType() == ExtensionDisplayFeature.TYPE_FOLD
+        if (feature.getType() == ExtensionFoldingFeature.TYPE_FOLD
                 && !feature.getBounds().isEmpty()) {
             return false;
         }
-        if (!hasMatchingDimension(feature.getBounds(), windowBounds)) {
+        if (feature.getType() != ExtensionFoldingFeature.TYPE_FOLD
+                && feature.getType() != ExtensionFoldingFeature.TYPE_HINGE) {
             return false;
         }
-        return true;
+        return hasMatchingDimension(feature.getBounds(), windowBounds);
     }
 
-    private boolean hasMatchingDimension(Rect lhs, Rect rhs) {
+    private static boolean hasMatchingDimension(Rect lhs, Rect rhs) {
         boolean matchesWidth = lhs.left == rhs.left && lhs.right == rhs.right;
         boolean matchesHeight = lhs.top == rhs.top && lhs.bottom == rhs.bottom;
         return matchesWidth || matchesHeight;
diff --git a/window/window/src/main/java/androidx/window/ExtensionCompat.java b/window/window/src/main/java/androidx/window/ExtensionCompat.java
index 8a09172..ca97067 100644
--- a/window/window/src/main/java/androidx/window/ExtensionCompat.java
+++ b/window/window/src/main/java/androidx/window/ExtensionCompat.java
@@ -25,8 +25,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.window.extensions.ExtensionDeviceState;
 import androidx.window.extensions.ExtensionDisplayFeature;
+import androidx.window.extensions.ExtensionFoldingFeature;
 import androidx.window.extensions.ExtensionInterface;
 import androidx.window.extensions.ExtensionProvider;
 import androidx.window.extensions.ExtensionWindowLayoutInfo;
@@ -137,23 +137,20 @@
                                 + rtUnregisterWindowLayoutChangeListener);
             }
 
-            // ExtensionDeviceState constructor
-            ExtensionDeviceState deviceState = new ExtensionDeviceState(
-                    ExtensionDeviceState.POSTURE_UNKNOWN);
+            // {@link ExtensionFoldingFeature} constructor
+            ExtensionFoldingFeature displayFoldingFeature =
+                    new ExtensionFoldingFeature(new Rect(0, 0, 100, 0),
+                            ExtensionFoldingFeature.TYPE_FOLD,
+                            ExtensionFoldingFeature.STATE_FLAT);
 
-            // deviceState.getPosture();
-            int tmpPosture = deviceState.getPosture();
+            // displayFoldFeature.getBounds()
+            Rect tmpRect = displayFoldingFeature.getBounds();
 
-            // ExtensionDisplayFeature constructor
-            ExtensionDisplayFeature displayFeature =
-                    new ExtensionDisplayFeature(new Rect(0, 0, 0, 0),
-                            ExtensionDisplayFeature.TYPE_FOLD);
+            // displayFoldFeature.getState()
+            int tmpState = displayFoldingFeature.getState();
 
-            // displayFeature.getBounds()
-            Rect tmpRect = displayFeature.getBounds();
-
-            // displayFeature.getType()
-            int tmpType = displayFeature.getType();
+            // displayFoldFeature.getType()
+            int tmpType = displayFoldingFeature.getType();
 
             // ExtensionWindowLayoutInfo constructor
             ExtensionWindowLayoutInfo windowLayoutInfo =
@@ -163,10 +160,10 @@
             List<ExtensionDisplayFeature> tmpDisplayFeatures =
                     windowLayoutInfo.getDisplayFeatures();
             return true;
-        } catch (Exception e) {
+        } catch (Throwable t) {
             if (DEBUG) {
                 Log.e(TAG, "Extension implementation doesn't conform to interface version "
-                        + getExtensionVersion() + ", error: " + e);
+                        + getExtensionVersion() + ", error: " + t);
             }
             return false;
         }
diff --git a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
index 44e0c47..6488073 100644
--- a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
+++ b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
@@ -77,8 +77,12 @@
 
     private static final String TAG = "WindowServer";
 
-    private ExtensionWindowBackend() {
-        // Empty
+    @VisibleForTesting
+    ExtensionWindowBackend(@Nullable ExtensionInterfaceCompat windowExtension) {
+        mWindowExtension = windowExtension;
+        if (mWindowExtension != null) {
+            mWindowExtension.setExtensionCallback(new ExtensionListenerImpl());
+        }
     }
 
     /**
@@ -89,25 +93,14 @@
         if (sInstance == null) {
             synchronized (sLock) {
                 if (sInstance == null) {
-                    sInstance = new ExtensionWindowBackend();
-                    sInstance.initExtension(context.getApplicationContext());
+                    ExtensionInterfaceCompat windowExtension = initAndVerifyExtension(context);
+                    sInstance = new ExtensionWindowBackend(windowExtension);
                 }
             }
         }
         return sInstance;
     }
 
-    /** Tries to initialize Extension, returns early if it's not available. */
-    @SuppressLint("SyntheticAccessor")
-    @GuardedBy("sLock")
-    private void initExtension(Context context) {
-        mWindowExtension = initAndVerifyExtension(context);
-        if (mWindowExtension == null) {
-            return;
-        }
-        mWindowExtension.setExtensionCallback(new ExtensionListenerImpl());
-    }
-
     @Override
     public void registerLayoutChangeCallback(@NonNull Activity activity,
             @NonNull Executor executor, @NonNull Consumer<WindowLayoutInfo> callback) {
@@ -126,10 +119,12 @@
             WindowLayoutChangeCallbackWrapper callbackWrapper =
                     new WindowLayoutChangeCallbackWrapper(activity, executor, callback);
             mWindowLayoutChangeCallbacks.add(callbackWrapper);
+            // Read value before registering in case the extension updates synchronously.
+            // A synchronous update would result in two values emitted.
+            WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(activity);
             if (!isActivityRegistered) {
                 mWindowExtension.onWindowLayoutChangeListenerAdded(activity);
             }
-            WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(activity);
             if (lastReportedValue != null) {
                 callbackWrapper.accept(lastReportedValue);
             }
diff --git a/window/window/src/main/java/androidx/window/FoldingFeature.java b/window/window/src/main/java/androidx/window/FoldingFeature.java
new file mode 100644
index 0000000..34f2b68
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/FoldingFeature.java
@@ -0,0 +1,201 @@
+/*
+ * 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.window;
+
+import android.graphics.Rect;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A feature that describes a fold in the flexible display
+ * or a hinge between two physical display panels.
+ */
+public class FoldingFeature implements DisplayFeature {
+
+    /**
+     * A fold in the flexible screen without a physical gap.
+     */
+    public static final int TYPE_FOLD = 1;
+
+    /**
+     * A physical separation with a hinge that allows two display panels to fold.
+     */
+    public static final int TYPE_HINGE = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            TYPE_FOLD,
+            TYPE_HINGE,
+    })
+    @interface Type{}
+
+    /**
+     * The foldable device is completely open, the screen space that is presented to the user is
+     * flat. See the
+     * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a>
+     * section in the official documentation for visual samples and references.
+     */
+    public static final int STATE_FLAT = 1;
+
+    /**
+     * The foldable device's hinge is in an intermediate position between opened and closed state,
+     * there is a non-flat angle between parts of the flexible screen or between physical screen
+     * panels. See the
+     * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a>
+     * section in the official documentation for visual samples and references.
+     */
+    public static final int STATE_HALF_OPENED = 2;
+
+    /**
+     * The foldable device is flipped with the flexible screen parts or physical screens facing
+     * opposite directions. See the
+     * <a href="https://developer.android.com/guide/topics/ui/foldables#postures">Posture</a>
+     * section in the official documentation for visual samples and references.
+     */
+    public static final int STATE_FLIPPED = 3;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            STATE_HALF_OPENED,
+            STATE_FLAT,
+            STATE_FLIPPED,
+    })
+    @interface State {}
+
+    /**
+     * The bounding rectangle of the feature within the application window in the window
+     * coordinate space.
+     */
+    @NonNull
+    private final Rect mBounds;
+
+    /**
+     * The physical type of the feature.
+     */
+    @Type
+    private final int mType;
+
+    /**
+     * The state of the feature.
+     */
+    @State
+    private final int mState;
+
+    public FoldingFeature(@NonNull Rect bounds, @Type int type, @State int state) {
+        validateFeatureBounds(bounds, type);
+        mBounds = new Rect(bounds);
+        mType = type;
+        mState = state;
+    }
+
+    @NonNull
+    @Override
+    public Rect getBounds() {
+        return new Rect(mBounds);
+    }
+
+    @Type
+    public int getType() {
+        return mType;
+    }
+
+    @State
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Verifies the bounds of the folding feature.
+     */
+    private static void validateFeatureBounds(@NonNull Rect bounds, int type) {
+        if (bounds.width() == 0 && bounds.height() == 0) {
+            throw new IllegalArgumentException("Bounds must be non zero");
+        }
+        if (type == TYPE_FOLD) {
+            if (bounds.width() != 0 && bounds.height() != 0) {
+                throw new IllegalArgumentException("Bounding rectangle must be either zero-wide "
+                        + "or zero-high for features of type " + typeToString(type));
+            }
+
+            if ((bounds.width() != 0 && bounds.left != 0)
+                    || (bounds.height() != 0 && bounds.top != 0)) {
+                throw new IllegalArgumentException("Bounding rectangle must span the entire "
+                        + "window space for features of type " + typeToString(type));
+            }
+        } else if (type == TYPE_HINGE) {
+            if (bounds.left != 0 && bounds.top != 0) {
+                throw new IllegalArgumentException("Bounding rectangle must span the entire "
+                        + "window space for features of type " + typeToString(type));
+            }
+        }
+    }
+
+    @NonNull
+    private static String typeToString(int type) {
+        switch (type) {
+            case TYPE_FOLD:
+                return "FOLD";
+            case TYPE_HINGE:
+                return "HINGE";
+            default:
+                return "Unknown feature type (" + type + ")";
+        }
+    }
+
+    @NonNull
+    private static String stateToString(int state) {
+        switch (state) {
+            case STATE_FLAT:
+                return "FLAT";
+            case STATE_FLIPPED:
+                return "FLIPPED";
+            case STATE_HALF_OPENED:
+                return "HALF_OPENED";
+            default:
+                return "Unknown feature state (" + state + ")";
+        }
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return FoldingFeature.class.getSimpleName() + " { " + mBounds + ", type="
+                + typeToString(getType()) + ", state=" + stateToString(mState) + " }";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof FoldingFeature)) return false;
+        FoldingFeature that = (FoldingFeature) o;
+        return mType == that.mType
+            && mState == that.mState
+            && mBounds.equals(that.mBounds);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mBounds.hashCode();
+        result = 31 * result + mType;
+        result = 31 * result + mState;
+        return result;
+    }
+}
diff --git a/window/window/src/main/java/androidx/window/SidecarAdapter.java b/window/window/src/main/java/androidx/window/SidecarAdapter.java
index 1a5ceb2..b7dcbdc 100644
--- a/window/window/src/main/java/androidx/window/SidecarAdapter.java
+++ b/window/window/src/main/java/androidx/window/SidecarAdapter.java
@@ -20,16 +20,20 @@
 import static androidx.window.DeviceState.POSTURE_UNKNOWN;
 import static androidx.window.ExtensionCompat.DEBUG;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.graphics.Rect;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.window.sidecar.SidecarDeviceState;
 import androidx.window.sidecar.SidecarDisplayFeature;
 import androidx.window.sidecar.SidecarWindowLayoutInfo;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -42,14 +46,17 @@
 
     @NonNull
     List<DisplayFeature> translate(SidecarWindowLayoutInfo sidecarWindowLayoutInfo,
-            Rect windowBounds) {
+            SidecarDeviceState deviceState, Rect windowBounds) {
         List<DisplayFeature> displayFeatures = new ArrayList<>();
-        if (sidecarWindowLayoutInfo.displayFeatures == null) {
+        List<SidecarDisplayFeature> sidecarDisplayFeatures =
+                getSidecarDisplayFeatures(sidecarWindowLayoutInfo);
+        if (sidecarDisplayFeatures == null) {
             return displayFeatures;
         }
 
-        for (SidecarDisplayFeature sidecarFeature : sidecarWindowLayoutInfo.displayFeatures) {
-            final DisplayFeature displayFeature = translate(sidecarFeature, windowBounds);
+        for (SidecarDisplayFeature sidecarFeature : sidecarDisplayFeatures) {
+            final DisplayFeature displayFeature = translate(sidecarFeature, deviceState,
+                    windowBounds);
             if (displayFeature != null) {
                 displayFeatures.add(displayFeature);
             }
@@ -57,32 +64,89 @@
         return displayFeatures;
     }
 
+    // TODO(b/172620880): Workaround for Sidecar API implementation issue.
+    @SuppressLint("BanUncheckedReflection")
+    @SuppressWarnings("unchecked")
+    @VisibleForTesting
+    @Nullable
+    static List<SidecarDisplayFeature> getSidecarDisplayFeatures(SidecarWindowLayoutInfo info) {
+        try {
+            return info.displayFeatures;
+        } catch (NoSuchFieldError error) {
+            try {
+                Method methodGetFeatures = SidecarWindowLayoutInfo.class.getMethod(
+                        "getDisplayFeatures");
+                return (List<SidecarDisplayFeature>) methodGetFeatures.invoke(info);
+            } catch (NoSuchMethodException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (IllegalAccessException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (InvocationTargetException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+
+    // TODO(b/172620880): Workaround for Sidecar API implementation issue.
+    @SuppressLint("BanUncheckedReflection")
+    @VisibleForTesting
+    static void setSidecarDisplayFeatures(SidecarWindowLayoutInfo info,
+            List<SidecarDisplayFeature> displayFeatures) {
+        try {
+            info.displayFeatures = displayFeatures;
+        } catch (NoSuchFieldError error) {
+            try {
+                Method methodSetFeatures = SidecarWindowLayoutInfo.class.getMethod(
+                        "setDisplayFeatures", List.class);
+                methodSetFeatures.invoke(info, displayFeatures);
+            } catch (NoSuchMethodException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (IllegalAccessException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (InvocationTargetException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
     @NonNull
     WindowLayoutInfo translate(@NonNull Activity activity,
-            @Nullable SidecarWindowLayoutInfo extensionInfo) {
+            @Nullable SidecarWindowLayoutInfo extensionInfo, @NonNull SidecarDeviceState state) {
         if (extensionInfo == null) {
             return new WindowLayoutInfo(new ArrayList<>());
         }
 
+        SidecarDeviceState deviceState = new SidecarDeviceState();
+        int devicePosture = getSidecarDevicePosture(state);
+        setSidecarDevicePosture(deviceState, devicePosture);
+
         Rect windowBounds = WindowBoundsHelper.getInstance().computeCurrentWindowBounds(activity);
-        List<DisplayFeature> displayFeatures = translate(extensionInfo, windowBounds);
+        List<DisplayFeature> displayFeatures = translate(extensionInfo, deviceState, windowBounds);
         return new WindowLayoutInfo(displayFeatures);
     }
 
     @NonNull
-    DeviceState translate(@Nullable SidecarDeviceState sidecarDeviceState) {
-        if (sidecarDeviceState == null) {
-            return new DeviceState(POSTURE_UNKNOWN);
-        }
-
+    DeviceState translate(@NonNull SidecarDeviceState sidecarDeviceState) {
         int posture = postureFromSidecar(sidecarDeviceState);
         return new DeviceState(posture);
     }
 
-
     @DeviceState.Posture
     private static int postureFromSidecar(SidecarDeviceState sidecarDeviceState) {
-        int sidecarPosture = sidecarDeviceState.posture;
+        int sidecarPosture = getSidecarDevicePosture(sidecarDeviceState);
         if (sidecarPosture > POSTURE_MAX_KNOWN) {
             if (DEBUG) {
                 Log.d(TAG, "Unknown posture reported, WindowManager library should be updated");
@@ -92,12 +156,67 @@
         return sidecarPosture;
     }
 
+    // TODO(b/172620880): Workaround for Sidecar API implementation issue.
+    @SuppressLint("BanUncheckedReflection")
+    @VisibleForTesting
+    static int getSidecarDevicePosture(SidecarDeviceState sidecarDeviceState) {
+        try {
+            return sidecarDeviceState.posture;
+        } catch (NoSuchFieldError error) {
+            try {
+                Method methodGetPosture = SidecarDeviceState.class.getMethod("getPosture");
+                return (int) methodGetPosture.invoke(sidecarDeviceState);
+            } catch (NoSuchMethodException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (IllegalAccessException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (InvocationTargetException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return SidecarDeviceState.POSTURE_UNKNOWN;
+    }
+
+    // TODO(b/172620880): Workaround for Sidecar API implementation issue.
+    @SuppressLint("BanUncheckedReflection")
+    @VisibleForTesting
+    static void setSidecarDevicePosture(SidecarDeviceState sidecarDeviceState, int posture) {
+        try {
+            sidecarDeviceState.posture = posture;
+        } catch (NoSuchFieldError error) {
+            try {
+                Method methodSetPosture = SidecarDeviceState.class.getMethod("setPosture",
+                        int.class);
+                methodSetPosture.invoke(sidecarDeviceState, posture);
+            } catch (NoSuchMethodException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (IllegalAccessException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            } catch (InvocationTargetException e) {
+                if (DEBUG) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
     /**
      * Converts the display feature from extension. Can return {@code null} if there is an issue
      * with the value passed from extension.
      */
     @Nullable
-    private static DisplayFeature translate(SidecarDisplayFeature feature, Rect windowBounds) {
+    private static DisplayFeature translate(SidecarDisplayFeature feature,
+            SidecarDeviceState deviceState, Rect windowBounds) {
         Rect bounds = feature.getRect();
         if (bounds.width() == 0 && bounds.height() == 0) {
             if (DEBUG) {
@@ -131,6 +250,31 @@
             }
         }
 
-        return new DisplayFeature(feature.getRect(), feature.getType());
+        final int type;
+        if (feature.getType() == SidecarDisplayFeature.TYPE_HINGE) {
+            type = FoldingFeature.TYPE_HINGE;
+        } else {
+            type = FoldingFeature.TYPE_FOLD;
+        }
+
+        final int state;
+        final int devicePosture = getSidecarDevicePosture(deviceState);
+        switch (devicePosture) {
+            case SidecarDeviceState.POSTURE_CLOSED:
+            case SidecarDeviceState.POSTURE_UNKNOWN:
+                return null;
+            case SidecarDeviceState.POSTURE_FLIPPED:
+                state = FoldingFeature.STATE_FLIPPED;
+                break;
+            case SidecarDeviceState.POSTURE_HALF_OPENED:
+                state = FoldingFeature.STATE_HALF_OPENED;
+                break;
+            case SidecarDeviceState.POSTURE_OPENED:
+            default:
+                state = FoldingFeature.STATE_FLAT;
+                break;
+        }
+
+        return new FoldingFeature(feature.getRect(), type, state);
     }
 }
diff --git a/window/window/src/main/java/androidx/window/SidecarCompat.java b/window/window/src/main/java/androidx/window/SidecarCompat.java
index 7de2b77..295151b 100644
--- a/window/window/src/main/java/androidx/window/SidecarCompat.java
+++ b/window/window/src/main/java/androidx/window/SidecarCompat.java
@@ -32,7 +32,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.SimpleArrayMap;
-import androidx.core.util.Consumer;
 import androidx.window.sidecar.SidecarDeviceState;
 import androidx.window.sidecar.SidecarDisplayFeature;
 import androidx.window.sidecar.SidecarInterface;
@@ -40,6 +39,7 @@
 import androidx.window.sidecar.SidecarWindowLayoutInfo;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.List;
 
 /** Extension interface compatibility wrapper for v0.1 sidecar. */
@@ -53,7 +53,7 @@
             new SimpleArrayMap<>();
 
     private ExtensionCallbackInterface mExtensionCallback;
-    private SidecarAdapter mSidecarAdapter;
+    private final SidecarAdapter mSidecarAdapter;
 
     @VisibleForTesting
     final SidecarInterface mSidecar;
@@ -92,6 +92,17 @@
             @SuppressLint("SyntheticAccessor")
             public void onDeviceStateChanged(@NonNull SidecarDeviceState newDeviceState) {
                 extensionCallback.onDeviceStateChanged(mSidecarAdapter.translate(newDeviceState));
+
+                for (int i = 0; i < mWindowListenerRegisteredContexts.size(); i++) {
+                    Activity activity = mWindowListenerRegisteredContexts.valueAt(i);
+                    IBinder windowToken = getActivityWindowToken(activity);
+                    if (windowToken == null) {
+                        continue;
+                    }
+                    SidecarWindowLayoutInfo layoutInfo = mSidecar.getWindowLayoutInfo(windowToken);
+                    extensionCallback.onWindowLayoutChanged(activity,
+                            mSidecarAdapter.translate(activity, layoutInfo, newDeviceState));
+                }
             }
 
             @Override
@@ -106,7 +117,7 @@
                 }
 
                 extensionCallback.onWindowLayoutChanged(activity,
-                        mSidecarAdapter.translate(activity, newLayout));
+                        mSidecarAdapter.translate(activity, newLayout, mSidecar.getDeviceState()));
             }
         });
     }
@@ -117,7 +128,7 @@
         IBinder windowToken = getActivityWindowToken(activity);
 
         SidecarWindowLayoutInfo windowLayoutInfo = mSidecar.getWindowLayoutInfo(windowToken);
-        return mSidecarAdapter.translate(activity, windowLayoutInfo);
+        return mSidecarAdapter.translate(activity, windowLayoutInfo, mSidecar.getDeviceState());
     }
 
     @Override
@@ -127,7 +138,8 @@
         if (windowToken != null) {
             register(windowToken, activity);
         } else {
-            FirstAttachAdapter attachAdapter = new FirstAttachAdapter((token) -> {
+            FirstAttachAdapter attachAdapter = new FirstAttachAdapter(() -> {
+                IBinder token = getActivityWindowToken(activity);
                 register(token, activity);
             });
             activity.getWindow().getDecorView().addOnAttachStateChangeListener(attachAdapter);
@@ -159,6 +171,7 @@
         mSidecar.onDeviceStateListenersChanged(isEmpty);
     }
 
+    @SuppressLint("BanUncheckedReflection")
     @Override
     @SuppressWarnings("unused")
     public boolean validateExtensionInterface() {
@@ -215,7 +228,24 @@
             tmpDeviceState = new SidecarDeviceState();
 
             // deviceState.posture
-            tmpDeviceState.posture = SidecarDeviceState.POSTURE_OPENED;
+            // TODO(b/172620880): Workaround for Sidecar API implementation issue.
+            try {
+                tmpDeviceState.posture = SidecarDeviceState.POSTURE_OPENED;
+            } catch (NoSuchFieldError error) {
+                if (DEBUG) {
+                    Log.w(TAG, "Sidecar implementation doesn't conform to primary interface "
+                            + "version, continue to check for the secondary one "
+                            + VERSION_0_1 + ", error: " + error);
+                }
+                Method methodSetPosture = SidecarDeviceState.class.getMethod("setPosture",
+                        int.class);
+                methodSetPosture.invoke(tmpDeviceState, SidecarDeviceState.POSTURE_OPENED);
+                Method methodGetPosture = SidecarDeviceState.class.getMethod("getPosture");
+                int posture = (int) methodGetPosture.invoke(tmpDeviceState);
+                if (posture != SidecarDeviceState.POSTURE_OPENED) {
+                    throw new Exception("Invalid device posture getter/setter");
+                }
+            }
 
             // SidecarDisplayFeature constructor
             SidecarDisplayFeature displayFeature = new SidecarDisplayFeature();
@@ -232,13 +262,36 @@
             SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
 
             // windowLayoutInfo.displayFeatures
-            final List<SidecarDisplayFeature> tmpDisplayFeatures = windowLayoutInfo.displayFeatures;
+            try {
+                final List<SidecarDisplayFeature> tmpDisplayFeatures =
+                        windowLayoutInfo.displayFeatures;
+                // TODO(b/172620880): Workaround for Sidecar API implementation issue.
+            } catch (NoSuchFieldError error) {
+                if (DEBUG) {
+                    Log.w(TAG, "Sidecar implementation doesn't conform to primary interface "
+                            + "version, continue to check for the secondary one "
+                            + VERSION_0_1 + ", error: " + error);
+                }
+                List<SidecarDisplayFeature> featureList = new ArrayList<>();
+                featureList.add(displayFeature);
+                Method methodSetFeatures = SidecarWindowLayoutInfo.class.getMethod(
+                        "setDisplayFeatures", List.class);
+                methodSetFeatures.invoke(windowLayoutInfo, featureList);
+                Method methodGetFeatures = SidecarWindowLayoutInfo.class.getMethod(
+                        "getDisplayFeatures");
+                @SuppressWarnings("unchecked")
+                final List<SidecarDisplayFeature> resultDisplayFeatures =
+                        (List<SidecarDisplayFeature>) methodGetFeatures.invoke(windowLayoutInfo);
+                if (!featureList.equals(resultDisplayFeatures)) {
+                    throw new Exception("Invalid display feature getter/setter");
+                }
+            }
 
             return true;
-        } catch (Exception e) {
+        } catch (Throwable t) {
             if (DEBUG) {
-                Log.e(TAG, "Extension implementation doesn't conform to interface version "
-                        + VERSION_0_1 + ", error: " + e);
+                Log.e(TAG, "Sidecar implementation doesn't conform to interface version "
+                        + VERSION_0_1 + ", error: " + t);
             }
             return false;
         }
@@ -273,15 +326,15 @@
      */
     private static class FirstAttachAdapter implements View.OnAttachStateChangeListener {
 
-        private final Consumer<IBinder> mCallback;
+        private final Runnable mCallback;
 
-        FirstAttachAdapter(Consumer<IBinder> callback) {
+        FirstAttachAdapter(Runnable callback) {
             mCallback = callback;
         }
 
         @Override
         public void onViewAttachedToWindow(View view) {
-            mCallback.accept(view.getWindowToken());
+            mCallback.run();
             view.removeOnAttachStateChangeListener(this);
         }
 
diff --git a/window/window/src/main/java/androidx/window/WindowManager.java b/window/window/src/main/java/androidx/window/WindowManager.java
index 6d83906..56d2988 100644
--- a/window/window/src/main/java/androidx/window/WindowManager.java
+++ b/window/window/src/main/java/androidx/window/WindowManager.java
@@ -50,6 +50,7 @@
      * Gets an instance of the class initialized with and connected to the provided {@link Context}.
      * All methods of this class will return information that is associated with this visual
      * context.
+     *
      * @param context A visual context, such as an {@link Activity} or a {@link ContextWrapper}
      *                around one, to use for initialization.
      */
@@ -61,8 +62,10 @@
      * Gets an instance of the class initialized with and connected to the provided {@link Context}.
      * All methods of this class will return information that is associated with this visual
      * context.
-     * @param context A visual context, such as an {@link Activity} or a {@link ContextWrapper}
-     *                around one, to use for initialization.
+     *
+     * @param context       A visual context, such as an {@link Activity} or a
+     * {@link ContextWrapper}
+     *                      around one, to use for initialization.
      * @param windowBackend Backing server class that will provide information for this instance.
      *                      Pass a custom {@link WindowBackend} implementation for testing.
      */
@@ -80,6 +83,7 @@
     /**
      * Registers a callback for layout changes of the window of the current visual {@link Context}.
      * Must be called only after the it is attached to the window.
+     *
      * @see Activity#onAttachedToWindow()
      */
     public void registerLayoutChangeCallback(@NonNull Executor executor,
@@ -95,16 +99,20 @@
     }
 
     /**
+     * @deprecated {@link DeviceState} information has been merged into {@link WindowLayoutInfo}
      * Registers a callback for device state changes.
      */
+    @Deprecated
     public void registerDeviceStateChangeCallback(@NonNull Executor executor,
             @NonNull Consumer<DeviceState> callback) {
         mWindowBackend.registerDeviceStateChangeCallback(executor, callback);
     }
 
     /**
+     * @deprecated {@link DeviceState} information has been merged into {@link WindowLayoutInfo}
      * Unregisters a callback for device state changes.
      */
+    @Deprecated
     public void unregisterDeviceStateChangeCallback(@NonNull Consumer<DeviceState> callback) {
         mWindowBackend.unregisterDeviceStateChangeCallback(callback);
     }
@@ -160,6 +168,7 @@
 
     /**
      * Unwraps the hierarchy of {@link ContextWrapper}-s until {@link Activity} is reached.
+     *
      * @return Base {@link Activity} context or {@code null} if not available.
      */
     @Nullable
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/Logger.java b/window/window/src/test/java/androidx/window/ActivityTestUtil.java
similarity index 69%
rename from car/app/app/src/main/java/androidx/car/app/utils/Logger.java
rename to window/window/src/test/java/androidx/window/ActivityTestUtil.java
index a402482..2d6d5d7 100644
--- a/car/app/app/src/main/java/androidx/car/app/utils/Logger.java
+++ b/window/window/src/test/java/androidx/window/ActivityTestUtil.java
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 
-package androidx.car.app.utils;
+package androidx.window;
+
+import android.app.Activity;
+import android.os.IBinder;
 
 import androidx.annotation.NonNull;
 
-/**
- * Logger interface to allow the host to log while using the client library.
- */
-// TODO: Allow setting logging severity and including throwables
-public interface Logger {
-    void log(@NonNull String message);
+public class ActivityTestUtil {
+
+    private ActivityTestUtil() { }
+
+    static IBinder getActivityWindowToken(@NonNull Activity activity) {
+        return activity.getWindow().getAttributes().token;
+    }
 }
diff --git a/window/window/src/test/java/androidx/window/ExtensionWindowBackendUnitTest.java b/window/window/src/test/java/androidx/window/ExtensionWindowBackendUnitTest.java
new file mode 100644
index 0000000..b79864f
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/ExtensionWindowBackendUnitTest.java
@@ -0,0 +1,367 @@
+/*
+ * 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.window;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
+
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Range;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ExtensionWindowBackend} that run on the JVM.
+ */
+@SuppressWarnings("deprecation") // TODO(b/173739071) Remove DeviceState
+public class ExtensionWindowBackendUnitTest {
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = mock(Context.class);
+        ExtensionWindowBackend.resetInstance();
+    }
+
+    @Test
+    public void testGetInstance() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        assertNotNull(backend);
+
+        // Verify that getInstance always returns the same value
+        ExtensionWindowBackend newBackend = ExtensionWindowBackend.getInstance(mContext);
+        assertEquals(backend, newBackend);
+    }
+
+    @Test
+    public void testRegisterDeviceStateChangeCallback_noExtension() {
+        // Verify method with extension
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        assumeTrue(backend.mWindowExtension == null);
+        SimpleConsumer<DeviceState> simpleConsumer = new SimpleConsumer<>();
+
+        backend.registerDeviceStateChangeCallback(directExecutor(), simpleConsumer);
+
+        DeviceState deviceState = simpleConsumer.lastValue();
+        assertNotNull(deviceState);
+        assertThat(deviceState.getPosture()).isIn(Range.range(
+                DeviceState.POSTURE_UNKNOWN, BoundType.CLOSED,
+                DeviceState.POSTURE_MAX_KNOWN, BoundType.CLOSED));
+        DeviceState initialLastReportedState = backend.mLastReportedDeviceState;
+
+        // Verify method without extension
+        backend.mWindowExtension = null;
+        SimpleConsumer<DeviceState> noExtensionConsumer = new SimpleConsumer<>();
+        backend.registerDeviceStateChangeCallback(directExecutor(), noExtensionConsumer);
+        deviceState = noExtensionConsumer.lastValue();
+        assertNotNull(deviceState);
+        assertEquals(DeviceState.POSTURE_UNKNOWN, deviceState.getPosture());
+        // Verify that last reported state does not change when using the getter
+        assertEquals(initialLastReportedState, backend.mLastReportedDeviceState);
+    }
+
+    @Test
+    public void testRegisterLayoutChangeCallback() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
+
+        // Check registering the layout change callback
+        Consumer<WindowLayoutInfo> consumer = mock(WindowLayoutInfoConsumer.class);
+        Activity activity = mock(Activity.class);
+        backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
+
+        assertEquals(1, backend.mWindowLayoutChangeCallbacks.size());
+        verify(backend.mWindowExtension).onWindowLayoutChangeListenerAdded(activity);
+
+        // Check unregistering the layout change callback
+        backend.unregisterLayoutChangeCallback(consumer);
+
+        assertTrue(backend.mWindowLayoutChangeCallbacks.isEmpty());
+        verify(backend.mWindowExtension).onWindowLayoutChangeListenerRemoved(eq(activity));
+    }
+
+    @Test
+    public void testRegisterLayoutChangeCallback_synchronousExtension() {
+        WindowLayoutInfo expectedInfo = newTestWindowLayoutInfo();
+        ExtensionInterfaceCompat extensionInterfaceCompat =
+                new SynchronousExtensionInterface(expectedInfo,
+                newTestDeviceState());
+        ExtensionWindowBackend backend = new ExtensionWindowBackend(extensionInterfaceCompat);
+
+        // Check registering the layout change callback
+        Consumer<WindowLayoutInfo> consumer = mock(WindowLayoutInfoConsumer.class);
+        Activity activity = mock(Activity.class);
+        backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
+
+        // Check unregistering the layout change callback
+        verify(consumer).accept(expectedInfo);
+    }
+
+    @Test
+    public void testRegisterLayoutChangeCallback_callsExtensionOnce() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
+
+        // Check registering the layout change callback
+        Consumer<WindowLayoutInfo> consumer = mock(WindowLayoutInfoConsumer.class);
+        Activity activity = mock(Activity.class);
+        backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
+        backend.registerLayoutChangeCallback(activity, Runnable::run,
+                mock(WindowLayoutInfoConsumer.class));
+
+        assertEquals(2, backend.mWindowLayoutChangeCallbacks.size());
+        verify(backend.mWindowExtension).onWindowLayoutChangeListenerAdded(activity);
+
+        // Check unregistering the layout change callback
+        backend.unregisterLayoutChangeCallback(consumer);
+
+        assertEquals(1, backend.mWindowLayoutChangeCallbacks.size());
+        verify(backend.mWindowExtension, times(0))
+                .onWindowLayoutChangeListenerRemoved(eq(activity));
+    }
+
+    @Test
+    public void testRegisterLayoutChangeCallback_clearListeners() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
+
+        // Check registering the layout change callback
+        Consumer<WindowLayoutInfo> firstConsumer = mock(WindowLayoutInfoConsumer.class);
+        Consumer<WindowLayoutInfo> secondConsumer = mock(WindowLayoutInfoConsumer.class);
+        Activity activity = mock(Activity.class);
+        backend.registerLayoutChangeCallback(activity, Runnable::run, firstConsumer);
+        backend.registerLayoutChangeCallback(activity, Runnable::run, secondConsumer);
+
+        // Check unregistering the layout change callback
+        backend.unregisterLayoutChangeCallback(firstConsumer);
+        backend.unregisterLayoutChangeCallback(secondConsumer);
+
+        assertTrue(backend.mWindowLayoutChangeCallbacks.isEmpty());
+        verify(backend.mWindowExtension).onWindowLayoutChangeListenerRemoved(activity);
+    }
+
+    @Test
+    public void testRegisterDeviceChangeCallback() {
+        ExtensionInterfaceCompat mockInterface = mock(ExtensionInterfaceCompat.class);
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        backend.mWindowExtension = mockInterface;
+
+        // Check registering the device state change callback
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
+        backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
+
+        assertEquals(1, backend.mDeviceStateChangeCallbacks.size());
+        verify(backend.mWindowExtension).onDeviceStateListenersChanged(eq(false));
+
+        // Check unregistering the device state change callback
+        backend.unregisterDeviceStateChangeCallback(consumer);
+
+        assertTrue(backend.mDeviceStateChangeCallbacks.isEmpty());
+        verify(backend.mWindowExtension).onDeviceStateListenersChanged(eq(true));
+    }
+
+    @Test
+    public void testDeviceChangeCallback() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
+
+        // Check that callbacks from the extension are propagated correctly
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
+
+        backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
+        DeviceState deviceState = newTestDeviceState();
+        ExtensionWindowBackend.ExtensionListenerImpl backendListener =
+                backend.new ExtensionListenerImpl();
+        backendListener.onDeviceStateChanged(deviceState);
+
+        verify(consumer, times(1)).accept(eq(deviceState));
+        assertEquals(deviceState, backend.mLastReportedDeviceState);
+
+        // Test that the same value wouldn't be reported again
+        backendListener.onDeviceStateChanged(deviceState);
+        verify(consumer, times(1)).accept(any());
+    }
+
+    @Test
+    public void testDeviceChangeChangeCallback_callsExtensionOnce() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
+
+        // Check registering the layout change callback
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
+        backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
+        backend.registerDeviceStateChangeCallback(Runnable::run, mock(DeviceStateConsumer.class));
+
+        assertEquals(2, backend.mDeviceStateChangeCallbacks.size());
+        verify(backend.mWindowExtension).onDeviceStateListenersChanged(false);
+
+        // Check unregistering the layout change callback
+        backend.unregisterDeviceStateChangeCallback(consumer);
+
+        assertEquals(1, backend.mDeviceStateChangeCallbacks.size());
+        verify(backend.mWindowExtension, times(0))
+                .onDeviceStateListenersChanged(true);
+    }
+
+    @Test
+    public void testDeviceChangeChangeCallback_clearListeners() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
+
+        // Check registering the layout change callback
+        Consumer<DeviceState> firstConsumer = mock(DeviceStateConsumer.class);
+        Consumer<DeviceState> secondConsumer = mock(DeviceStateConsumer.class);
+        backend.registerDeviceStateChangeCallback(Runnable::run, firstConsumer);
+        backend.registerDeviceStateChangeCallback(Runnable::run, secondConsumer);
+
+        // Check unregistering the layout change callback
+        backend.unregisterDeviceStateChangeCallback(firstConsumer);
+        backend.unregisterDeviceStateChangeCallback(secondConsumer);
+
+        assertTrue(backend.mDeviceStateChangeCallbacks.isEmpty());
+        verify(backend.mWindowExtension).onDeviceStateListenersChanged(true);
+    }
+
+    @Test
+    public void testDeviceChangeCallback_relayLastEmittedValue() {
+        DeviceState expectedState = newTestDeviceState();
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
+        backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
+        backend.mLastReportedDeviceState = expectedState;
+
+        backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
+
+        verify(consumer).accept(expectedState);
+    }
+
+    @Test
+    public void testDeviceChangeCallback_clearLastEmittedValue() {
+        ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
+        Consumer<DeviceState> consumer = mock(DeviceStateConsumer.class);
+
+        backend.registerDeviceStateChangeCallback(Runnable::run, consumer);
+        backend.unregisterDeviceStateChangeCallback(consumer);
+
+        assertTrue(backend.mDeviceStateChangeCallbacks.isEmpty());
+        assertNull(backend.mLastReportedDeviceState);
+    }
+
+    private static WindowLayoutInfo newTestWindowLayoutInfo() {
+        WindowLayoutInfo.Builder builder = new WindowLayoutInfo.Builder();
+        return builder.build();
+    }
+
+    private static DeviceState newTestDeviceState() {
+        DeviceState.Builder builder = new DeviceState.Builder();
+        builder.setPosture(DeviceState.POSTURE_OPENED);
+        return builder.build();
+    }
+
+    private interface DeviceStateConsumer extends Consumer<DeviceState> { }
+
+    private interface WindowLayoutInfoConsumer extends Consumer<WindowLayoutInfo> { }
+
+    private static class SimpleConsumer<T> implements Consumer<T> {
+        private final List<T> mValues;
+
+        SimpleConsumer() {
+            mValues = new ArrayList<>();
+        }
+
+        @Override
+        public void accept(T t) {
+            mValues.add(t);
+        }
+
+        T lastValue() {
+            return mValues.get(mValues.size() - 1);
+        }
+    }
+
+    private static class SynchronousExtensionInterface implements ExtensionInterfaceCompat {
+
+        private ExtensionCallbackInterface mInterface;
+        private final DeviceState mDeviceState;
+        private final WindowLayoutInfo mWindowLayoutInfo;
+
+        SynchronousExtensionInterface(WindowLayoutInfo windowLayoutInfo, DeviceState deviceState) {
+            mInterface = new ExtensionCallbackInterface() {
+                @Override
+                public void onDeviceStateChanged(@NonNull @NotNull DeviceState newDeviceState) {
+
+                }
+
+                @Override
+                public void onWindowLayoutChanged(@NonNull @NotNull Activity activity,
+                        @NonNull @NotNull WindowLayoutInfo newLayout) {
+
+                }
+            };
+            mWindowLayoutInfo = windowLayoutInfo;
+            mDeviceState = deviceState;
+        }
+
+        @Override
+        public boolean validateExtensionInterface() {
+            return true;
+        }
+
+        @Override
+        public void setExtensionCallback(
+                @NonNull @NotNull ExtensionCallbackInterface extensionCallback) {
+            mInterface = extensionCallback;
+        }
+
+        @Override
+        public void onWindowLayoutChangeListenerAdded(@NonNull @NotNull Activity activity) {
+            mInterface.onWindowLayoutChanged(activity, mWindowLayoutInfo);
+        }
+
+        @Override
+        public void onWindowLayoutChangeListenerRemoved(@NonNull @NotNull Activity activity) {
+
+        }
+
+        @Override
+        public void onDeviceStateListenersChanged(boolean isEmpty) {
+            mInterface.onDeviceStateChanged(mDeviceState);
+        }
+    }
+}
diff --git a/window/window/src/test/java/androidx/window/SidecarCompatUnitTest.java b/window/window/src/test/java/androidx/window/SidecarCompatUnitTest.java
new file mode 100644
index 0000000..a7b9da2
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/SidecarCompatUnitTest.java
@@ -0,0 +1,376 @@
+/*
+ * 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.window;
+
+import static androidx.window.ActivityTestUtil.getActivityWindowToken;
+import static androidx.window.TestBoundsUtil.invalidFoldBounds;
+import static androidx.window.TestBoundsUtil.invalidHingeBounds;
+import static androidx.window.TestBoundsUtil.validFoldBound;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.window.sidecar.SidecarDeviceState;
+import androidx.window.sidecar.SidecarDisplayFeature;
+import androidx.window.sidecar.SidecarInterface;
+import androidx.window.sidecar.SidecarWindowLayoutInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link SidecarCompat} that run on the JVM.
+ */
+public final class SidecarCompatUnitTest {
+
+    private static final Rect WINDOW_BOUNDS = new Rect(1, 1, 50, 100);
+
+    private Activity mActivity;
+    private SidecarCompat mSidecarCompat;
+
+    @Before
+    public void setUp() {
+        TestWindowBoundsHelper mWindowBoundsHelper = new TestWindowBoundsHelper();
+        mWindowBoundsHelper.setCurrentBounds(WINDOW_BOUNDS);
+        WindowBoundsHelper.setForTesting(mWindowBoundsHelper);
+
+        mActivity = mock(Activity.class);
+        Window window = spy(new TestWindow(mActivity));
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        doReturn(params).when(window).getAttributes();
+        when(mActivity.getWindow()).thenReturn(window);
+
+        SidecarInterface mockSidecarInterface = mock(SidecarInterface.class);
+        when(mockSidecarInterface.getDeviceState()).thenReturn(
+                newDeviceState(DeviceState.POSTURE_FLIPPED));
+        when(mockSidecarInterface.getWindowLayoutInfo(any())).thenReturn(
+                newWindowLayoutInfo(new ArrayList<>()));
+        mSidecarCompat = new SidecarCompat(mockSidecarInterface, new SidecarAdapter());
+    }
+
+    @After
+    public void tearDown() {
+        WindowBoundsHelper.setForTesting(null);
+    }
+
+    @Test
+    public void testGetDeviceState() {
+        FakeExtensionImp fakeSidecarImp = new FakeExtensionImp();
+        SidecarCompat compat = new SidecarCompat(fakeSidecarImp, new SidecarAdapter());
+        ExtensionInterfaceCompat.ExtensionCallbackInterface mockCallback = mock(
+                ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
+        compat.setExtensionCallback(mockCallback);
+        compat.onDeviceStateListenersChanged(false);
+        SidecarDeviceState deviceState = newDeviceState(SidecarDeviceState.POSTURE_OPENED);
+
+        fakeSidecarImp.triggerDeviceState(deviceState);
+
+        verify(mockCallback).onDeviceStateChanged(any());
+    }
+
+    @Test
+    public void testGetWindowLayout_featureWithEmptyBounds() {
+        // Add a feature with an empty bounds to the reported list
+        SidecarWindowLayoutInfo originalWindowLayoutInfo =
+                mSidecarCompat.mSidecar.getWindowLayoutInfo(getActivityWindowToken(mActivity));
+        List<SidecarDisplayFeature> sidecarDisplayFeatures =
+                originalWindowLayoutInfo.displayFeatures;
+        SidecarDisplayFeature newFeature = new SidecarDisplayFeature();
+        newFeature.setRect(new Rect());
+        sidecarDisplayFeatures.add(newFeature);
+
+        // Verify that this feature is skipped.
+        WindowLayoutInfo windowLayoutInfo = mSidecarCompat.getWindowLayoutInfo(mActivity);
+
+        assertEquals(sidecarDisplayFeatures.size() - 1,
+                windowLayoutInfo.getDisplayFeatures().size());
+    }
+
+    @Test
+    public void testGetWindowLayout_foldWithNonZeroArea() {
+        SidecarWindowLayoutInfo originalWindowLayoutInfo =
+                mSidecarCompat.mSidecar.getWindowLayoutInfo(mock(IBinder.class));
+        List<SidecarDisplayFeature> sidecarDisplayFeatures =
+                originalWindowLayoutInfo.displayFeatures;
+        // Horizontal fold.
+        sidecarDisplayFeatures.add(
+                newDisplayFeature(new Rect(0, 1, WINDOW_BOUNDS.width(), 2),
+                        SidecarDisplayFeature.TYPE_FOLD));
+        // Vertical fold.
+        sidecarDisplayFeatures.add(
+                newDisplayFeature(new Rect(1, 0, 2, WINDOW_BOUNDS.height()),
+                        SidecarDisplayFeature.TYPE_FOLD));
+
+        // Verify that these features are skipped.
+        WindowLayoutInfo windowLayoutInfo =
+                mSidecarCompat.getWindowLayoutInfo(mActivity);
+
+        assertEquals(sidecarDisplayFeatures.size() - 2,
+                windowLayoutInfo.getDisplayFeatures().size());
+    }
+
+    @Test
+    public void testGetWindowLayout_hingeNotSpanningEntireWindow() {
+        SidecarWindowLayoutInfo originalWindowLayoutInfo =
+                mSidecarCompat.mSidecar.getWindowLayoutInfo(mock(IBinder.class));
+        List<SidecarDisplayFeature> sidecarDisplayFeatures =
+                originalWindowLayoutInfo.displayFeatures;
+        // Horizontal hinge.
+        sidecarDisplayFeatures.add(
+                newDisplayFeature(new Rect(0, 1, WINDOW_BOUNDS.width() - 1, 2),
+                        SidecarDisplayFeature.TYPE_FOLD));
+        // Vertical hinge.
+        sidecarDisplayFeatures.add(
+                newDisplayFeature(new Rect(1, 0, 2, WINDOW_BOUNDS.height() - 1),
+                        SidecarDisplayFeature.TYPE_FOLD));
+
+        // Verify that these features are skipped.
+        WindowLayoutInfo windowLayoutInfo =
+                mSidecarCompat.getWindowLayoutInfo(mActivity);
+
+        assertEquals(sidecarDisplayFeatures.size() - 2,
+                windowLayoutInfo.getDisplayFeatures().size());
+    }
+
+    @Test
+    public void testGetWindowLayout_foldNotSpanningEntireWindow() {
+        SidecarWindowLayoutInfo originalWindowLayoutInfo =
+                mSidecarCompat.mSidecar.getWindowLayoutInfo(mock(IBinder.class));
+        List<SidecarDisplayFeature> sidecarDisplayFeatures =
+                originalWindowLayoutInfo.displayFeatures;
+        // Horizontal fold.
+        sidecarDisplayFeatures.add(
+                newDisplayFeature(new Rect(0, 1, WINDOW_BOUNDS.width() - 1, 2),
+                        SidecarDisplayFeature.TYPE_FOLD));
+        // Vertical fold.
+        sidecarDisplayFeatures.add(
+                newDisplayFeature(new Rect(1, 0, 2, WINDOW_BOUNDS.height() - 1),
+                        SidecarDisplayFeature.TYPE_FOLD));
+
+        // Verify that these features are skipped.
+        WindowLayoutInfo windowLayoutInfo =
+                mSidecarCompat.getWindowLayoutInfo(mActivity);
+
+        assertEquals(sidecarDisplayFeatures.size() - 2,
+                windowLayoutInfo.getDisplayFeatures().size());
+    }
+
+    @Test
+    public void testOnWindowLayoutChangeListenerAdded() {
+        IBinder expectedToken = mock(IBinder.class);
+        mActivity.getWindow().getAttributes().token = expectedToken;
+        mSidecarCompat.onWindowLayoutChangeListenerAdded(mActivity);
+        verify(mSidecarCompat.mSidecar).onWindowLayoutChangeListenerAdded(eq(expectedToken));
+    }
+
+    @Test
+    public void testOnWindowLayoutChangeListenerAdded_emitInitialValueDelayed() {
+        SidecarWindowLayoutInfo layoutInfo = new SidecarWindowLayoutInfo();
+        WindowLayoutInfo expectedLayoutInfo = new WindowLayoutInfo(new ArrayList<>());
+        ExtensionInterfaceCompat.ExtensionCallbackInterface listener =
+                mock(ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
+        mSidecarCompat.setExtensionCallback(listener);
+        when(mSidecarCompat.mSidecar.getWindowLayoutInfo(any())).thenReturn(layoutInfo);
+        View fakeView = mock(View.class);
+        Window mockWindow = mock(Window.class);
+        when(mockWindow.getAttributes()).thenReturn(new WindowManager.LayoutParams());
+        doAnswer(invocation -> {
+            View.OnAttachStateChangeListener stateChangeListener = invocation.getArgument(0);
+            mockWindow.getAttributes().token = mock(IBinder.class);
+            stateChangeListener.onViewAttachedToWindow(fakeView);
+            return null;
+        }).when(fakeView).addOnAttachStateChangeListener(any());
+        when(mockWindow.getDecorView()).thenReturn(fakeView);
+        when(mActivity.getWindow()).thenReturn(mockWindow);
+
+        mSidecarCompat.onWindowLayoutChangeListenerAdded(mActivity);
+
+        verify(listener).onWindowLayoutChanged(mActivity, expectedLayoutInfo);
+        verify(mSidecarCompat.mSidecar).onWindowLayoutChangeListenerAdded(
+                getActivityWindowToken(mActivity));
+        verify(fakeView).addOnAttachStateChangeListener(any());
+    }
+
+    @Test
+    public void testOnWindowLayoutChangeListenerRemoved() {
+        IBinder windowToken = getActivityWindowToken(mActivity);
+        mSidecarCompat.onWindowLayoutChangeListenerRemoved(mActivity);
+        verify(mSidecarCompat.mSidecar).onWindowLayoutChangeListenerRemoved(eq(windowToken));
+    }
+
+    @Test
+    public void testOnDeviceStateListenersChanged() {
+        mSidecarCompat.onDeviceStateListenersChanged(true);
+        verify(mSidecarCompat.mSidecar).onDeviceStateListenersChanged(eq(true));
+    }
+
+    @Test
+    public void testOnDeviceStateListenersAdded_emitInitialValue() {
+        SidecarDeviceState deviceState = new SidecarDeviceState();
+        DeviceState expectedDeviceState = new DeviceState(DeviceState.POSTURE_UNKNOWN);
+        ExtensionInterfaceCompat.ExtensionCallbackInterface listener =
+                mock(ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
+        mSidecarCompat.setExtensionCallback(listener);
+        when(mSidecarCompat.mSidecar.getDeviceState()).thenReturn(deviceState);
+
+        mSidecarCompat.onDeviceStateListenersChanged(false);
+
+        verify(listener).onDeviceStateChanged(expectedDeviceState);
+    }
+
+    private static SidecarDisplayFeature newDisplayFeature(Rect rect, int type) {
+        SidecarDisplayFeature feature = new SidecarDisplayFeature();
+        feature.setRect(rect);
+        feature.setType(type);
+        return feature;
+    }
+
+    private static SidecarWindowLayoutInfo newWindowLayoutInfo(
+            List<SidecarDisplayFeature> features) {
+        SidecarWindowLayoutInfo info = new SidecarWindowLayoutInfo();
+        info.displayFeatures = new ArrayList<>();
+        info.displayFeatures.addAll(features);
+        return info;
+    }
+
+    private static SidecarDeviceState newDeviceState(int posture) {
+        SidecarDeviceState state = new SidecarDeviceState();
+        state.posture = posture;
+        return state;
+    }
+
+    private static final class FakeExtensionImp implements SidecarInterface {
+
+        private SidecarInterface.SidecarCallback mCallback;
+        private List<IBinder> mTokens = new ArrayList<>();
+
+        FakeExtensionImp() {
+            mCallback = new SidecarInterface.SidecarCallback() {
+                @Override
+                public void onDeviceStateChanged(@NonNull SidecarDeviceState newDeviceState) {
+
+                }
+
+                @Override
+                public void onWindowLayoutChanged(@NonNull IBinder windowToken,
+                        @NonNull SidecarWindowLayoutInfo newLayout) {
+
+                }
+            };
+        }
+
+        @Override
+        public void setSidecarCallback(@NonNull SidecarCallback callback) {
+
+        }
+
+        @NonNull
+        @Override
+        public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
+            return null;
+        }
+
+        @Override
+        public void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken) {
+
+        }
+
+        @Override
+        public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken) {
+
+        }
+
+        @NonNull
+        @Override
+        public SidecarDeviceState getDeviceState() {
+            SidecarDeviceState state = new SidecarDeviceState();
+            return state;
+        }
+
+        @Override
+        public void onDeviceStateListenersChanged(boolean isEmpty) {
+
+        }
+
+        void triggerMalformedSignal() {
+            triggerSignal(malformedWindowLayoutInfo());
+        }
+
+        void triggerGoodSignal() {
+            triggerSignal(validWindowLayoutInfo());
+        }
+
+        void triggerSignal(SidecarWindowLayoutInfo info) {
+            for (IBinder token: mTokens) {
+                triggerSignal(token, info);
+            }
+        }
+
+        void triggerSignal(IBinder token, SidecarWindowLayoutInfo info) {
+            mCallback.onWindowLayoutChanged(token, info);
+        }
+
+        public void triggerDeviceState(SidecarDeviceState state) {
+            mCallback.onDeviceStateChanged(state);
+
+        }
+
+        private SidecarWindowLayoutInfo malformedWindowLayoutInfo() {
+            List<SidecarDisplayFeature> malformedFeatures = new ArrayList<>();
+
+            for (Rect malformedBound : invalidFoldBounds(WINDOW_BOUNDS)) {
+                malformedFeatures.add(newDisplayFeature(malformedBound,
+                        SidecarDisplayFeature.TYPE_FOLD));
+            }
+
+            for (Rect malformedBound : invalidHingeBounds(WINDOW_BOUNDS)) {
+                malformedFeatures.add(newDisplayFeature(malformedBound,
+                        SidecarDisplayFeature.TYPE_HINGE));
+            }
+
+            return newWindowLayoutInfo(malformedFeatures);
+        }
+
+        private SidecarWindowLayoutInfo validWindowLayoutInfo() {
+            List<SidecarDisplayFeature> goodFeatures = new ArrayList<>();
+
+            goodFeatures.add(newDisplayFeature(validFoldBound(WINDOW_BOUNDS),
+                    SidecarDisplayFeature.TYPE_FOLD));
+
+            return newWindowLayoutInfo(goodFeatures);
+        }
+    }
+}
diff --git a/window/window/src/androidTest/java/androidx/window/TestBoundUtil.java b/window/window/src/test/java/androidx/window/TestBoundsUtil.java
similarity index 91%
rename from window/window/src/androidTest/java/androidx/window/TestBoundUtil.java
rename to window/window/src/test/java/androidx/window/TestBoundsUtil.java
index 18447f5..4988e40 100644
--- a/window/window/src/androidTest/java/androidx/window/TestBoundUtil.java
+++ b/window/window/src/test/java/androidx/window/TestBoundsUtil.java
@@ -24,7 +24,7 @@
 /**
  * A utility class to provide bounds for a display feature
  */
-class TestBoundUtil {
+class TestBoundsUtil {
 
     public static Rect validFoldBound(Rect windowBounds) {
         return new Rect(windowBounds.left, windowBounds.top, windowBounds.right, 0);
@@ -38,7 +38,7 @@
         return new Rect(windowBounds.left, windowBounds.top, windowBounds.right / 2, 2);
     }
 
-    public static Rect invalidBoundShortHeightHeight(Rect windowBounds) {
+    public static Rect invalidBoundShortHeight(Rect windowBounds) {
         return new Rect(windowBounds.left, windowBounds.top, 2, windowBounds.bottom / 2);
     }
 
@@ -47,7 +47,7 @@
 
         badBounds.add(invalidZeroBound());
         badBounds.add(invalidBoundShortWidth(windowBounds));
-        badBounds.add(invalidBoundShortHeightHeight(windowBounds));
+        badBounds.add(invalidBoundShortHeight(windowBounds));
 
         return badBounds;
     }
diff --git a/window/window/src/test/java/androidx/window/TestWindow.java b/window/window/src/test/java/androidx/window/TestWindow.java
new file mode 100644
index 0000000..8e3591f
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/TestWindow.java
@@ -0,0 +1,293 @@
+/*
+ * 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.window;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.InputQueue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class TestWindow extends Window {
+
+    private View mDecorView;
+
+    public TestWindow(Context context) {
+        this(context, mock(View.class));
+    }
+
+    public TestWindow(Context context, View decorView) {
+        super(context);
+        mDecorView = decorView;
+    }
+
+    @Override
+    public void takeSurface(SurfaceHolder.Callback2 callback) {
+
+    }
+
+    @Override
+    public void takeInputQueue(InputQueue.Callback callback) {
+
+    }
+
+    @Override
+    public boolean isFloating() {
+        return false;
+    }
+
+    @Override
+    public void setContentView(int layoutResID) {
+
+    }
+
+    @Override
+    public void setContentView(View view) {
+
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+
+    }
+
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+
+    }
+
+    @Nullable
+    @Override
+    public View getCurrentFocus() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    public LayoutInflater getLayoutInflater() {
+        return null;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+
+    }
+
+    @Override
+    public void setTitleColor(int textColor) {
+
+    }
+
+    @Override
+    public void openPanel(int featureId, KeyEvent event) {
+
+    }
+
+    @Override
+    public void closePanel(int featureId) {
+
+    }
+
+    @Override
+    public void togglePanel(int featureId, KeyEvent event) {
+
+    }
+
+    @Override
+    public void invalidatePanelMenu(int featureId) {
+
+    }
+
+    @Override
+    public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
+        return false;
+    }
+
+    @Override
+    public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
+        return false;
+    }
+
+    @Override
+    public void closeAllPanels() {
+
+    }
+
+    @Override
+    public boolean performContextMenuIdentifierAction(int id, int flags) {
+        return false;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable drawable) {
+
+    }
+
+    @Override
+    public void setFeatureDrawableResource(int featureId, int resId) {
+
+    }
+
+    @Override
+    public void setFeatureDrawableUri(int featureId, Uri uri) {
+
+    }
+
+    @Override
+    public void setFeatureDrawable(int featureId, Drawable drawable) {
+
+    }
+
+    @Override
+    public void setFeatureDrawableAlpha(int featureId, int alpha) {
+
+    }
+
+    @Override
+    public void setFeatureInt(int featureId, int value) {
+
+    }
+
+    @Override
+    public void takeKeyEvents(boolean get) {
+
+    }
+
+    @Override
+    public boolean superDispatchKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean superDispatchTouchEvent(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean superDispatchTrackballEvent(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public View getDecorView() {
+        return mDecorView;
+    }
+
+    @Override
+    public View peekDecorView() {
+        return null;
+    }
+
+    @Override
+    public Bundle saveHierarchyState() {
+        return null;
+    }
+
+    @Override
+    public void restoreHierarchyState(Bundle savedInstanceState) {
+
+    }
+
+    @Override
+    protected void onActive() {
+
+    }
+
+    @Override
+    public void setChildDrawable(int featureId, Drawable drawable) {
+
+    }
+
+    @Override
+    public void setChildInt(int featureId, int value) {
+
+    }
+
+    @Override
+    public boolean isShortcutKey(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public void setVolumeControlStream(int streamType) {
+
+    }
+
+    @Override
+    public int getVolumeControlStream() {
+        return 0;
+    }
+
+    @Override
+    public int getStatusBarColor() {
+        return 0;
+    }
+
+    @Override
+    public void setStatusBarColor(int color) {
+
+    }
+
+    @Override
+    public int getNavigationBarColor() {
+        return 0;
+    }
+
+    @Override
+    public void setNavigationBarColor(int color) {
+
+    }
+
+    @Override
+    public void setDecorCaptionShade(int decorCaptionShade) {
+
+    }
+
+    @Override
+    public void setResizingCaptionDrawable(Drawable drawable) {
+
+    }
+}
diff --git a/window/window/src/test/java/androidx/window/TestWindowBoundsHelper.java b/window/window/src/test/java/androidx/window/TestWindowBoundsHelper.java
new file mode 100644
index 0000000..15a069e
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/TestWindowBoundsHelper.java
@@ -0,0 +1,100 @@
+/*
+ * 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.window;
+
+import android.app.Activity;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.HashMap;
+
+/**
+ * Subclass of {@link WindowBoundsHelper} used to override the results for testing.
+ *
+ * @see WindowBoundsHelper
+ * @see WindowBoundsHelper#setForTesting(WindowBoundsHelper)
+ */
+class TestWindowBoundsHelper extends WindowBoundsHelper {
+    private Rect mGlobalOverriddenBounds;
+    private final HashMap<Activity, Rect> mOverriddenBounds = new HashMap<>();
+    private final HashMap<Activity, Rect> mOverriddenMaximumBounds = new HashMap<>();
+
+    /**
+     * Overrides the bounds returned from this helper for the given context. Passing null {@code
+     * bounds} has the effect of clearing the bounds override.
+     * <p>
+     * Note: A global override set as a result of {@link #setCurrentBounds(Rect)} takes precedence
+     * over the value set with this method.
+     */
+    void setCurrentBoundsForActivity(@NonNull Activity activity, @Nullable Rect bounds) {
+        mOverriddenBounds.put(activity, bounds);
+    }
+
+    /**
+     * Overrides the max bounds returned from this helper for the given context. Passing {@code
+     * null} {@code bounds} has the effect of clearing the bounds override.
+     */
+    void setMaximumBoundsForActivity(@NonNull Activity activity, @Nullable Rect bounds) {
+        mOverriddenMaximumBounds.put(activity, bounds);
+    }
+
+    /**
+     * Overrides the bounds returned from this helper for all supplied contexts. Passing null
+     * {@code bounds} has the effect of clearing the global override.
+     */
+    void setCurrentBounds(@Nullable Rect bounds) {
+        mGlobalOverriddenBounds = bounds;
+    }
+
+    @Override
+    @NonNull
+    Rect computeCurrentWindowBounds(Activity activity) {
+        if (mGlobalOverriddenBounds != null) {
+            return mGlobalOverriddenBounds;
+        }
+
+        Rect bounds = mOverriddenBounds.get(activity);
+        if (bounds != null) {
+            return bounds;
+        }
+
+        return super.computeCurrentWindowBounds(activity);
+    }
+
+    @NonNull
+    @Override
+    Rect computeMaximumWindowBounds(Activity activity) {
+        Rect bounds = mOverriddenMaximumBounds.get(activity);
+        if (bounds != null) {
+            return bounds;
+        }
+
+        return super.computeMaximumWindowBounds(activity);
+    }
+
+    /**
+     * Clears any overrides set with {@link #setCurrentBounds(Rect)} or
+     * {@link #setCurrentBoundsForActivity(Activity, Rect)}.
+     */
+    void reset() {
+        mGlobalOverriddenBounds = null;
+        mOverriddenBounds.clear();
+        mOverriddenMaximumBounds.clear();
+    }
+}