Merge "Pass repository URLs for ProjectSetupRule" into androidx-main
diff --git a/biometric/biometric-ktx/OWNERS b/biometric/biometric-ktx/OWNERS
index a1780fe..71f000e 100644
--- a/biometric/biometric-ktx/OWNERS
+++ b/biometric/biometric-ktx/OWNERS
@@ -1,3 +1,2 @@
-graciecheng@google.com
 kchyn@google.com
 curtislb@google.com
\ No newline at end of file
diff --git a/browser/OWNERS b/browser/OWNERS
index 2848be3..af78ac1 100644
--- a/browser/OWNERS
+++ b/browser/OWNERS
@@ -1,3 +1,2 @@
-lizeb@google.com
 peconn@google.com
 eirage@google.com
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 4f01dc7..0b63bd4 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -106,7 +106,7 @@
 const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
 const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.2.9"
 const val RX_JAVA3 = "io.reactivex.rxjava3:rxjava:3.0.0"
-val SKIKO_VERSION = System.getenv("SKIKO_VERSION") ?: "0.2.6"
+val SKIKO_VERSION = System.getenv("SKIKO_VERSION") ?: "0.2.13"
 val SKIKO = "org.jetbrains.skiko:skiko-jvm:$SKIKO_VERSION"
 val SKIKO_LINUX_X64 = "org.jetbrains.skiko:skiko-jvm-runtime-linux-x64:$SKIKO_VERSION"
 val SKIKO_MACOS_X64 = "org.jetbrains.skiko:skiko-jvm-runtime-macos-x64:$SKIKO_VERSION"
diff --git a/buildSrc/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt b/buildSrc/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt
index f121ea7..528c6bb 100644
--- a/buildSrc/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/resources/PublicResourcesStubHelper.kt
@@ -19,14 +19,23 @@
 import androidx.build.getSupportRootFolder
 import com.android.build.gradle.LibraryExtension
 import org.gradle.api.Project
+import org.gradle.api.tasks.Copy
 import java.io.File
 
 fun Project.configurePublicResourcesStub(extension: LibraryExtension) {
+    val targetResFolder = File(project.buildDir, "generated/res/public-stub")
+
+    val generatePublicResourcesTask = tasks.register(
+        "generatePublicResourcesStub",
+        Copy::class.java
+    ) { task ->
+        task.from(File(project.getSupportRootFolder(), "buildSrc/res"))
+        task.into(targetResFolder)
+    }
+
     extension.libraryVariants.all { variant ->
         variant.registerGeneratedResFolders(
-            project.files(
-                File(project.getSupportRootFolder(), "/buildSrc/res")
-            )
+            project.files(targetResFolder).builtBy(generatePublicResourcesTask)
         )
     }
 }
diff --git a/car/app/app-activity/api/current.txt b/car/app/app-activity/api/current.txt
index 6638685..a77b152 100644
--- a/car/app/app-activity/api/current.txt
+++ b/car/app/app-activity/api/current.txt
@@ -9,11 +9,8 @@
 
 package androidx.car.app.activity.renderer.surface {
 
-  public final class LegacySurfacePackage implements android.os.Parcelable {
+  public final class LegacySurfacePackage {
     ctor public LegacySurfacePackage(androidx.car.app.activity.renderer.surface.SurfaceControlCallback);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.LegacySurfacePackage!> CREATOR;
   }
 
   public interface SurfaceControlCallback {
@@ -22,21 +19,9 @@
     method public void setSurfaceWrapper(androidx.car.app.activity.renderer.surface.SurfaceWrapper);
   }
 
-  public final class SurfacePackageWrapper implements android.os.Parcelable {
-    ctor public SurfacePackageWrapper(androidx.car.app.activity.renderer.surface.LegacySurfacePackage);
-    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageWrapper(android.view.SurfaceControlViewHost.SurfacePackage);
-    method public int describeContents();
-    method public android.os.Parcelable? getSurfacePackage();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.SurfacePackageWrapper!> CREATOR;
-  }
-
-  public final class SurfaceWrapper implements android.os.Parcelable {
+  public final class SurfaceWrapper {
     ctor public SurfaceWrapper(android.os.IBinder?, int, int, int, int, android.view.Surface);
-    method public int describeContents();
     method public android.os.IBinder? getHostToken();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.SurfaceWrapper!> CREATOR;
   }
 
 }
diff --git a/car/app/app-activity/api/public_plus_experimental_current.txt b/car/app/app-activity/api/public_plus_experimental_current.txt
index 6638685..a77b152 100644
--- a/car/app/app-activity/api/public_plus_experimental_current.txt
+++ b/car/app/app-activity/api/public_plus_experimental_current.txt
@@ -9,11 +9,8 @@
 
 package androidx.car.app.activity.renderer.surface {
 
-  public final class LegacySurfacePackage implements android.os.Parcelable {
+  public final class LegacySurfacePackage {
     ctor public LegacySurfacePackage(androidx.car.app.activity.renderer.surface.SurfaceControlCallback);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.LegacySurfacePackage!> CREATOR;
   }
 
   public interface SurfaceControlCallback {
@@ -22,21 +19,9 @@
     method public void setSurfaceWrapper(androidx.car.app.activity.renderer.surface.SurfaceWrapper);
   }
 
-  public final class SurfacePackageWrapper implements android.os.Parcelable {
-    ctor public SurfacePackageWrapper(androidx.car.app.activity.renderer.surface.LegacySurfacePackage);
-    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageWrapper(android.view.SurfaceControlViewHost.SurfacePackage);
-    method public int describeContents();
-    method public android.os.Parcelable? getSurfacePackage();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.SurfacePackageWrapper!> CREATOR;
-  }
-
-  public final class SurfaceWrapper implements android.os.Parcelable {
+  public final class SurfaceWrapper {
     ctor public SurfaceWrapper(android.os.IBinder?, int, int, int, int, android.view.Surface);
-    method public int describeContents();
     method public android.os.IBinder? getHostToken();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.SurfaceWrapper!> CREATOR;
   }
 
 }
diff --git a/car/app/app-activity/api/restricted_current.txt b/car/app/app-activity/api/restricted_current.txt
index 6638685..a77b152 100644
--- a/car/app/app-activity/api/restricted_current.txt
+++ b/car/app/app-activity/api/restricted_current.txt
@@ -9,11 +9,8 @@
 
 package androidx.car.app.activity.renderer.surface {
 
-  public final class LegacySurfacePackage implements android.os.Parcelable {
+  public final class LegacySurfacePackage {
     ctor public LegacySurfacePackage(androidx.car.app.activity.renderer.surface.SurfaceControlCallback);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.LegacySurfacePackage!> CREATOR;
   }
 
   public interface SurfaceControlCallback {
@@ -22,21 +19,9 @@
     method public void setSurfaceWrapper(androidx.car.app.activity.renderer.surface.SurfaceWrapper);
   }
 
-  public final class SurfacePackageWrapper implements android.os.Parcelable {
-    ctor public SurfacePackageWrapper(androidx.car.app.activity.renderer.surface.LegacySurfacePackage);
-    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageWrapper(android.view.SurfaceControlViewHost.SurfacePackage);
-    method public int describeContents();
-    method public android.os.Parcelable? getSurfacePackage();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.SurfacePackageWrapper!> CREATOR;
-  }
-
-  public final class SurfaceWrapper implements android.os.Parcelable {
+  public final class SurfaceWrapper {
     ctor public SurfaceWrapper(android.os.IBinder?, int, int, int, int, android.view.Surface);
-    method public int describeContents();
     method public android.os.IBinder? getHostToken();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.activity.renderer.surface.SurfaceWrapper!> CREATOR;
   }
 
 }
diff --git a/car/app/app-activity/build.gradle b/car/app/app-activity/build.gradle
index d422b0c..83298c4 100644
--- a/car/app/app-activity/build.gradle
+++ b/car/app/app-activity/build.gradle
@@ -40,6 +40,10 @@
     defaultConfig {
         minSdkVersion 29
     }
+    lintOptions {
+        // We rely on keeping a bunch of private variables in the library for serialization.
+        disable("BanKeepAnnotation")
+    }
     buildFeatures {
         aidl = true
     }
diff --git a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/ICarAppActivity.aidl b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/ICarAppActivity.aidl
index 1a51024..a4bbd28 100644
--- a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/ICarAppActivity.aidl
+++ b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/ICarAppActivity.aidl
@@ -18,7 +18,7 @@
 
 import androidx.car.app.activity.renderer.IRendererCallback;
 import androidx.car.app.activity.renderer.surface.ISurfaceListener;
-import androidx.car.app.activity.renderer.surface.SurfacePackageWrapper;
+import androidx.car.app.serialization.Bundleable;
 
 /**
  * An interface to let renderer service communicate with the car activity.
@@ -27,7 +27,7 @@
  */
 oneway interface ICarAppActivity {
     /** Sets the surface package. */
-    void setSurfacePackage(in SurfacePackageWrapper surfacePackageWrapper) = 1;
+    void setSurfacePackage(in Bundleable surfacePackage) = 1;
 
     /** Registers the listener to get callbacks for surface events. */
     void setSurfaceListener(ISurfaceListener listener) = 2;
diff --git a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceControl.aidl b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceControl.aidl
index 75c3299..4399a6d 100644
--- a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceControl.aidl
+++ b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceControl.aidl
@@ -1,6 +1,6 @@
 package androidx.car.app.activity.renderer.surface;
 
-import androidx.car.app.activity.renderer.surface.SurfaceWrapper;
+import androidx.car.app.serialization.Bundleable;
 
 /**
  * Interface implemented by an off-process renderer to receive events affecting the
@@ -10,7 +10,7 @@
  */
 interface ISurfaceControl {
   /** Notifies that the underlying surface changed. */
-  void setSurfaceWrapper(in SurfaceWrapper surfaceWrapper) = 1;
+  void setSurfaceWrapper(in Bundleable surfaceWrapper) = 1;
 
   /** Notifies that the surface received a new touch event. */
   void onTouchEvent(in MotionEvent event) = 2;
diff --git a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceListener.aidl b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceListener.aidl
index de0046b..f2f7486 100644
--- a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceListener.aidl
+++ b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/ISurfaceListener.aidl
@@ -1,6 +1,6 @@
 package androidx.car.app.activity.renderer.surface;
 
-import androidx.car.app.activity.renderer.surface.SurfaceWrapper;
+import androidx.car.app.serialization.Bundleable;
 
 /**
  * A surface event listener interface.
@@ -14,7 +14,7 @@
    * @param surfaceWrapper a {@link SurfaceWrapper} that contains information on the surface that
    * has become available.
    */
-  void onSurfaceAvailable(in SurfaceWrapper surfaceWrapper) = 1;
+  void onSurfaceAvailable(in Bundleable surfaceWrapper) = 1;
 
   /**
    * Notifies that the surface size has changed.
@@ -22,5 +22,5 @@
    * @param surfaceWrapper a {@link SurfaceWrapper} that contains the updated information on the
    * surface.
    */
-  void onSurfaceChanged(in SurfaceWrapper surfaceWrapper) = 2;
+  void onSurfaceChanged(in Bundleable surfaceWrapper) = 2;
 }
diff --git a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/LegacySurfacePackage.aidl b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/LegacySurfacePackage.aidl
deleted file mode 100644
index 04f4984..0000000
--- a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/LegacySurfacePackage.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package androidx.car.app.activity.renderer.surface;
-
-parcelable LegacySurfacePackage;
\ No newline at end of file
diff --git a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/SurfacePackageWrapper.aidl b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/SurfacePackageWrapper.aidl
deleted file mode 100644
index 29cb736..0000000
--- a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/SurfacePackageWrapper.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package androidx.car.app.activity.renderer.surface;
-
-parcelable SurfacePackageWrapper;
\ No newline at end of file
diff --git a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/SurfaceWrapper.aidl b/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/SurfaceWrapper.aidl
deleted file mode 100644
index 3a7f762..0000000
--- a/car/app/app-activity/src/main/aidl/androidx/car/app/activity/renderer/surface/SurfaceWrapper.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.car.app.activity.renderer.surface;
-
-parcelable SurfaceWrapper;
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
index df053e0..5716729 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
@@ -45,9 +45,9 @@
 import androidx.car.app.activity.renderer.surface.OnBackPressedListener;
 import androidx.car.app.activity.renderer.surface.RotaryEventCallback;
 import androidx.car.app.activity.renderer.surface.SurfaceHolderListener;
-import androidx.car.app.activity.renderer.surface.SurfacePackageWrapper;
 import androidx.car.app.activity.renderer.surface.SurfaceWrapperProvider;
 import androidx.car.app.activity.renderer.surface.TemplateSurfaceView;
+import androidx.car.app.serialization.Bundleable;
 import androidx.car.app.utils.ThreadUtils;
 
 import java.util.List;
@@ -88,7 +88,7 @@
      */
     private final ICarAppActivity.Stub mCarActivity = new ICarAppActivity.Stub() {
         @Override
-        public void setSurfacePackage(@NonNull SurfacePackageWrapper surfacePackage) {
+        public void setSurfacePackage(@NonNull Bundleable surfacePackage) {
             requireNonNull(surfacePackage);
             ThreadUtils.runOnMain(() -> mSurfaceView.setSurfacePackage(surfacePackage));
         }
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/LegacySurfacePackage.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/LegacySurfacePackage.java
index 81a226b..5844820 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/LegacySurfacePackage.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/LegacySurfacePackage.java
@@ -16,14 +16,18 @@
 
 package androidx.car.app.activity.renderer.surface;
 
+import static androidx.car.app.activity.LogTags.TAG;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.SuppressLint;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.util.Log;
 import android.view.MotionEvent;
 
+import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
+import androidx.car.app.serialization.Bundleable;
+import androidx.car.app.serialization.BundlerException;
 
 /**
  * A serializable class containing all the data required to render and interact with a surface from
@@ -32,10 +36,9 @@
  * This class exists for compatibility with Q devices. In Android R and later,
  * {@link android.view.SurfaceControlViewHost.SurfacePackage} will be used instead.
  */
-//TODO(179714355): Investigate using Bundleable instead of Parcelable
-@SuppressLint({"BanParcelableUsage"})
-public final class LegacySurfacePackage implements Parcelable {
-    private final ISurfaceControl mSurfaceControl;
+public final class LegacySurfacePackage {
+    @Keep
+    private ISurfaceControl mISurfaceControl;
 
     /**
      * Creates a {@link LegacySurfacePackage}.
@@ -46,14 +49,19 @@
      */
     @SuppressLint("ExecutorRegistration")
     public LegacySurfacePackage(@NonNull SurfaceControlCallback callback) {
-        mSurfaceControl = new ISurfaceControl.Stub() {
+        mISurfaceControl = new ISurfaceControl.Stub() {
             final SurfaceControlCallback mCallback = callback;
 
             @Override
-            public void setSurfaceWrapper(@NonNull SurfaceWrapper surfaceWrapper) {
+            public void setSurfaceWrapper(@NonNull Bundleable surfaceWrapper) {
                 requireNonNull(surfaceWrapper);
                 if (mCallback != null) {
-                    mCallback.setSurfaceWrapper(surfaceWrapper);
+                    try {
+                        mCallback.setSurfaceWrapper((SurfaceWrapper) surfaceWrapper.get());
+                    } catch (BundlerException e) {
+                        //TODO(b/179930319): Surface error on the CarAppActivity
+                        Log.e(TAG, "Unable to deserialize surface wrapper", e);
+                    }
                 }
             }
 
@@ -74,39 +82,13 @@
         };
     }
 
-    LegacySurfacePackage(@NonNull Parcel parcel) {
-        mSurfaceControl = ISurfaceControl.Stub.asInterface(parcel.readStrongBinder());
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        parcel.writeStrongInterface(mSurfaceControl);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
+    /** Empty constructor needed for serializations. **/
+    private LegacySurfacePackage() {
     }
 
     @NonNull
     ISurfaceControl getSurfaceControl() {
-        return mSurfaceControl;
+        return mISurfaceControl;
     }
-
-    @NonNull
-    public static final Creator<LegacySurfacePackage> CREATOR =
-            new Creator<LegacySurfacePackage>() {
-                @NonNull
-                @Override
-                public LegacySurfacePackage createFromParcel(@NonNull Parcel parcel) {
-                    return new LegacySurfacePackage(parcel);
-                }
-
-                @NonNull
-                @Override
-                public LegacySurfacePackage[] newArray(int size) {
-                    return new LegacySurfacePackage[size];
-                }
-            };
 }
 
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceHolderListener.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceHolderListener.java
index 12a55c1..50e831f 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceHolderListener.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceHolderListener.java
@@ -28,6 +28,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.car.app.serialization.Bundleable;
+import androidx.car.app.serialization.BundlerException;
 
 /**
  * A listener of {@link SurfaceHolder}.
@@ -80,10 +82,13 @@
     private void notifySurfaceCreated() {
         try {
             if (mSurfaceListener != null) {
-                mSurfaceListener.onSurfaceAvailable(mSurfaceWrapperProvider.createSurfaceWrapper());
+                mSurfaceListener.onSurfaceAvailable(
+                        Bundleable.create(mSurfaceWrapperProvider.createSurfaceWrapper()));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Remote connection lost", e);
+        } catch (BundlerException e) {
+            Log.e(TAG, "Unable to serialize surface wrapper", e);
         }
 
     }
@@ -91,10 +96,13 @@
     private void notifySurfaceChanged() {
         try {
             if (mSurfaceListener != null) {
-                mSurfaceListener.onSurfaceChanged(mSurfaceWrapperProvider.createSurfaceWrapper());
+                mSurfaceListener.onSurfaceChanged(
+                        Bundleable.create(mSurfaceWrapperProvider.createSurfaceWrapper()));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Remote connection lost", e);
+        } catch (BundlerException e) {
+            Log.e(TAG, "Unable to serialize surface wrapper", e);
         }
     }
 }
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfacePackageWrapper.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfacePackageWrapper.java
deleted file mode 100644
index 5ab44e8..0000000
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfacePackageWrapper.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.car.app.activity.renderer.surface;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.os.Build.VERSION;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControlViewHost.SurfacePackage;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-/**
- * A parcelable that stores either a {@link LegacySurfacePackage} or a
- * {@link android.view.SurfaceControlViewHost.SurfacePackage}.
- *
- * Please note that {@link android.view.SurfaceControlViewHost.SurfacePackage} requires API level R.
- */
-//TODO(179714355): Investigate using Bundleable instead of Parcelable
-@SuppressLint({"BanParcelableUsage"})
-public final class SurfacePackageWrapper implements Parcelable {
-    enum SurfacePackageType {
-        LEGACY,
-        SURFACE_CONTROL;
-    }
-
-    @Nullable
-    private final Parcelable mSurfacePackage;
-
-    /**
-     * Creates a {@link SurfacePackageWrapper} that stores a
-     * {@link android.view.SurfaceControlViewHost.SurfacePackage}.
-     *
-     * @param legacySurfacePackage the {@link android.view.SurfaceControlViewHost.SurfacePackage}
-     *                             to be stored in this wrapper.
-     */
-    public SurfacePackageWrapper(@NonNull LegacySurfacePackage legacySurfacePackage) {
-        mSurfacePackage = legacySurfacePackage;
-    }
-
-    /**
-     * Creates a {@link SurfacePackageWrapper} that stores a {@link LegacySurfacePackage}.
-     *
-     * @param surfacePackage the {@link LegacySurfacePackage} to be stored in this wrapper.
-     */
-    @RequiresApi(Build.VERSION_CODES.R)
-    public SurfacePackageWrapper(@NonNull SurfacePackage surfacePackage) {
-        mSurfacePackage = surfacePackage;
-    }
-
-    SurfacePackageWrapper(Parcel parcel) {
-        SurfacePackageType type = SurfacePackageType.values()[parcel.readInt()];
-        if (type == SurfacePackageType.LEGACY) {
-            mSurfacePackage = parcel.readParcelable(LegacySurfacePackage.class.getClassLoader());
-        } else if (type == SurfacePackageWrapper.SurfacePackageType.SURFACE_CONTROL
-                && VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            mSurfacePackage = parcel.readParcelable(SurfacePackage.class.getClassLoader());
-        } else {
-            mSurfacePackage = null;
-        }
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        SurfacePackageWrapper.SurfacePackageType type =
-                mSurfacePackage instanceof LegacySurfacePackage
-                        ? SurfacePackageWrapper.SurfacePackageType.LEGACY
-                        : SurfacePackageWrapper.SurfacePackageType.SURFACE_CONTROL;
-        parcel.writeInt(type.ordinal());
-        parcel.writeParcelable(mSurfacePackage, flags);
-    }
-
-    @Nullable
-    public Parcelable getSurfacePackage() {
-        return mSurfacePackage;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @NonNull
-    public static final Creator<SurfacePackageWrapper> CREATOR =
-            new Creator<SurfacePackageWrapper>() {
-                @NonNull
-                @Override
-                public SurfacePackageWrapper createFromParcel(@NonNull Parcel parcel) {
-                    return new SurfacePackageWrapper(parcel);
-                }
-
-                @NonNull
-                @Override
-                public SurfacePackageWrapper[] newArray(int size) {
-                    return new SurfacePackageWrapper[size];
-                }
-            };
-}
-
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceWrapper.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceWrapper.java
index 4ad0e4a..43020a1 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceWrapper.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/SurfaceWrapper.java
@@ -16,29 +16,32 @@
 
 package androidx.car.app.activity.renderer.surface;
 
-import android.annotation.SuppressLint;
 import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.view.Surface;
 import android.view.SurfaceView;
 
+import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 /**
  * A class holding the information needed to render the content on a surface.
  */
-//TODO(179714355): Investigate using Bundleable instead of Parcelable
-@SuppressLint("BanParcelableUsage")
-public final class SurfaceWrapper implements Parcelable {
+
+public final class SurfaceWrapper {
+    @Keep
     @Nullable
-    private final IBinder mHostToken;
-    private final int mWidth;
-    private final int mHeight;
-    private final int mDisplayId;
-    private final int mDensityDpi;
-    private final Surface mSurface;
+    private IBinder mHostToken;
+    @Keep
+    private int mWidth;
+    @Keep
+    private int mHeight;
+    @Keep
+    private int mDisplayId;
+    @Keep
+    private int mDensityDpi;
+    @Keep
+    private Surface mSurface;
 
     /**
      * Creates a {@link SurfaceWrapper}.
@@ -61,28 +64,8 @@
         mSurface = surface;
     }
 
-    SurfaceWrapper(@NonNull Parcel parcel) {
-        mHostToken = parcel.readStrongBinder();
-        mWidth = parcel.readInt();
-        mHeight = parcel.readInt();
-        mDisplayId = parcel.readInt();
-        mDensityDpi = parcel.readInt();
-        mSurface = parcel.readParcelable(Surface.class.getClassLoader());
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        parcel.writeStrongBinder(mHostToken);
-        parcel.writeInt(mWidth);
-        parcel.writeInt(mHeight);
-        parcel.writeInt(mDisplayId);
-        parcel.writeInt(mDensityDpi);
-        parcel.writeParcelable((Parcelable) mSurface, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
+    /** Empty constructor needed for serializations. **/
+    private SurfaceWrapper() {
     }
 
     @Nullable
@@ -110,19 +93,4 @@
     Surface getSurface() {
         return mSurface;
     }
-
-    @NonNull
-    public static final Creator<SurfaceWrapper> CREATOR = new Creator<SurfaceWrapper>() {
-        @NonNull
-        @Override
-        public SurfaceWrapper createFromParcel(@NonNull Parcel parcel) {
-            return new SurfaceWrapper(parcel);
-        }
-
-        @NonNull
-        @Override
-        public SurfaceWrapper[] newArray(int size) {
-            return new SurfaceWrapper[size];
-        }
-    };
 }
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/TemplateSurfaceView.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/TemplateSurfaceView.java
index 8f28ccd..6c5bfc0 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/TemplateSurfaceView.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/renderer/surface/TemplateSurfaceView.java
@@ -35,7 +35,6 @@
 import android.os.Build;
 import android.os.Build.VERSION;
 import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -59,6 +58,8 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.activity.renderer.IProxyInputConnection;
+import androidx.car.app.serialization.Bundleable;
+import androidx.car.app.serialization.BundlerException;
 
 /**
  * A surface view suitable for template rendering.
@@ -289,15 +290,21 @@
 
     /**
      * Updates the surface package. The surface package can be either a
-     * {@link android.view.SurfaceControlViewHost.SurfacePackage} or a
-     * {@link LegacySurfacePackage} wrapped in a {@link SurfacePackageWrapper}.
+     * {@link android.view.SurfaceControlViewHost.SurfacePackage} or a {@link LegacySurfacePackage}.
      */
-    public void setSurfacePackage(@NonNull SurfacePackageWrapper surfacePackageWrapper) {
-        Parcelable surfacePackage = surfacePackageWrapper.getSurfacePackage();
+    public void setSurfacePackage(@NonNull Bundleable bundle) {
+        Object surfacePackage;
+        try {
+            surfacePackage = bundle.get();
+        } catch (BundlerException e) {
+            Log.e(TAG, "Unable to deserialize surface package.");
+            return;
+        }
+
         if (SUPPORTS_SURFACE_CONTROL && surfacePackage instanceof SurfacePackage) {
             Api30Impl.setSurfacePackage(this, (SurfacePackage) surfacePackage);
         } else if (surfacePackage instanceof LegacySurfacePackage) {
-            setLegacySurfacePackage((LegacySurfacePackage) surfacePackage);
+            setSurfacePackage((LegacySurfacePackage) surfacePackage);
         } else {
             Log.e(TAG, "Unrecognized surface package");
         }
@@ -309,14 +316,16 @@
      * This control is used to communicate the UI events and focus with the host.
      */
     @SuppressLint({"ClickableViewAccessibility"})
-    private void setLegacySurfacePackage(LegacySurfacePackage surfacePackage) {
+    private void setSurfacePackage(LegacySurfacePackage surfacePackage) {
         ISurfaceControl surfaceControl = surfacePackage.getSurfaceControl();
         SurfaceWrapper surfaceWrapper = mSurfaceWrapperProvider.createSurfaceWrapper();
         try {
-            surfaceControl.setSurfaceWrapper(surfaceWrapper);
+            surfaceControl.setSurfaceWrapper(Bundleable.create(surfaceWrapper));
         } catch (RemoteException e) {
             Log.e(TAG, "Remote connection lost", e);
             return;
+        } catch (BundlerException e) {
+            Log.e(TAG, "Unable to serialize surface wrapper", e);
         }
         mSurfaceControl = surfaceControl;
         setOnTouchListener((view, event) -> handleTouchEvent(event));
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
index fa20071..254c005 100644
--- a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
@@ -60,7 +60,8 @@
 import androidx.car.app.activity.renderer.IRendererService;
 import androidx.car.app.activity.renderer.surface.LegacySurfacePackage;
 import androidx.car.app.activity.renderer.surface.SurfaceControlCallback;
-import androidx.car.app.activity.renderer.surface.SurfacePackageWrapper;
+import androidx.car.app.serialization.Bundleable;
+import androidx.car.app.serialization.BundlerException;
 import androidx.lifecycle.Lifecycle;
 import androidx.test.core.app.ActivityScenario;
 import androidx.test.core.app.ApplicationProvider;
@@ -272,10 +273,9 @@
                     SurfaceControlCallback callback = mock(SurfaceControlCallback.class);
                     IRendererCallback rendererCallback = mock(IRendererCallback.class);
 
-                    SurfacePackageWrapper wrapper =
-                            new SurfacePackageWrapper(new LegacySurfacePackage(callback));
                     ICarAppActivity carAppActivity = mRenderServiceDelegate.getCarAppActivity();
-                    carAppActivity.setSurfacePackage(wrapper);
+                    carAppActivity.setSurfacePackage(
+                            Bundleable.create(new LegacySurfacePackage(callback)));
                     carAppActivity.registerRendererCallback(rendererCallback);
 
                     // Verify back events on surfaceView are sent to host.
@@ -320,7 +320,7 @@
                             y, metaState);
                     activity.mSurfaceView.dispatchGenericMotionEvent(event);
                     verify(rendererCallback, times(1)).onRotate(anyInt(), eq(false));
-                } catch (RemoteException e) {
+                } catch (RemoteException | BundlerException e) {
                     fail(Log.getStackTraceString(e));
                 }
             });
diff --git a/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/car/NavigationSession.java b/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/car/NavigationSession.java
index ed1e8ec..cc41007 100644
--- a/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/car/NavigationSession.java
+++ b/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/car/NavigationSession.java
@@ -28,7 +28,6 @@
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Log;
 
@@ -65,14 +64,18 @@
     static final String URI_SCHEME = "samples";
     static final String URI_HOST = "navigation";
 
-    @Nullable NavigationScreen mNavigationScreen;
+    @Nullable
+    NavigationScreen mNavigationScreen;
 
-    @Nullable SurfaceRenderer mNavigationCarSurface;
+    @Nullable
+    SurfaceRenderer mNavigationCarSurface;
 
     // A reference to the navigation service used to get location updates and routing.
-    @Nullable NavigationService mService;
+    @Nullable
+    NavigationService mService;
 
-    @NonNull Action mSettingsAction;
+    @NonNull
+    Action mSettingsAction;
 
     final NavigationService.Listener mServiceListener =
             new NavigationService.Listener() {
@@ -109,15 +112,6 @@
                 public void onLocationChanged(Location location) {
                     mNavigationCarSurface.updateLocationString(getLocationString(location));
                 }
-
-                @Override
-                public void onStatusChanged(String provider, int status, Bundle extras) {}
-
-                @Override
-                public void onProviderEnabled(String provider) {}
-
-                @Override
-                public void onProviderDisabled(String provider) {}
             };
 
     // Monitors the state of the connection to the Navigation service.
@@ -200,8 +194,8 @@
                 new Action.Builder()
                         .setIcon(
                                 new CarIcon.Builder(
-                                                IconCompat.createWithResource(
-                                                        getCarContext(), R.drawable.ic_settings))
+                                        IconCompat.createWithResource(
+                                                getCarContext(), R.drawable.ic_settings))
                                         .build())
                         .setOnClickListener(
                                 () -> {
@@ -218,9 +212,9 @@
         String action = intent.getAction();
         if (action != null && CarContext.ACTION_NAVIGATE.equals(action)) {
             CarToast.makeText(
-                            getCarContext(),
-                            "Navigation intent: " + intent.getDataString(),
-                            CarToast.LENGTH_LONG)
+                    getCarContext(),
+                    "Navigation intent: " + intent.getDataString(),
+                    CarToast.LENGTH_LONG)
                     .show();
         }
 
diff --git a/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/nav/NavigationService.java b/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/nav/NavigationService.java
index fccc8be..9d07221 100644
--- a/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/nav/NavigationService.java
+++ b/car/app/app-samples/navigation/src/main/java/androidx/car/app/samples/navigation/nav/NavigationService.java
@@ -523,7 +523,8 @@
 
                         // Set the notification's background color on the car screen.
                         .setColor(
-                                getResources().getColor(R.color.nav_notification_background_color))
+                                getResources().getColor(R.color.nav_notification_background_color,
+                                        null))
                         .setColorized(true)
                         .setSmallIcon(R.drawable.ic_launcher)
                         .setLargeIcon(
diff --git a/car/app/app-samples/showcase/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/src/main/AndroidManifest.xml
index 15e36e4..688b309 100644
--- a/car/app/app-samples/showcase/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/src/main/AndroidManifest.xml
@@ -66,6 +66,8 @@
         android:excludeFromRecents="true"
         android:noHistory="true"
         android:launchMode="singleTask"
+        android:showWhenLocked="true"
+        android:turnScreenOn="true"
         android:exported="false"/>
 
     <receiver
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/StartScreen.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/StartScreen.java
index 212ff6e..6b66d75 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/StartScreen.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/StartScreen.java
@@ -74,10 +74,8 @@
                                         .build())
                         .setTitle("Navigation Demos")
                         .setOnClickListener(
-                                () -> {
-                                    getScreenManager()
-                                            .push(new NavigationDemosScreen(getCarContext()));
-                                })
+                                () -> getScreenManager()
+                                        .push(new NavigationDemosScreen(getCarContext())))
                         .setBrowsable(true)
                         .build());
 
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/TaskRestrictionDemoScreen.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/TaskRestrictionDemoScreen.java
index 6492f6c..de416c6 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/TaskRestrictionDemoScreen.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/TaskRestrictionDemoScreen.java
@@ -144,8 +144,4 @@
                                 .build())
                 .build();
     }
-
-    boolean isToggleChecked() {
-        return mToggleState;
-    }
 }
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/common/SamplePlaces.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/common/SamplePlaces.java
index 01256ff..1503215 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/common/SamplePlaces.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/common/SamplePlaces.java
@@ -55,7 +55,7 @@
         return new SamplePlaces(demoScreen);
     }
 
-    /** Retusn the {@link ItemList} of the sample places. */
+    /** Return the {@link ItemList} of the sample places. */
     @NonNull
     public ItemList getPlaceList() {
         ItemList.Builder listBuilder = new ItemList.Builder();
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/MiscDemoScreen.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/MiscDemoScreen.java
index 573791e..ca9bd9c 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/MiscDemoScreen.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/MiscDemoScreen.java
@@ -193,11 +193,7 @@
         }
 
         NotificationCompat.Builder builder;
-        if (VERSION.SDK_INT >= VERSION_CODES.O) {
-            builder = new NotificationCompat.Builder(getCarContext(), NOTIFICATION_CHANNEL_ID);
-        } else {
-            builder = new NotificationCompat.Builder(getCarContext());
-        }
+        builder = new NotificationCompat.Builder(getCarContext(), NOTIFICATION_CHANNEL_ID);
 
         Notification notification =
                 builder.setSmallIcon(R.drawable.ic_bug_report_24px)
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/OnPhoneActivity.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/OnPhoneActivity.java
index 440263d..493eec5 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/OnPhoneActivity.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/misc/OnPhoneActivity.java
@@ -33,11 +33,7 @@
         super.onCreate(savedInstanceState);
 
         // Show Activity over lock screen and turn on device screen.
-        getWindow()
-                .addFlags(
-                        LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                                | LayoutParams.FLAG_TURN_SCREEN_ON
-                                | LayoutParams.FLAG_KEEP_SCREEN_ON);
+        getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
 
         setContentView(R.layout.phone_activity);
 
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationMapOnlyScreen.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationMapOnlyScreen.java
index df0ec70..1057cee 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationMapOnlyScreen.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationMapOnlyScreen.java
@@ -39,7 +39,7 @@
                         .addAction(
                                 new Action.Builder()
                                         .setTitle("BACK")
-                                        .setOnClickListener(() -> finish())
+                                        .setOnClickListener(this::finish)
                                         .build())
                         .build();
 
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationNotificationService.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationNotificationService.java
index 76eb58c..98c44a2 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationNotificationService.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/NavigationNotificationService.java
@@ -118,7 +118,7 @@
     static Notification getNavigationNotification(
             Context context, int notificationCount) {
         NotificationCompat.Builder builder =
-                getNotificationBuilder(context, NAV_NOTIFICATION_CHANNEL_ID);
+                new NotificationCompat.Builder(context, NAV_NOTIFICATION_CHANNEL_ID);
         DirectionInfo directionInfo = getDirectionInfo(notificationCount);
         return builder
                 // This title, text, and icon will be shown in both phone and car screen. These
@@ -167,13 +167,6 @@
                 .build();
     }
 
-    private static NotificationCompat.Builder getNotificationBuilder(
-            Context context, String channelId) {
-        return VERSION.SDK_INT >= VERSION_CODES.O
-                ? new NotificationCompat.Builder(context, channelId)
-                : new NotificationCompat.Builder(context);
-    }
-
     /**
      * A {@link Handler.Callback} used to process the message queue for the notification service.
      */
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/PlaceListNavigationTemplateDemoScreen.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/PlaceListNavigationTemplateDemoScreen.java
index 9a1c142..5d090be 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/PlaceListNavigationTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/PlaceListNavigationTemplateDemoScreen.java
@@ -27,7 +27,7 @@
 
 /** Creates a screen using the {@link PlaceListNavigationTemplate} */
 public final class PlaceListNavigationTemplateDemoScreen extends Screen {
-    private SamplePlaces mPlaces;
+    private final SamplePlaces mPlaces;
 
     public PlaceListNavigationTemplateDemoScreen(@NonNull CarContext carContext) {
         super(carContext);
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/routing/RoutingDemoModels.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/routing/RoutingDemoModels.java
index 072b1c7..0941b49 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/routing/RoutingDemoModels.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/navigation/routing/RoutingDemoModels.java
@@ -131,13 +131,11 @@
                 .addAction(
                         new Action.Builder()
                                 .setOnClickListener(
-                                        () -> {
-                                            CarToast.makeText(
-                                                    carContext,
-                                                    "Bug reported!",
-                                                    CarToast.LENGTH_SHORT)
-                                                    .show();
-                                        })
+                                        () -> CarToast.makeText(
+                                                carContext,
+                                                "Bug reported!",
+                                                CarToast.LENGTH_SHORT)
+                                                .show())
                                 .setIcon(
                                         new CarIcon.Builder(
                                                 IconCompat.createWithResource(
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/GridTemplateDemoScreen.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/GridTemplateDemoScreen.java
index 08111b7..239efec 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/GridTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/GridTemplateDemoScreen.java
@@ -84,17 +84,17 @@
     @NonNull
     @Override
     public Template onGetTemplate() {
-        ItemList.Builder gridItemlistBuilder = new ItemList.Builder();
+        ItemList.Builder gridItemListBuilder = new ItemList.Builder();
 
         // Grid item with an icon and a title.
-        gridItemlistBuilder.addItem(
+        gridItemListBuilder.addItem(
                 new GridItem.Builder()
                         .setImage(new CarIcon.Builder(mIcon).build())
                         .setTitle("Non-actionable")
                         .build());
 
         // Grid item with a large icon, a title, onClickListener and no text.
-        gridItemlistBuilder.addItem(
+        gridItemListBuilder.addItem(
                 new GridItem.Builder()
                         .setImage(new CarIcon.Builder(mIcon).build(), GridItem.IMAGE_TYPE_LARGE)
                         .setTitle("Second Item")
@@ -108,7 +108,7 @@
                         .build());
 
         // Grid item with an icon marked as icon, a title, a text and a toggle in unchecked state.
-        gridItemlistBuilder.addItem(
+        gridItemListBuilder.addItem(
                 new GridItem.Builder()
                         .setImage(new CarIcon.Builder(mIcon).build(), GridItem.IMAGE_TYPE_ICON)
                         .setTitle("Third Item")
@@ -128,27 +128,25 @@
         // Grid item with an image, a title, a long text and a toggle that takes some time to
         // update.
         if (mIsFourthItemLoading) {
-            gridItemlistBuilder.addItem(
+            gridItemListBuilder.addItem(
                     new GridItem.Builder()
                             .setTitle("Fourth")
                             .setText(mFourthItemToggleState ? "On" : "Off")
                             .setLoading(true)
                             .build());
         } else {
-            gridItemlistBuilder.addItem(
+            gridItemListBuilder.addItem(
                     new GridItem.Builder()
                             .setImage(new CarIcon.Builder(mImage).build())
                             .setTitle("Fourth")
                             .setText(mFourthItemToggleState ? "On" : "Off")
                             .setOnClickListener(
-                                    () -> {
-                                        triggerFourthItemLoading();
-                                    })
+                                    this::triggerFourthItemLoading)
                             .build());
         }
 
         // Grid item with a large image, a long title, no text and a toggle in unchecked state.
-        gridItemlistBuilder.addItem(
+        gridItemListBuilder.addItem(
                 new GridItem.Builder()
                         .setImage(new CarIcon.Builder(mImage).build(), GridItem.IMAGE_TYPE_LARGE)
                         .setTitle("Fifth Item has a long title set")
@@ -165,7 +163,7 @@
                         .build());
 
         // Grid item with an image marked as an icon, a long title, a long text and onClickListener.
-        gridItemlistBuilder.addItem(
+        gridItemListBuilder.addItem(
                 new GridItem.Builder()
                         .setImage(new CarIcon.Builder(mImage).build(), GridItem.IMAGE_TYPE_ICON)
                         .setTitle("Sixth Item has a long title set")
@@ -181,7 +179,7 @@
 
         return new GridTemplate.Builder()
                 .setHeaderAction(Action.APP_ICON)
-                .setSingleList(gridItemlistBuilder.build())
+                .setSingleList(gridItemListBuilder.build())
                 .setTitle("Grid Template Demo")
                 .setActionStrip(
                         new ActionStrip.Builder()
diff --git a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/PaneTemplateDemoScreen.java b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/PaneTemplateDemoScreen.java
index ba6ff53..f525791 100644
--- a/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/PaneTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/src/main/java/androidx/car/app/samples/showcase/templates/PaneTemplateDemoScreen.java
@@ -87,26 +87,22 @@
                                 .setTitle("Search")
                                 .setBackgroundColor(CarColor.BLUE)
                                 .setOnClickListener(
-                                        () -> {
-                                            CarToast.makeText(
-                                                    getCarContext(),
-                                                    "Search button pressed",
-                                                    LENGTH_SHORT)
-                                                    .show();
-                                        })
+                                        () -> CarToast.makeText(
+                                                getCarContext(),
+                                                "Search button pressed",
+                                                LENGTH_SHORT)
+                                                .show())
                                 .build())
                 .addAction(
                         new Action.Builder()
                                 .setTitle("Options")
                                 .setBackgroundColor(CarColor.YELLOW)
                                 .setOnClickListener(
-                                        () -> {
-                                            CarToast.makeText(
-                                                    getCarContext(),
-                                                    "Options button pressed",
-                                                    LENGTH_SHORT)
-                                                    .show();
-                                        })
+                                        () -> CarToast.makeText(
+                                                getCarContext(),
+                                                "Options button pressed",
+                                                LENGTH_SHORT)
+                                                .show())
                                 .build());
 
         return new PaneTemplate.Builder(paneBuilder.build())
@@ -117,14 +113,12 @@
                                         new Action.Builder()
                                                 .setTitle("Settings")
                                                 .setOnClickListener(
-                                                        () -> {
-                                                            CarToast.makeText(
-                                                                    getCarContext(),
-                                                                    "Settings button"
-                                                                            + " pressed",
-                                                                    LENGTH_SHORT)
-                                                                    .show();
-                                                        })
+                                                        () -> CarToast.makeText(
+                                                                getCarContext(),
+                                                                "Settings button"
+                                                                        + " pressed",
+                                                                LENGTH_SHORT)
+                                                                .show())
                                                 .build())
                                 .build())
                 .setTitle("Pane Template Demo")
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
index d107f34..d6f5b95 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
@@ -17,7 +17,6 @@
 package androidx.compose.foundation.text.selection
 
 import androidx.compose.foundation.text.isInTouchMode
-import androidx.compose.foundation.interaction.collectIsFocusedAsState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
@@ -97,8 +96,7 @@
         // cross-composable selection.
         SimpleLayout(modifier = modifier.then(manager.modifier)) {
             children()
-            val isFocused = manager.interactionSource.collectIsFocusedAsState()
-            if (isInTouchMode && isFocused.value) {
+            if (isInTouchMode && manager.hasFocus) {
                 manager.selection?.let {
                     for (isStartHandle in listOf(true, false)) {
                         SelectionHandle(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index d630533..6448b20 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -18,7 +18,6 @@
 
 package androidx.compose.foundation.text.selection
 
-import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.focusable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
@@ -95,9 +94,9 @@
     var focusRequester: FocusRequester = FocusRequester()
 
     /**
-     * MutableInteractionSource for the selection container, containing focus interactions.
+     * Return true if the corresponding SelectionContainer is focused.
      */
-    val interactionSource: MutableInteractionSource = MutableInteractionSource()
+    var hasFocus: Boolean by mutableStateOf(false)
 
     /**
      * Modifier for selection container.
@@ -106,11 +105,12 @@
         .onGloballyPositioned { containerLayoutCoordinates = it }
         .focusRequester(focusRequester)
         .onFocusChanged { focusState ->
-            if (!focusState.isFocused) {
+            if (!focusState.isFocused && hasFocus) {
                 onRelease()
             }
+            hasFocus = focusState.isFocused
         }
-        .focusable(interactionSource = interactionSource)
+        .focusable()
         .onKeyEvent {
             if (isCopyKeyEvent(it)) {
                 copy()
@@ -126,8 +126,10 @@
     var containerLayoutCoordinates: LayoutCoordinates? = null
         set(value) {
             field = value
-            updateHandleOffsets()
-            updateSelectionToolbarPosition()
+            if (hasFocus) {
+                updateHandleOffsets()
+                updateSelectionToolbarPosition()
+            }
         }
 
     /**
@@ -179,8 +181,8 @@
                 isStartHandle = true,
                 longPress = touchMode
             )
-            hideSelectionToolbar()
             focusRequester.requestFocus()
+            hideSelectionToolbar()
         }
 
         selectionRegistrar.onSelectionUpdateCallback =
@@ -334,25 +336,27 @@
      * the copy method as a callback when "copy" is clicked.
      */
     internal fun showSelectionToolbar() {
-        selection?.let {
-            textToolbar?.showMenu(
-                getContentRect(),
-                onCopyRequested = {
-                    copy()
-                    onRelease()
-                }
-            )
+        if (hasFocus) {
+            selection?.let {
+                textToolbar?.showMenu(
+                    getContentRect(),
+                    onCopyRequested = {
+                        copy()
+                        onRelease()
+                    }
+                )
+            }
         }
     }
 
     internal fun hideSelectionToolbar() {
-        if (textToolbar?.status == TextToolbarStatus.Shown) {
+        if (hasFocus && textToolbar?.status == TextToolbarStatus.Shown) {
             textToolbar?.hide()
         }
     }
 
     private fun updateSelectionToolbarPosition() {
-        if (textToolbar?.status == TextToolbarStatus.Shown) {
+        if (hasFocus && textToolbar?.status == TextToolbarStatus.Shown) {
             showSelectionToolbar()
         }
     }
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
index d96c2df..61026bd 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
@@ -31,6 +31,7 @@
 import com.nhaarman.mockitokotlin2.eq
 import com.nhaarman.mockitokotlin2.isNull
 import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
 import com.nhaarman.mockitokotlin2.spy
 import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
@@ -368,6 +369,7 @@
             ),
             handlesCrossed = true
         )
+        selectionManager.hasFocus = true
 
         selectionManager.showSelectionToolbar()
 
@@ -381,6 +383,39 @@
     }
 
     @Test
+    fun showSelectionToolbar_withoutFocus_notTrigger_textToolbar_showMenu() {
+        val text = "Text Demo"
+        val annotatedString = AnnotatedString(text = text)
+        val startOffset = text.indexOf('m')
+        val endOffset = text.indexOf('x')
+        selectable.textToReturn = annotatedString
+        selectionManager.selection = Selection(
+            start = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = startOffset,
+                selectable = selectable
+            ),
+            end = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = endOffset,
+                selectable = selectable
+            ),
+            handlesCrossed = true
+        )
+        selectionManager.hasFocus = false
+
+        selectionManager.showSelectionToolbar()
+
+        verify(textToolbar, never()).showMenu(
+            eq(Rect.Zero),
+            any(),
+            isNull(),
+            isNull(),
+            isNull()
+        )
+    }
+
+    @Test
     fun cancel_selection_calls_getSelection_selection_becomes_null() {
         val fakeSelection =
             Selection(
diff --git a/compose/integration-tests/docs-snippets/build.gradle b/compose/integration-tests/docs-snippets/build.gradle
index 289c415..e53dfad 100644
--- a/compose/integration-tests/docs-snippets/build.gradle
+++ b/compose/integration-tests/docs-snippets/build.gradle
@@ -41,6 +41,7 @@
     implementation(project(":navigation:navigation-compose"))
     implementation(project(":activity:activity-compose"))
     implementation(project(":lifecycle:lifecycle-viewmodel-compose"))
+    implementation(project(":paging:paging-compose"))
 
     implementation(KOTLIN_STDLIB)
     implementation(KOTLIN_REFLECT)
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/animation/Animation.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/animation/Animation.kt
new file mode 100644
index 0000000..4b2b047
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/animation/Animation.kt
@@ -0,0 +1,571 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Ignore lint warnings in documentation snippets
+@file:Suppress("CanBeVal", "UNUSED_VARIABLE", "RemoveExplicitTypeArguments", "unused")
+@file:SuppressLint("ModifierInspectorInfo", "NewApi")
+
+package androidx.compose.integration.docs.animation
+
+import android.annotation.SuppressLint
+import androidx.compose.animation.Animatable
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.TargetBasedAnimation
+import androidx.compose.animation.core.Transition
+import androidx.compose.animation.core.TwoWayConverter
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.animateRect
+import androidx.compose.animation.core.animateValueAsState
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.keyframes
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.repeatable
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.animation.splineBasedDecay
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.horizontalDrag
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.integration.docs.animation.UpdateTransitionEnumState.BoxState
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+import kotlin.math.absoluteValue
+import kotlin.math.roundToInt
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/animation
+ *
+ * No action required if it's modified.
+ */
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AnimatedVisibilitySimple() {
+    var editable by remember { mutableStateOf(true) }
+    AnimatedVisibility(visible = editable) {
+        Text(text = "Edit")
+    }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AnimatedVisibilityWithEnterAndExit() {
+    var visible by remember { mutableStateOf(true) }
+    AnimatedVisibility(
+        visible = visible,
+        enter = slideInVertically(
+            // Slide in from 40 px from the top.
+            initialOffsetY = { -40 }
+        ) + expandVertically(
+            // Expand from the top.
+            expandFrom = Alignment.Top
+        ) + fadeIn(
+            // Fade in with the initial alpha of 0.3f.
+            initialAlpha = 0.3f
+        ),
+        exit = slideOutVertically() + shrinkVertically() + fadeOut()
+    ) {
+        Text("Hello", Modifier.fillMaxWidth().height(200.dp))
+    }
+}
+
+@Composable
+fun AnimateContentSizeSimple() {
+    var message by remember { mutableStateOf("Hello") }
+    Box(
+        modifier = Modifier.background(Color.Blue).animateContentSize()
+    ) {
+        Text(text = message)
+    }
+}
+
+@Composable
+fun CrossfadeSimple() {
+    var currentPage by remember { mutableStateOf("A") }
+    Crossfade(targetState = currentPage) { screen ->
+        when (screen) {
+            "A" -> Text("Page A")
+            "B" -> Text("Page B")
+        }
+    }
+}
+
+@Composable
+fun AnimateAsStateSimple(enabled: Boolean) {
+    val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f)
+    Box(
+        Modifier.fillMaxSize()
+            .graphicsLayer(alpha = alpha)
+            .background(Color.Red)
+    )
+}
+
+@Composable
+fun AnimatableSimple(ok: Boolean) {
+    // Start out gray and animate to green/red based on `ok`
+    val color = remember { Animatable(Color.Gray) }
+    LaunchedEffect(ok) {
+        color.animateTo(if (ok) Color.Green else Color.Red)
+    }
+    Box(Modifier.fillMaxSize().background(color.value))
+}
+
+object UpdateTransitionEnumState {
+    enum class BoxState {
+        Collapsed,
+        Expanded
+    }
+}
+
+@Composable
+fun UpdateTransitionInstance() {
+    var currentState by remember { mutableStateOf(BoxState.Collapsed) }
+    val transition = updateTransition(currentState)
+}
+
+@Composable
+fun UpdateTransitionAnimationValues(transition: Transition<BoxState>) {
+    val rect by transition.animateRect { state ->
+        when (state) {
+            BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
+            BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
+        }
+    }
+    val borderWidth by transition.animateDp { state ->
+        when (state) {
+            BoxState.Collapsed -> 1.dp
+            BoxState.Expanded -> 0.dp
+        }
+    }
+}
+
+@Composable
+fun UpdateTransitionTransitionSpec(transition: Transition<BoxState>) {
+    val color by transition.animateColor(
+        transitionSpec = {
+            when {
+                BoxState.Expanded isTransitioningTo BoxState.Collapsed ->
+                    spring(stiffness = 50f)
+                else ->
+                    tween(durationMillis = 500)
+            }
+        }
+    ) { state ->
+        when (state) {
+            BoxState.Collapsed -> MaterialTheme.colors.primary
+            BoxState.Expanded -> MaterialTheme.colors.background
+        }
+    }
+}
+
+@Composable
+fun UpdateTransitionMutableTransitionState() {
+    // Start in collapsed state and immediately animate to expanded
+    var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
+    currentState.targetState = BoxState.Expanded
+    val transition = updateTransition(currentState)
+    // ……
+}
+
+object UpdateTransitionEncapsulating {
+    enum class BoxState { Collapsed, Expanded }
+
+    @Composable
+    fun AnimatingBox(boxState: BoxState) {
+        val transitionData = updateTransitionData(boxState)
+        // UI tree
+        Box(
+            modifier = Modifier
+                .background(transitionData.color)
+                .size(transitionData.size)
+        )
+    }
+
+    // Holds the animation values.
+    private class TransitionData(
+        color: State<Color>,
+        size: State<Dp>
+    ) {
+        val color by color
+        val size by size
+    }
+
+    // Create a Transition and return its animation values.
+    @Composable
+    private fun updateTransitionData(boxState: BoxState): TransitionData {
+        val transition = updateTransition(boxState)
+        val color = transition.animateColor { state ->
+            when (state) {
+                BoxState.Collapsed -> Color.Gray
+                BoxState.Expanded -> Color.Red
+            }
+        }
+        val size = transition.animateDp { state ->
+            when (state) {
+                BoxState.Collapsed -> 64.dp
+                BoxState.Expanded -> 128.dp
+            }
+        }
+        return remember(transition) { TransitionData(color, size) }
+    }
+}
+
+@Composable
+fun RememberInfiniteTransitionSimple() {
+    val infiniteTransition = rememberInfiniteTransition()
+    val color by infiniteTransition.animateColor(
+        initialValue = Color.Red,
+        targetValue = Color.Green,
+        animationSpec = infiniteRepeatable(
+            animation = tween(1000, easing = LinearEasing),
+            repeatMode = RepeatMode.Reverse
+        )
+    )
+
+    Box(Modifier.fillMaxSize().background(color))
+}
+
+@Composable
+fun TargetBasedAnimationSimple(someCustomCondition: () -> Boolean, scope: CoroutineScope) {
+    val anim = remember {
+        TargetBasedAnimation(
+            animationSpec = tween(200),
+            typeConverter = Float.VectorConverter,
+            initialValue = 200f,
+            targetValue = 1000f
+        )
+    }
+    var playTime by remember { mutableStateOf(0L) }
+
+    scope.launch {
+        val startTime = withFrameNanos { it }
+
+        do {
+            playTime = withFrameNanos { it } - startTime
+            val animationValue = anim.getValueFromNanos(playTime)
+        } while (someCustomCondition())
+    }
+}
+
+@Composable
+fun AnimationSpecTween(enabled: Boolean) {
+    val alpha: Float by animateFloatAsState(
+        targetValue = if (enabled) 1f else 0.5f,
+        // Configure the animation duration and easing.
+        animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
+    )
+}
+
+@Composable
+fun AnimationSpecSpring() {
+    val value by animateFloatAsState(
+        targetValue = 1f,
+        animationSpec = spring(
+            dampingRatio = Spring.DampingRatioHighBouncy,
+            stiffness = Spring.StiffnessMedium
+        )
+    )
+}
+
+@Composable
+fun AnimationSpecTween() {
+    val value by animateFloatAsState(
+        targetValue = 1f,
+        animationSpec = tween(
+            durationMillis = 300,
+            delayMillis = 50,
+            easing = LinearOutSlowInEasing
+        )
+    )
+}
+
+@Composable
+fun AnimationSpecKeyframe() {
+    val value by animateFloatAsState(
+        targetValue = 1f,
+        animationSpec = keyframes {
+            durationMillis = 375
+            0.0f at 0 with LinearOutSlowInEasing // for 0-15 ms
+            0.2f at 15 with FastOutLinearInEasing // for 15-75 ms
+            0.4f at 75 // ms
+            0.4f at 225 // ms
+        }
+    )
+}
+
+@Composable
+fun AnimationSpecRepeatable() {
+    val value by animateFloatAsState(
+        targetValue = 1f,
+        animationSpec = repeatable(
+            iterations = 3,
+            animation = tween(durationMillis = 300),
+            repeatMode = RepeatMode.Reverse
+        )
+    )
+}
+
+@Composable
+fun AnimationSpecInfiniteRepeatable() {
+    val value by animateFloatAsState(
+        targetValue = 1f,
+        animationSpec = infiniteRepeatable(
+            animation = tween(durationMillis = 300),
+            repeatMode = RepeatMode.Reverse
+        )
+    )
+}
+
+@Composable
+fun AnimationSpecSnap() {
+    val value by animateFloatAsState(
+        targetValue = 1f,
+        animationSpec = snap(delayMillis = 50)
+    )
+}
+
+object Easing {
+    val CustomEasing = Easing { fraction -> fraction * fraction }
+
+    @Composable
+    fun EasingUsage() {
+        val value by animateFloatAsState(
+            targetValue = 1f,
+            animationSpec = tween(
+                durationMillis = 300,
+                easing = CustomEasing
+            )
+        )
+        // ……
+    }
+}
+
+object AnimationVectorTwoWayConverter {
+    val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
+        TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })
+}
+
+object AnimationVectorCustomType {
+    data class MySize(val width: Dp, val height: Dp)
+
+    @Composable
+    fun MyAnimation(targetSize: MySize) {
+        val animSize: MySize by animateValueAsState<MySize, AnimationVector2D>(
+            targetSize,
+            TwoWayConverter(
+                convertToVector = { size: MySize ->
+                    // Extract a float value from each of the `Dp` fields.
+                    AnimationVector2D(size.width.value, size.height.value)
+                },
+                convertFromVector = { vector: AnimationVector2D ->
+                    MySize(vector.v1.dp, vector.v2.dp)
+                }
+            )
+        )
+    }
+}
+
+object GestureAndAnimationSimple {
+    @Composable
+    fun Gesture() {
+        val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
+        Box(
+            modifier = Modifier
+                .fillMaxSize()
+                .pointerInput(Unit) {
+                    coroutineScope {
+                        while (true) {
+                            // Detect a tap event and obtain its position.
+                            val position = awaitPointerEventScope {
+                                awaitFirstDown().position
+                            }
+                            launch {
+                                // Animate to the tap position.
+                                offset.animateTo(position)
+                            }
+                        }
+                    }
+                }
+        ) {
+            Circle(modifier = Modifier.offset { offset.value.toIntOffset() })
+        }
+    }
+
+    private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt())
+}
+
+object GestureAndAnimationSwipeToDismiss {
+    fun Modifier.swipeToDismiss(
+        onDismissed: () -> Unit
+    ): Modifier = composed {
+        val offsetX = remember { Animatable(0f) }
+        pointerInput(Unit) {
+            val decay = splineBasedDecay<Float>(this)
+            coroutineScope {
+                while (true) {
+                    // Detect a touch down event.
+                    val pointerId = awaitPointerEventScope { awaitFirstDown().id }
+                    val velocityTracker = VelocityTracker()
+                    // Intercept an ongoing animation (if there's one).
+                    offsetX.stop()
+                    awaitPointerEventScope {
+                        horizontalDrag(pointerId) { change ->
+                            // Update the animation value with touch events.
+                            launch {
+                                offsetX.snapTo(
+                                    offsetX.value + change.positionChange().x
+                                )
+                            }
+                            velocityTracker.addPosition(
+                                change.uptimeMillis,
+                                change.position
+                            )
+                        }
+                    }
+                    val velocity = velocityTracker.calculateVelocity().x
+                    val targetOffsetX = decay.calculateTargetValue(
+                        offsetX.value,
+                        velocity
+                    )
+                    // The animation stops when it reaches the bounds.
+                    offsetX.updateBounds(
+                        lowerBound = -size.width.toFloat(),
+                        upperBound = size.width.toFloat()
+                    )
+                    launch {
+                        if (targetOffsetX.absoluteValue <= size.width) {
+                            // Not enough velocity; Slide back.
+                            offsetX.animateTo(
+                                targetValue = 0f,
+                                initialVelocity = velocity
+                            )
+                        } else {
+                            // The element was swiped away.
+                            offsetX.animateDecay(velocity, decay)
+                            onDismissed()
+                        }
+                    }
+                }
+            }
+        }
+            .offset { IntOffset(offsetX.value.roundToInt(), 0) }
+    }
+}
+
+object Testing {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun testAnimationWithClock() {
+        // Pause animations
+        rule.mainClock.autoAdvance = false
+        var enabled by mutableStateOf(false)
+        rule.setContent {
+            val color by animateColorAsState(
+                targetValue = if (enabled) Color.Red else Color.Green,
+                animationSpec = tween(durationMillis = 250)
+            )
+            Box(Modifier.size(64.dp).background(color))
+        }
+
+        // Initiate the animation.
+        enabled = true
+
+        // Let the animation proceed.
+        rule.mainClock.advanceTimeBy(50L)
+
+        // Compare the result with the image showing the expected result.
+        rule.onRoot().captureToImage().assertAgainstGolden()
+    }
+}
+
+private fun ImageBitmap.assertAgainstGolden() {
+}
+
+@Composable
+private fun Circle(modifier: Modifier = Modifier) {
+    Box(
+        modifier = modifier
+            .size(64.dp)
+            .background(Color.Gray, CircleShape)
+    )
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/gestures/Gestures.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/gestures/Gestures.kt
index 6ef6d29..2b55af8 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/gestures/Gestures.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/gestures/Gestures.kt
@@ -58,7 +58,6 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.TileMode
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.consumeAllChanges
 import androidx.compose.ui.input.pointer.pointerInput
@@ -177,12 +176,12 @@
     fun ScrollableSample() {
         // actual composable state
         var offset by remember { mutableStateOf(0f) }
-        // state for Scrollable, describes how to consume scrolling delta and update offset
         Box(
             Modifier
                 .size(150.dp)
                 .scrollable(
                     orientation = Orientation.Vertical,
+                    // Scrollable state: describes how to consume scrolling delta and update offset
                     state = rememberScrollableState { delta ->
                         offset += delta
                         delta
@@ -216,9 +215,7 @@
 private object GesturesSnippet6 {
     @Composable
     fun NestedSample() {
-        val gradient = Brush.verticalGradient(
-            listOf(Color.Gray, Color.White), 0.0f, 1000.0f, TileMode.Repeated
-        )
+        val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
         Box(
             modifier = Modifier
                 .background(Color.LightGray)
@@ -280,19 +277,19 @@
 }
 @Composable private fun GesturesSnippet8() {
     Box(modifier = Modifier.fillMaxSize()) {
-        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 { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
+                .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                 .background(Color.Blue)
                 .size(50.dp)
                 .pointerInput(Unit) {
                     detectDragGestures { change, dragAmount ->
                         change.consumeAllChanges()
-                        offsetX.value = (offsetX.value + dragAmount.x)
-                        offsetY.value = (offsetY.value + dragAmount.y)
+                        offsetX += dragAmount.x
+                        offsetY += dragAmount.y
                     }
                 }
         )
@@ -314,7 +311,7 @@
 
         val swipeableState = rememberSwipeableState(0)
         val sizePx = with(LocalDensity.current) { squareSize.toPx() }
-        val anchors = mapOf(0f to 0, sizePx to 1)
+        val anchors = mapOf(0f to 0, sizePx to 1) // Maps anchor points (in px) to states
 
         Box(
             modifier = Modifier
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lists/Lists.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lists/Lists.kt
new file mode 100644
index 0000000..6ddc570
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lists/Lists.kt
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("unused", "UNUSED_PARAMETER")
+
+package androidx.compose.integration.docs.lists
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.lazy.GridCells
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.LazyVerticalGrid
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.unit.dp
+import androidx.paging.Pager
+import androidx.paging.compose.collectAsLazyPagingItems
+import androidx.paging.compose.items
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+internal object ListsSnippetsColumn {
+    @Composable
+    fun MessageList(messages: List<Message>) {
+        Column {
+            messages.forEach { message ->
+                MessageRow(message)
+            }
+        }
+    }
+}
+
+internal object ListsSnippetsLazyListScope {
+    @Composable
+    fun Snippet() {
+        LazyColumn {
+            // Add a single item
+            item {
+                Text(text = "First item")
+            }
+
+            // Add 5 items
+            items(5) { index ->
+                Text(text = "Item: $index")
+            }
+
+            // Add another single item
+            item {
+                Text(text = "Last item")
+            }
+        }
+    }
+
+    @Composable
+    fun MessageList(messages: List<Message>) {
+        LazyColumn {
+            items(messages) { message ->
+                MessageRow(message)
+            }
+        }
+    }
+}
+
+internal object ListsSnippetsContentPadding {
+    @Composable
+    fun Snippet() {
+        LazyColumn(
+            contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
+        ) {
+            // ...
+        }
+    }
+}
+
+internal object ListsSnippetsContentSpacing {
+    @Composable
+    fun Column() {
+        LazyColumn(
+            verticalArrangement = Arrangement.spacedBy(4.dp),
+        ) {
+            // ...
+        }
+    }
+
+    @Composable
+    fun Row() {
+        LazyRow(
+            horizontalArrangement = Arrangement.spacedBy(4.dp),
+        ) {
+            // ...
+        }
+    }
+}
+
+internal object ListsSnippetsStickyHeaders {
+    @OptIn(ExperimentalFoundationApi::class)
+    @Composable
+    fun ListWithHeader(items: List<Item>) {
+        LazyColumn {
+            stickyHeader {
+                Header()
+            }
+
+            items(items) { item ->
+                ItemRow(item)
+            }
+        }
+    }
+
+    // TODO: This ideally would be done in the ViewModel
+    val grouped = contacts.groupBy { it.firstName[0] }
+
+    @OptIn(ExperimentalFoundationApi::class)
+    @Composable
+    fun ContactsList(grouped: Map<Char, List<Contact>>) {
+        LazyColumn {
+            grouped.forEach { (initial, contactsForInitial) ->
+                stickyHeader {
+                    CharacterHeader(initial)
+                }
+
+                items(contactsForInitial) { contact ->
+                    ContactListItem(contact)
+                }
+            }
+        }
+    }
+}
+
+internal object ListsSnippetsGrids {
+    @OptIn(ExperimentalFoundationApi::class)
+    @Composable
+    fun PhotoGrid(photos: List<Photo>) {
+        LazyVerticalGrid(
+            cells = GridCells.Adaptive(minSize = 128.dp)
+        ) {
+            items(photos) { photo ->
+                PhotoItem(photo)
+            }
+        }
+    }
+}
+
+internal object ListsSnippetsReactingScrollPosition1 {
+    @Composable
+    fun MessageList(messages: List<Message>) {
+        // Remember our own LazyListState
+        val listState = rememberLazyListState()
+
+        // Provide it to LazyColumn
+        LazyColumn(state = listState) {
+            // ...
+        }
+    }
+}
+
+internal object ListsSnippetsReactingScrollPosition2 {
+    @OptIn(ExperimentalAnimationApi::class)
+    @Composable
+    fun MessageList(messages: List<Message>) {
+        Box {
+            val listState = rememberLazyListState()
+
+            LazyColumn(state = listState) {
+                // ...
+            }
+
+            // Show the button if the first visible item is past
+            // the first item. We use a remembered derived state to
+            // minimize unnecessary compositions
+            val showButton by remember {
+                derivedStateOf {
+                    listState.firstVisibleItemIndex > 0
+                }
+            }
+
+            AnimatedVisibility(visible = showButton) {
+                ScrollToTopButton()
+            }
+        }
+    }
+}
+
+@Suppress("SimplifyBooleanWithConstants")
+internal object ListsSnippetsReactingScrollPosition3 {
+    @Composable
+    fun Snippet(messages: List<Message>) {
+        val listState = rememberLazyListState()
+
+        LazyColumn(state = listState) {
+            // ...
+        }
+
+        LaunchedEffect(listState) {
+            snapshotFlow { listState.firstVisibleItemIndex }
+                .map { index -> index > 0 }
+                .distinctUntilChanged()
+                .filter { it == true }
+                .collect {
+                    MyAnalyticsService.sendScrolledPastFirstItemEvent()
+                }
+        }
+    }
+}
+
+internal object ListsSnippetsControllingScrollPosition {
+    @Composable
+    fun MessageList(messages: List<Message>) {
+        val listState = rememberLazyListState()
+        // Remember a CoroutineScope to be able to launch
+        val coroutineScope = rememberCoroutineScope()
+
+        LazyColumn(state = listState) {
+            // ...
+        }
+
+        ScrollToTopButton(
+            onClick = {
+                coroutineScope.launch {
+                    // Animate scroll to the first item
+                    listState.animateScrollToItem(index = 0)
+                }
+            },
+        )
+    }
+}
+
+internal object ListsSnippetsPaging {
+    @Composable
+    fun MessageList(pager: Pager<Int, Message>) {
+        val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+
+        LazyColumn {
+            items(lazyPagingItems) { message ->
+                if (message != null) {
+                    MessageRow(message)
+                } else {
+                    MessagePlaceholder()
+                }
+            }
+        }
+    }
+}
+
+internal object ListsSnippetsItemKeys {
+    @Composable
+    fun MessageList(messages: List<Message>) {
+        LazyColumn {
+            items(
+                items = messages,
+                key = { message ->
+                    // Return a stable + unique key for the item
+                    message.id
+                }
+            ) { message ->
+                MessageRow(message)
+            }
+        }
+    }
+}
+
+// ========================
+// Fakes below
+// ========================
+
+class Message(val id: Long)
+class Item
+
+data class Contact(val firstName: String)
+val contacts = listOf<Contact>()
+
+class Photo
+val photos = listOf<Photo>()
+
+@Composable
+private fun MessageRow(message: Message) = Unit
+
+@Composable
+private fun MessagePlaceholder() = Unit
+
+@Composable
+private fun ItemRow(item: Item) = Unit
+
+@Composable
+private fun Header() = Unit
+
+@Composable
+private fun CharacterHeader(initial: Char) = Unit
+
+@Composable
+private fun ContactListItem(contact: Contact) = Unit
+
+@Composable
+private fun PhotoItem(photo: Photo) = Unit
+
+@Composable
+private fun ScrollToTopButton(onClick: () -> Unit = {}) = Unit
+
+object MyAnalyticsService {
+    fun sendScrolledPastFirstItemEvent() = Unit
+}
\ No newline at end of file
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
index 7912d72..3feae6b 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
@@ -28,7 +28,7 @@
 import androidx.compose.ui.layout.FirstBaseline
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.semantics.SemanticsActions
-import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.IdlingResource
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertAll
 import androidx.compose.ui.test.assertAny
@@ -82,26 +82,18 @@
 import androidx.compose.ui.test.hasAnyChild
 import androidx.compose.ui.test.hasAnyDescendant
 import androidx.compose.ui.test.hasAnySibling
-import androidx.compose.ui.test.hasBottomPosition
 import androidx.compose.ui.test.hasClickAction
 import androidx.compose.ui.test.hasContentDescription
-import androidx.compose.ui.test.hasHeight
-import androidx.compose.ui.test.hasHeightAtLeast
 import androidx.compose.ui.test.hasImeAction
-import androidx.compose.ui.test.hasLeftPosition
 import androidx.compose.ui.test.hasNoClickAction
 import androidx.compose.ui.test.hasNoScrollAction
 import androidx.compose.ui.test.hasParent
 import androidx.compose.ui.test.hasProgressBarRangeInfo
-import androidx.compose.ui.test.hasRightPosition
 import androidx.compose.ui.test.hasScrollAction
 import androidx.compose.ui.test.hasSetTextAction
 import androidx.compose.ui.test.hasStateDescription
 import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.hasTopPosition
-import androidx.compose.ui.test.hasWidth
-import androidx.compose.ui.test.hasWidthAtLeast
 import androidx.compose.ui.test.height
 import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.isEnabled
@@ -172,6 +164,8 @@
 import androidx.compose.ui.test.width
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
 import android.view.KeyEvent as AndroidKeyEvent
 import android.view.KeyEvent.ACTION_DOWN as ActionDown
 import android.view.KeyEvent.KEYCODE_A as KeyCodeA
@@ -226,7 +220,6 @@
     )
 
     // MATCHERS
-    @OptIn(ExperimentalTestApi::class)
     composeTestRule.onNode(
         hasClickAction() and
             hasNoClickAction() and
@@ -239,14 +232,6 @@
             hasStateDescription("label") and
             hasTestTag("tag") and
             hasText("text") and
-            hasLeftPosition(0.dp) and
-            hasTopPosition(0.dp) and
-            hasRightPosition(0.dp) and
-            hasBottomPosition(0.dp) and
-            hasWidth(0.dp) and
-            hasWidthAtLeast(0.dp) and
-            hasHeight(0.dp) and
-            hasHeightAtLeast(0.dp) and
             isDialog() and
             isEnabled() and
             isFocusable() and
@@ -396,6 +381,7 @@
         runOnIdle { }
         runOnUiThread { }
         waitForIdle()
+        waitUntil { true }
         mainClock.apply {
             autoAdvance
             currentTime
@@ -403,6 +389,11 @@
             advanceTimeByFrame()
             advanceTimeUntil { true }
         }
+        registerIdlingResource(idlingResource)
+        unregisterIdlingResource(idlingResource)
+    }
+    GlobalScope.launch {
+        nonAndroidComposeTestRule.awaitIdle()
     }
 
     // ANDROID COMPOSE TEST RULE
@@ -428,4 +419,8 @@
 private val nonAndroidComposeTestRule = createComposeRule()
 private val keyEvent2 = KeyEvent(AndroidKeyEvent(ActionDown, KeyCodeA))
 private val offset = Offset(0f, 0f)
-private val rangeInfo = ProgressBarRangeInfo(0f, 0f..1f)
\ No newline at end of file
+private val rangeInfo = ProgressBarRangeInfo(0f, 0f..1f)
+private val idlingResource = object : IdlingResource {
+    override val isIdleNow: Boolean
+        get() = TODO("Stub!")
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/Testing.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/Testing.kt
index f69f42d..9060916 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/Testing.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/Testing.kt
@@ -224,6 +224,20 @@
     composeTestRule.unregisterIdlingResource(idlingResource)
 }
 
+private fun TestingSyncSnippets5() {
+    composeTestRule.mainClock.autoAdvance = true // default
+    composeTestRule.waitForIdle() // Advances the clock until Compose is idle
+
+    composeTestRule.mainClock.autoAdvance = false
+    composeTestRule.waitForIdle() // Only waits for Idling Resources to become idle
+}
+
+private fun TestingSyncSnippets6and7() {
+    composeTestRule.mainClock.advanceTimeUntil(timeoutMs) { condition }
+
+    composeTestRule.waitUntil(timeoutMs) { condition }
+}
+
 private object TestingSemanticsSnippets1 {
     // Creates a Semantics property of type boolean
     val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
@@ -295,7 +309,9 @@
 private lateinit var key: SemanticsPropertyKey<AccessibilityAction<() -> Boolean>>
 private var keyEvent = KeyEvent(AndroidKeyEvent(ActionDown, KeyCodeA))
 private const val milliseconds = 10L
-val idlingResource = object : IdlingResource {
+private const val timeoutMs = 10L
+private val idlingResource = object : IdlingResource {
     override val isIdleNow: Boolean
         get() = TODO("Stub!")
-}
\ No newline at end of file
+}
+private val condition = true
\ No newline at end of file
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
index 907d2b5..8931b7e 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
@@ -25,6 +25,8 @@
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ComposableNode
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetComposablesResponse
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetParametersResponse
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Parameter
 import org.junit.Rule
@@ -39,8 +41,9 @@
     fun lambda(): Unit = runBlocking {
         val composables = rule.inspectorTester.sendCommand(GetComposablesCommand(rule.rootId))
             .getComposablesResponse
-        // first button's id
-        val buttonId = composables.rootsList[0]!!.nodesList[0]!!.childrenList[0]!!.id
+
+        val buttons = composables.filter("Button")
+        val buttonId = buttons.first().id
         val params = rule.inspectorTester.sendCommand(GetParametersCommand(rule.rootId, buttonId))
             .getParametersResponse
 
@@ -57,8 +60,8 @@
         val composables = rule.inspectorTester.sendCommand(GetComposablesCommand(rule.rootId))
             .getComposablesResponse
 
-        // second's button id
-        val buttonId = composables.rootsList[0]!!.nodesList[0]!!.childrenList[1]!!.id
+        val buttons = composables.filter("Button")
+        val buttonId = buttons.last().id
         val params = rule.inspectorTester.sendCommand(GetParametersCommand(rule.rootId, buttonId))
             .getParametersResponse
 
@@ -81,4 +84,14 @@
     return parameterGroup.parameterList.find {
         strings[it.name] == name
     }
-}
\ No newline at end of file
+}
+
+private fun GetComposablesResponse.filter(name: String): List<ComposableNode> {
+    val strings = stringsList.toMap()
+    return rootsList.flatMap { it.nodesList }.flatMap { it.flatten() }.filter {
+        strings[it.name] == name
+    }
+}
+
+private fun ComposableNode.flatten(): List<ComposableNode> =
+    listOf(this).plus(this.childrenList.flatMap { it.flatten() })
\ No newline at end of file
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
index e6f6c73..47cb2ae 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.inspection.inspector
 
-import android.view.View
 import android.view.ViewGroup
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Column
@@ -33,10 +32,10 @@
 import androidx.compose.material.icons.filled.Call
 import androidx.compose.material.icons.filled.FavoriteBorder
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.tooling.CompositionData
-import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.tooling.CompositionData
 import androidx.compose.runtime.tooling.LocalInspectionTables
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.R
@@ -79,7 +78,7 @@
 @OptIn(UiToolingDataApi::class)
 class LayoutInspectorTreeTest {
     private lateinit var density: Density
-    private lateinit var view: View
+    private lateinit var view: ViewGroup
 
     @get:Rule
     val activityScenario = ActivityScenarioRule(TestActivity::class.java)
@@ -88,7 +87,7 @@
     fun before() {
         activityScenario.scenario.onActivity {
             density = Density(it)
-            view = it.findViewById<ViewGroup>(android.R.id.content)
+            view = it.findViewById(android.R.id.content)
         }
         isDebugInspectorInfoEnabled = true
     }
@@ -116,27 +115,12 @@
 
         // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
         view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val viewWidth = with(density) { view.width.toDp() }
-        val viewHeight = with(density) { view.height.toDp() }
         val builder = LayoutInspectorTree()
         val nodes = builder.convert(view)
         dumpNodes(nodes, builder)
 
         validate(nodes, builder, checkParameters = false) {
             node(
-                name = "Content",
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("Box")
-            )
-            node(
-                name = "Box",
-                isRenderNode = true,
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("Column")
-            )
-            node(
                 name = "Column",
                 fileName = "LayoutInspectorTreeTest.kt",
                 left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
@@ -196,31 +180,16 @@
 
         // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
         view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val viewWidth = with(density) { view.width.toDp() }
-        val viewHeight = with(density) { view.height.toDp() }
         val builder = LayoutInspectorTree()
         val nodes = builder.convert(view)
         dumpNodes(nodes, builder)
 
         validate(nodes, builder, checkParameters = false) {
             node(
-                name = "Content",
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("Box")
-            )
-            node(
-                name = "Box",
-                isRenderNode = true,
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("MaterialTheme")
-            )
-            node(
                 name = "MaterialTheme",
                 hasTransformations = true,
                 fileName = "LayoutInspectorTreeTest.kt",
-                left = 65.8.dp, top = 49.7.dp, width = 86.2.dp, height = 21.7.dp,
+                left = 68.0.dp, top = 49.7.dp, width = 88.6.dp, height = 21.7.dp,
                 children = listOf("Text")
             )
             node(
@@ -228,7 +197,7 @@
                 isRenderNode = true,
                 hasTransformations = true,
                 fileName = "LayoutInspectorTreeTest.kt",
-                left = 65.8.dp, top = 49.7.dp, width = 86.2.dp, height = 21.7.dp,
+                left = 68.0.dp, top = 49.7.dp, width = 88.6.dp, height = 21.7.dp,
             )
         }
     }
@@ -402,7 +371,11 @@
         checkParameters: Boolean,
         block: TreeValidationReceiver.() -> Unit = {}
     ) {
-        val nodes = result.flatMap { flatten(it) }.iterator()
+        val nodes = result.flatMap { flatten(it) }.listIterator()
+        // Ignore a starting CompositionLocalProvider...
+        if (nodes.next().name != "CompositionLocalProvider") {
+            nodes.previous()
+        }
         val tree = TreeValidationReceiver(nodes, density, checkParameters, builder)
         tree.block()
     }
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
index 815ec9c..7138052 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
@@ -38,6 +38,7 @@
 import kotlin.math.roundToInt
 
 private val systemPackages = setOf(
+    -1,
     packageNameHash("androidx.compose.animation"),
     packageNameHash("androidx.compose.animation.core"),
     packageNameHash("androidx.compose.desktop"),
@@ -53,27 +54,18 @@
     packageNameHash("androidx.compose.ui.tooling"),
     packageNameHash("androidx.compose.ui.selection"),
     packageNameHash("androidx.compose.ui.semantics"),
-    packageNameHash("androidx.compose.ui.viewinterop")
-)
-
-private val unwantedPackages = setOf(
-    -1,
-    packageNameHash("androidx.compose.ui"),
-    packageNameHash("androidx.compose.runtime"),
-    packageNameHash("androidx.compose.ui.tooling"),
-    packageNameHash("androidx.compose.ui.selection"),
-    packageNameHash("androidx.compose.ui.semantics"),
-    packageNameHash("androidx.compose.ui.inspection.inspector"),
+    packageNameHash("androidx.compose.ui.viewinterop"),
+    packageNameHash("androidx.compose.ui.window"),
 )
 
 private val unwantedCalls = setOf(
     "emit",
     "remember",
-    "Inspectable",
-    "Layout",
     "CompositionLocalProvider",
-    "SelectionContainer",
-    "SelectionLayout"
+    "Content",
+    "Inspectable",
+    "ProvideAndroidCompositionLocals",
+    "ProvideCommonCompositionLocals",
 )
 
 private fun packageNameHash(packageName: String) =
@@ -418,8 +410,7 @@
     }
 
     private fun unwantedGroup(node: MutableInspectorNode): Boolean =
-        (node.packageHash in unwantedPackages && node.name in unwantedCalls) ||
-            (hideSystemNodes && node.packageHash in systemPackages)
+        node.packageHash in systemPackages && (hideSystemNodes || node.name in unwantedCalls)
 
     private fun newNode(): MutableInspectorNode =
         if (cache.isNotEmpty()) cache.pop() else MutableInspectorNode()
diff --git a/compose/ui/ui-test/api/1.0.0-beta02.txt b/compose/ui/ui-test/api/1.0.0-beta02.txt
index 970280d..963ed71 100644
--- a/compose/ui/ui-test/api/1.0.0-beta02.txt
+++ b/compose/ui/ui-test/api/1.0.0-beta02.txt
@@ -76,26 +76,18 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyChild(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyDescendant(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnySibling(androidx.compose.ui.test.SemanticsMatcher matcher);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasBottomPosition-ioHfwGI(float bottom, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasContentDescription(String label, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeight-ioHfwGI(float height, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeightAtLeast-ioHfwGI(float height, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasImeAction(androidx.compose.ui.text.input.ImeAction actionType);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasLeftPosition-ioHfwGI(float left, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasRightPosition-ioHfwGI(float right, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
     method public static androidx.compose.ui.test.SemanticsMatcher hasText(String text, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasTopPosition-ioHfwGI(float top, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidth-ioHfwGI(float width, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidthAtLeast-ioHfwGI(float width, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher isDialog();
     method public static androidx.compose.ui.test.SemanticsMatcher isEnabled();
     method public static androidx.compose.ui.test.SemanticsMatcher isFocusable();
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index 970280d..963ed71 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -76,26 +76,18 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyChild(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyDescendant(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnySibling(androidx.compose.ui.test.SemanticsMatcher matcher);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasBottomPosition-ioHfwGI(float bottom, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasContentDescription(String label, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeight-ioHfwGI(float height, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeightAtLeast-ioHfwGI(float height, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasImeAction(androidx.compose.ui.text.input.ImeAction actionType);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasLeftPosition-ioHfwGI(float left, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasRightPosition-ioHfwGI(float right, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
     method public static androidx.compose.ui.test.SemanticsMatcher hasText(String text, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasTopPosition-ioHfwGI(float top, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidth-ioHfwGI(float width, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidthAtLeast-ioHfwGI(float width, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher isDialog();
     method public static androidx.compose.ui.test.SemanticsMatcher isEnabled();
     method public static androidx.compose.ui.test.SemanticsMatcher isFocusable();
diff --git a/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt b/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt
index 970280d..963ed71 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt
@@ -76,26 +76,18 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyChild(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyDescendant(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnySibling(androidx.compose.ui.test.SemanticsMatcher matcher);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasBottomPosition-ioHfwGI(float bottom, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasContentDescription(String label, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeight-ioHfwGI(float height, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeightAtLeast-ioHfwGI(float height, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasImeAction(androidx.compose.ui.text.input.ImeAction actionType);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasLeftPosition-ioHfwGI(float left, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasRightPosition-ioHfwGI(float right, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
     method public static androidx.compose.ui.test.SemanticsMatcher hasText(String text, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasTopPosition-ioHfwGI(float top, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidth-ioHfwGI(float width, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidthAtLeast-ioHfwGI(float width, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher isDialog();
     method public static androidx.compose.ui.test.SemanticsMatcher isEnabled();
     method public static androidx.compose.ui.test.SemanticsMatcher isFocusable();
diff --git a/compose/ui/ui-test/api/public_plus_experimental_current.txt b/compose/ui/ui-test/api/public_plus_experimental_current.txt
index 970280d..963ed71 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_current.txt
@@ -76,26 +76,18 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyChild(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyDescendant(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnySibling(androidx.compose.ui.test.SemanticsMatcher matcher);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasBottomPosition-ioHfwGI(float bottom, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasContentDescription(String label, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeight-ioHfwGI(float height, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeightAtLeast-ioHfwGI(float height, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasImeAction(androidx.compose.ui.text.input.ImeAction actionType);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasLeftPosition-ioHfwGI(float left, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasRightPosition-ioHfwGI(float right, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
     method public static androidx.compose.ui.test.SemanticsMatcher hasText(String text, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasTopPosition-ioHfwGI(float top, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidth-ioHfwGI(float width, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidthAtLeast-ioHfwGI(float width, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher isDialog();
     method public static androidx.compose.ui.test.SemanticsMatcher isEnabled();
     method public static androidx.compose.ui.test.SemanticsMatcher isFocusable();
diff --git a/compose/ui/ui-test/api/restricted_1.0.0-beta02.txt b/compose/ui/ui-test/api/restricted_1.0.0-beta02.txt
index 970280d..963ed71 100644
--- a/compose/ui/ui-test/api/restricted_1.0.0-beta02.txt
+++ b/compose/ui/ui-test/api/restricted_1.0.0-beta02.txt
@@ -76,26 +76,18 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyChild(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyDescendant(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnySibling(androidx.compose.ui.test.SemanticsMatcher matcher);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasBottomPosition-ioHfwGI(float bottom, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasContentDescription(String label, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeight-ioHfwGI(float height, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeightAtLeast-ioHfwGI(float height, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasImeAction(androidx.compose.ui.text.input.ImeAction actionType);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasLeftPosition-ioHfwGI(float left, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasRightPosition-ioHfwGI(float right, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
     method public static androidx.compose.ui.test.SemanticsMatcher hasText(String text, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasTopPosition-ioHfwGI(float top, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidth-ioHfwGI(float width, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidthAtLeast-ioHfwGI(float width, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher isDialog();
     method public static androidx.compose.ui.test.SemanticsMatcher isEnabled();
     method public static androidx.compose.ui.test.SemanticsMatcher isFocusable();
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index 970280d..963ed71 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -76,26 +76,18 @@
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyChild(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnyDescendant(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasAnySibling(androidx.compose.ui.test.SemanticsMatcher matcher);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasBottomPosition-ioHfwGI(float bottom, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasContentDescription(String label, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeight-ioHfwGI(float height, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasHeightAtLeast-ioHfwGI(float height, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasImeAction(androidx.compose.ui.text.input.ImeAction actionType);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasLeftPosition-ioHfwGI(float left, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoClickAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasNoScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasParent(androidx.compose.ui.test.SemanticsMatcher matcher);
     method public static androidx.compose.ui.test.SemanticsMatcher hasProgressBarRangeInfo(androidx.compose.ui.semantics.ProgressBarRangeInfo rangeInfo);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasRightPosition-ioHfwGI(float right, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher hasScrollAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasSetTextAction();
     method public static androidx.compose.ui.test.SemanticsMatcher hasStateDescription(String value);
     method public static androidx.compose.ui.test.SemanticsMatcher hasTestTag(String testTag);
     method public static androidx.compose.ui.test.SemanticsMatcher hasText(String text, optional boolean substring, optional boolean ignoreCase);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasTopPosition-ioHfwGI(float top, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidth-ioHfwGI(float width, optional float tolerance);
-    method @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsMatcher hasWidthAtLeast-ioHfwGI(float width, optional float tolerance);
     method public static androidx.compose.ui.test.SemanticsMatcher isDialog();
     method public static androidx.compose.ui.test.SemanticsMatcher isEnabled();
     method public static androidx.compose.ui.test.SemanticsMatcher isFocusable();
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/BoundsAssertions.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/BoundsAssertions.kt
index 4dcbaa2..79228a4 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/BoundsAssertions.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/BoundsAssertions.kt
@@ -22,44 +22,55 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpRect
+import androidx.compose.ui.unit.toSize
+import kotlin.math.absoluteValue
+
+private const val floatTolerance = 0.5f
 
 /**
  * Asserts that the layout of this node has width equal to [expectedWidth].
  *
  * @throws AssertionError if comparison fails.
  */
-@OptIn(ExperimentalTestApi::class)
-fun SemanticsNodeInteraction.assertWidthIsEqualTo(expectedWidth: Dp): SemanticsNodeInteraction =
-    assert(hasWidth(expectedWidth))
+fun SemanticsNodeInteraction.assertWidthIsEqualTo(expectedWidth: Dp): SemanticsNodeInteraction {
+    return withUnclippedBoundsInRoot {
+        it.width.toDp().assertIsEqualTo(expectedWidth, "width")
+    }
+}
 
 /**
  * Asserts that the layout of this node has height equal to [expectedHeight].
  *
  * @throws AssertionError if comparison fails.
  */
-@OptIn(ExperimentalTestApi::class)
-fun SemanticsNodeInteraction.assertHeightIsEqualTo(expectedHeight: Dp): SemanticsNodeInteraction =
-    assert(hasHeight(expectedHeight))
-
+fun SemanticsNodeInteraction.assertHeightIsEqualTo(expectedHeight: Dp): SemanticsNodeInteraction {
+    return withUnclippedBoundsInRoot {
+        it.height.toDp().assertIsEqualTo(expectedHeight, "height")
+    }
+}
 /**
  * Asserts that the layout of this node has width that is greater ot equal to [expectedMinWidth].
  *
  * @throws AssertionError if comparison fails.
  */
-@OptIn(ExperimentalTestApi::class)
-fun SemanticsNodeInteraction.assertWidthIsAtLeast(expectedMinWidth: Dp): SemanticsNodeInteraction =
-    assert(hasWidthAtLeast(expectedMinWidth))
+fun SemanticsNodeInteraction.assertWidthIsAtLeast(expectedMinWidth: Dp): SemanticsNodeInteraction {
+    return withUnclippedBoundsInRoot {
+        isAtLeastOrThrow("width", it.width, expectedMinWidth)
+    }
+}
 
 /**
  * Asserts that the layout of this node has height that is greater ot equal to [expectedMinHeight].
  *
  * @throws AssertionError if comparison fails.
  */
-@OptIn(ExperimentalTestApi::class)
 fun SemanticsNodeInteraction.assertHeightIsAtLeast(
     expectedMinHeight: Dp
-): SemanticsNodeInteraction =
-    assert(hasHeightAtLeast(expectedMinHeight))
+): SemanticsNodeInteraction {
+    return withUnclippedBoundsInRoot {
+        isAtLeastOrThrow("height", it.height, expectedMinHeight)
+    }
+}
 
 /**
  * Asserts that the layout of this node has position in the root composable that is equal to the
@@ -70,12 +81,15 @@
  *
  * @throws AssertionError if comparison fails.
  */
-@OptIn(ExperimentalTestApi::class)
 fun SemanticsNodeInteraction.assertPositionInRootIsEqualTo(
     expectedLeft: Dp,
     expectedTop: Dp
-): SemanticsNodeInteraction =
-    assert(hasLeftPosition(expectedLeft).and(hasTopPosition(expectedTop)))
+): SemanticsNodeInteraction {
+    return withUnclippedBoundsInRoot {
+        it.left.toDp().assertIsEqualTo(expectedLeft, "left")
+        it.top.toDp().assertIsEqualTo(expectedTop, "top")
+    }
+}
 
 /**
  * Asserts that the layout of this node has the top position in the root composable that is equal to
@@ -85,11 +99,13 @@
  *
  * @throws AssertionError if comparison fails.
  */
-@OptIn(ExperimentalTestApi::class)
 fun SemanticsNodeInteraction.assertTopPositionInRootIsEqualTo(
     expectedTop: Dp
-): SemanticsNodeInteraction =
-    assert(hasTopPosition(expectedTop))
+): SemanticsNodeInteraction {
+    return withUnclippedBoundsInRoot {
+        it.top.toDp().assertIsEqualTo(expectedTop, "top")
+    }
+}
 
 /**
  * Asserts that the layout of this node has the left position in the root composable that is
@@ -99,11 +115,13 @@
  *
  * @throws AssertionError if comparison fails.
  */
-@OptIn(ExperimentalTestApi::class)
 fun SemanticsNodeInteraction.assertLeftPositionInRootIsEqualTo(
     expectedLeft: Dp
-): SemanticsNodeInteraction =
-    assert(hasLeftPosition(expectedLeft))
+): SemanticsNodeInteraction {
+    return withUnclippedBoundsInRoot {
+        it.left.toDp().assertIsEqualTo(expectedLeft, "left")
+    }
+}
 
 /**
  * Returns the bounds of the layout of this node. The bounds are relative to the root composable.
@@ -136,6 +154,30 @@
     }
 }
 
+/**
+ * Asserts that this value is equal to the given [expected] value.
+ *
+ * Performs the comparison with the given [tolerance] or the default one if none is provided. It is
+ * recommended to use tolerance when comparing positions and size coming from the framework as there
+ * can be rounding operation performed by individual layouts so the values can be slightly off from
+ * the expected ones.
+ *
+ * @param expected The expected value to which this one should be equal to.
+ * @param subject Used in the error message to identify which item this assertion failed on.
+ * @param tolerance The tolerance within which the values should be treated as equal.
+ *
+ * @throws AssertionError if comparison fails.
+ */
+private fun Dp.assertIsEqualTo(expected: Dp, subject: String = "", tolerance: Dp = Dp(.5f)) {
+    val diff = (this - expected).value.absoluteValue
+    if (diff > tolerance.value) {
+        // Comparison failed, report the error in DPs
+        throw AssertionError(
+            "Actual $subject is $this, expected $expected (tolerance: $tolerance)"
+        )
+    }
+}
+
 private fun <R> SemanticsNodeInteraction.withDensity(
     operation: Density.(SemanticsNode) -> R
 ): R {
@@ -153,3 +195,21 @@
     assertion.invoke(density, node.unclippedBoundsInRoot)
     return this
 }
+
+private val SemanticsNode.unclippedBoundsInRoot: Rect
+    get() {
+        return Rect(positionInRoot, size.toSize())
+    }
+
+private fun Density.isAtLeastOrThrow(
+    subject: String,
+    actualPx: Float,
+    expected: Dp
+) {
+    if (actualPx + floatTolerance < expected.toPx()) {
+        // Comparison failed, report the error in DPs
+        throw AssertionError(
+            "Actual $subject is ${actualPx.toDp()}, expected at least $expected"
+        )
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt
index 94c44f0..8977883 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Filters.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.test
 
-import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsNode
@@ -24,11 +23,7 @@
 import androidx.compose.ui.semantics.getOrNull
 import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.util.fastAny
-import kotlin.math.abs
 
 /**
  * Returns whether the node is enabled.
@@ -307,150 +302,6 @@
     SemanticsMatcher.keyIsDefined(SemanticsActions.SetText)
 
 /**
- * Returns whether the node has the given [width], within a certain [tolerance]. The default
- * tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasWidthAtLeast
- * @see hasHeight
- * @see hasHeightAtLeast
- */
-@ExperimentalTestApi
-fun hasWidth(width: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("width = $width ± $tolerance") { node ->
-        node.withDensity {
-            abs(node.unclippedBoundsInRoot.width - width.toPx()) <= tolerance.toPx()
-        }
-    }
-
-/**
- * Returns whether the node has a width of at least the given [width], with a certain [tolerance].
- * The default tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasWidth
- * @see hasHeight
- * @see hasHeightAtLeast
- */
-@ExperimentalTestApi
-fun hasWidthAtLeast(width: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("width > $width - $tolerance") { node ->
-        node.withDensity {
-            node.unclippedBoundsInRoot.width - width.toPx() >= -tolerance.toPx()
-        }
-    }
-
-/**
- * Returns whether the node has the given [height], within a certain [tolerance]. The default
- * tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasWidth
- * @see hasWidthAtLeast
- * @see hasHeightAtLeast
- */
-@ExperimentalTestApi
-fun hasHeight(height: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("height = $height ± $tolerance") { node ->
-        node.withDensity {
-            abs(node.unclippedBoundsInRoot.height - height.toPx()) <= tolerance.toPx()
-        }
-    }
-
-/**
- * Returns whether the node has a height of at least the given [height], with a certain
- * [tolerance]. The default tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasWidth
- * @see hasWidthAtLeast
- * @see hasHeight
- */
-@ExperimentalTestApi
-fun hasHeightAtLeast(height: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("height > $height - $tolerance") { node ->
-        node.withDensity {
-            node.unclippedBoundsInRoot.height - height.toPx() >= -tolerance.toPx()
-        }
-    }
-
-/**
- * Returns whether the node's left edge relative to the root matches the given [left], within a
- * certain [tolerance]. The default tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasTopPosition
- * @see hasRightPosition
- * @see hasBottomPosition
- */
-@ExperimentalTestApi
-fun hasLeftPosition(left: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("left = $left ± $tolerance") { node ->
-        node.withDensity {
-            abs(node.unclippedBoundsInRoot.left - left.toPx()) <= tolerance.toPx()
-        }
-    }
-
-/**
- * Returns whether the node's top edge relative to the root matches the given [top], within a
- * certain [tolerance]. The default tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasLeftPosition
- * @see hasRightPosition
- * @see hasBottomPosition
- */
-@ExperimentalTestApi
-fun hasTopPosition(top: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("top = $top ± $tolerance") { node ->
-        node.withDensity {
-            abs(node.unclippedBoundsInRoot.top - top.toPx()) <= tolerance.toPx()
-        }
-    }
-
-/**
- * Returns whether the node's right edge relative to the root matches the given [right], within a
- * certain [tolerance]. The default tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasLeftPosition
- * @see hasTopPosition
- * @see hasBottomPosition
- */
-@ExperimentalTestApi
-fun hasRightPosition(right: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("right = $right ± $tolerance") { node ->
-        node.withDensity {
-            abs(node.unclippedBoundsInRoot.right - right.toPx()) <= tolerance.toPx()
-        }
-    }
-
-/**
- * Returns whether the node's bottom edge relative to the root matches the given [bottom], within
- * a certain [tolerance]. The default tolerance is 0.5 dp.
- *
- * This is mostly useful in [assertions][assert].
- *
- * @see hasLeftPosition
- * @see hasTopPosition
- * @see hasRightPosition
- */
-@ExperimentalTestApi
-fun hasBottomPosition(bottom: Dp, tolerance: Dp = DefaultTolerance): SemanticsMatcher =
-    SemanticsMatcher("bottom = $bottom ± $tolerance") { node ->
-        node.withDensity {
-            abs(node.unclippedBoundsInRoot.bottom - bottom.toPx()) <= tolerance.toPx()
-        }
-    }
-
-/**
  * Return whether the node is the root semantics node.
  *
  * There is always one root in every node tree, added implicitly by Compose.
@@ -560,11 +411,3 @@
             }
         }
     }
-
-private val DefaultTolerance = Dp(.5f)
-
-internal val SemanticsNode.unclippedBoundsInRoot: Rect
-    get() = Rect(positionInRoot, size.toSize())
-
-private fun <R> SemanticsNode.withDensity(block: Density.() -> R): R =
-    block.invoke(root!!.density)
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 244c29d..0dbe1df 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
@@ -101,27 +101,12 @@
 
         // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
         view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val viewWidth = with(density) { view.width.toDp() }
-        val viewHeight = with(density) { view.height.toDp() }
         val builder = LayoutInspectorTree()
         val nodes = builder.convert(view)
         dumpNodes(nodes, builder)
 
         validate(nodes, builder, checkParameters = false) {
             node(
-                name = "Content",
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("Box")
-            )
-            node(
-                name = "Box",
-                isRenderNode = true,
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("Column")
-            )
-            node(
                 name = "Column",
                 fileName = "LayoutInspectorTreeTest.kt",
                 left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
@@ -181,31 +166,16 @@
 
         // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
         view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val viewWidth = with(density) { view.width.toDp() }
-        val viewHeight = with(density) { view.height.toDp() }
         val builder = LayoutInspectorTree()
         val nodes = builder.convert(view)
         dumpNodes(nodes, builder)
 
         validate(nodes, builder, checkParameters = false) {
             node(
-                name = "Content",
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("Box")
-            )
-            node(
-                name = "Box",
-                isRenderNode = true,
-                fileName = "",
-                left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
-                children = listOf("MaterialTheme")
-            )
-            node(
                 name = "MaterialTheme",
                 hasTransformations = true,
                 fileName = "LayoutInspectorTreeTest.kt",
-                left = 65.8.dp, top = 49.7.dp, width = 86.2.dp, height = 21.7.dp,
+                left = 68.0.dp, top = 49.7.dp, width = 88.6.dp, height = 21.7.dp,
                 children = listOf("Text")
             )
             node(
@@ -213,7 +183,7 @@
                 isRenderNode = true,
                 hasTransformations = true,
                 fileName = "LayoutInspectorTreeTest.kt",
-                left = 65.8.dp, top = 49.7.dp, width = 86.2.dp, height = 21.7.dp,
+                left = 68.0.dp, top = 49.7.dp, width = 88.6.dp, height = 21.7.dp,
             )
         }
     }
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 19f8ed8..85f0933 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
@@ -38,6 +38,7 @@
 import kotlin.math.roundToInt
 
 private val systemPackages = setOf(
+    -1,
     packageNameHash("androidx.compose.animation"),
     packageNameHash("androidx.compose.animation.core"),
     packageNameHash("androidx.compose.desktop"),
@@ -53,26 +54,18 @@
     packageNameHash("androidx.compose.ui.tooling"),
     packageNameHash("androidx.compose.ui.selection"),
     packageNameHash("androidx.compose.ui.semantics"),
-    packageNameHash("androidx.compose.ui.viewinterop")
-)
-
-private val unwantedPackages = setOf(
-    -1,
-    packageNameHash("androidx.compose.ui"),
-    packageNameHash("androidx.compose.runtime"),
-    packageNameHash("androidx.compose.ui.tooling"),
-    packageNameHash("androidx.compose.ui.selection"),
-    packageNameHash("androidx.compose.ui.semantics")
+    packageNameHash("androidx.compose.ui.viewinterop"),
+    packageNameHash("androidx.compose.ui.window"),
 )
 
 private val unwantedCalls = setOf(
     "emit",
     "remember",
-    "Inspectable",
-    "Layout",
     "CompositionLocalProvider",
-    "SelectionContainer",
-    "SelectionLayout"
+    "Content",
+    "Inspectable",
+    "ProvideAndroidCompositionLocals",
+    "ProvideCommonCompositionLocals",
 )
 
 private fun packageNameHash(packageName: String) =
@@ -416,8 +409,7 @@
     }
 
     private fun unwantedGroup(node: MutableInspectorNode): Boolean =
-        (node.packageHash in unwantedPackages && node.name in unwantedCalls) ||
-            (hideSystemNodes && node.packageHash in systemPackages)
+        node.packageHash in systemPackages && (hideSystemNodes || node.name in unwantedCalls)
 
     private fun newNode(): MutableInspectorNode =
         if (cache.isNotEmpty()) cache.pop() else MutableInspectorNode()
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
index aa39593..08d62d4 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
@@ -65,6 +65,7 @@
                         next = item2
                         right = item2
                         down = item3
+                        previous = item4
                     }
                 )
                 FocusableText(
@@ -73,6 +74,7 @@
                         next = item3
                         left = item1
                         down = item4
+                        previous = item1
                     }
                 )
             }
@@ -83,6 +85,7 @@
                         next = item4
                         right = item4
                         up = item1
+                        previous = item2
                     }
                 )
                 FocusableText(
@@ -91,6 +94,7 @@
                         next = item1
                         left = item3
                         up = item2
+                        previous = item3
                     }
                 )
             }
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.desktop.kt
index 550feb08..cfe712b 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposePanel.desktop.kt
@@ -17,13 +17,11 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import org.jetbrains.skiko.ClipComponent
 import java.awt.Color
 import java.awt.Component
-import java.awt.event.ComponentAdapter
-import java.awt.event.ComponentEvent
 import javax.swing.JLayeredPane
 import javax.swing.SwingUtilities.isEventDispatchThread
-import org.jetbrains.skiko.ClipComponent
 
 /**
  * ComposePanel is a panel for building UI using Compose for Desktop.
@@ -37,18 +35,17 @@
         }
         setBackground(Color.white)
         setLayout(null)
-
-        addComponentListener(object : ComponentAdapter() {
-            override fun componentResized(e: ComponentEvent) {
-                layer?.wrapped?.setSize(width, height)
-            }
-        })
     }
 
     internal var layer: ComposeLayer? = null
     private val clipMap = mutableMapOf<Component, ClipComponent>()
     private var content: (@Composable () -> Unit)? = null
 
+    override fun setBounds(x: Int, y: Int, width: Int, height: Int) {
+        layer?.wrapped?.setSize(width, height)
+        super.setBounds(x, y, width, height)
+    }
+
     /**
      * Sets Compose content of the ComposePanel.
      *
@@ -97,6 +94,7 @@
         // content.
         layer = ComposeLayer()
         super.add(layer!!.component, Integer.valueOf(1))
+        layer?.wrapped?.setSize(width, height)
         initContent()
     }
 
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.desktop.kt
index 786b392..08be312 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeWindow.desktop.kt
@@ -17,12 +17,10 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionContext
+import org.jetbrains.skiko.ClipComponent
 import java.awt.Component
-import java.awt.event.ComponentAdapter
-import java.awt.event.ComponentEvent
 import javax.swing.JFrame
 import javax.swing.JLayeredPane
-import org.jetbrains.skiko.ClipComponent
 
 /**
  * ComposeWindow is a window for building UI using Compose for Desktop.
@@ -31,17 +29,18 @@
  */
 class ComposeWindow(val parent: AppFrame) : JFrame() {
     internal val layer = ComposeLayer()
-    private val pane = JLayeredPane()
+    private val pane = object : JLayeredPane() {
+        override fun setBounds(x: Int, y: Int, width: Int, height: Int) {
+            layer.wrapped.setSize(width, height)
+            super.setBounds(x, y, width, height)
+        }
+    }
+
     private val clipMap = mutableMapOf<Component, ClipComponent>()
 
     init {
         pane.setLayout(null)
         pane.add(layer.component, Integer.valueOf(1))
-        addComponentListener(object : ComponentAdapter() {
-            override fun componentResized(e: ComponentEvent) {
-                layer.wrapped.setSize(pane.width, pane.height)
-            }
-        })
         contentPane.add(pane)
     }
 
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
index 73797ec..bbb543a 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
@@ -66,6 +66,8 @@
     }
 
     val list = LinkedHashSet<DesktopOwner>()
+    private val listCopy = mutableListOf<DesktopOwner>()
+
     var keyboard: Keyboard? = null
 
     private var pointerId = 0L
@@ -113,7 +115,9 @@
             dispatcher.flush()
             frameClock.sendFrame(nanoTime)
 
-            for (owner in list) {
+            listCopy.clear()
+            listCopy.addAll(list)
+            for (owner in listCopy) {
                 owner.render(canvas, width, height)
             }
         }
@@ -121,7 +125,7 @@
         invalidateIfNeeded()
     }
 
-    val lastOwner: DesktopOwner?
+    private val lastOwner: DesktopOwner?
         get() = list.lastOrNull()
 
     fun onMousePressed(x: Int, y: Int, nativeEvent: MouseEvent? = null) {
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/DesktopPopupTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/DesktopPopupTest.kt
index 88125f3..efceb69 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/DesktopPopupTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/DesktopPopupTest.kt
@@ -19,14 +19,18 @@
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalDensity
@@ -196,4 +200,26 @@
 
         Truth.assertThat(lastCompositionState).isEqualTo(1)
     }
+
+    @Test(timeout = 5000)
+    fun `(Bug) use Popup inside LazyColumn`() {
+        rule.setContent {
+            var count by remember { mutableStateOf(0) }
+            LazyColumn {
+                items(count) {
+                    Popup { }
+                }
+            }
+            LaunchedEffect(Unit) {
+                withFrameNanos {
+                    count++
+                }
+                withFrameNanos {
+                    count++
+                }
+            }
+        }
+
+        rule.waitForIdle()
+    }
 }
diff --git a/core/OWNERS b/core/OWNERS
index 1de526b..458cdf0 100644
--- a/core/OWNERS
+++ b/core/OWNERS
@@ -2,7 +2,6 @@
 cinek@google.com
 clarabayarri@google.com
 juliacr@google.com
-jmonk@google.com
 
 # For fingerprint related files
 jaggies@google.com
diff --git a/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java b/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
index 70afe9b..c9aa1e9 100644
--- a/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
+++ b/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
@@ -304,10 +304,7 @@
          */
         @RestrictTo(LIBRARY_GROUP_PREFIX)
         public final void callbackSuccessAsync(final Typeface typeface, @Nullable Handler handler) {
-            if (handler == null) {
-                handler = new Handler(Looper.getMainLooper());
-            }
-            handler.post(new Runnable() {
+            ensureHandler(handler).post(new Runnable() {
                 @Override
                 public void run() {
                     onFontRetrieved(typeface);
@@ -323,16 +320,17 @@
         @RestrictTo(LIBRARY_GROUP_PREFIX)
         public final void callbackFailAsync(
                 @FontRequestFailReason final int reason, @Nullable Handler handler) {
-            if (handler == null) {
-                handler = new Handler(Looper.getMainLooper());
-            }
-            handler.post(new Runnable() {
+            ensureHandler(handler).post(new Runnable() {
                 @Override
                 public void run() {
                     onFontRetrievalFailed(reason);
                 }
             });
         }
+
+        private static Handler ensureHandler(@Nullable Handler handler) {
+            return handler == null ? new Handler(Looper.getMainLooper()) : handler;
+        }
     }
 
     /**
@@ -411,6 +409,16 @@
      * Load the given font. This method will always return null for asynchronous requests, which
      * provide a fontCallback, as there is no immediate result. When the callback is not provided,
      * the request is treated as synchronous and fails if async loading is required.
+     *
+     * @param context The Context to get Resources from
+     * @param id The Resource id to load
+     * @param value A TypedValue to use in the fetching
+     * @param style The font style to load
+     * @param fontCallback A callback to trigger when the font is fetched or an error occurs
+     * @param handler A handler to the thread the callback should be called on
+     * @param isRequestFromLayoutInflator Whether this request originated from XML. This is used to
+     *                     determine if we use or ignore the fontProviderFetchStrategy attribute in
+     *                     font provider XML fonts.
      */
     private static Typeface loadFont(
             @NonNull Context context, Resources wrapper, TypedValue value, int id, int style,
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequest.java b/core/core/src/main/java/androidx/core/provider/FontRequest.java
index 4b692c3..31ffd70 100644
--- a/core/core/src/main/java/androidx/core/provider/FontRequest.java
+++ b/core/core/src/main/java/androidx/core/provider/FontRequest.java
@@ -60,8 +60,7 @@
         mQuery = Preconditions.checkNotNull(query);
         mCertificates = Preconditions.checkNotNull(certificates);
         mCertificatesArray = 0;
-        mIdentifier = new StringBuilder(mProviderAuthority).append("-").append(mProviderPackage)
-                .append("-").append(mQuery).toString();
+        mIdentifier = createIdentifier(providerAuthority, providerPackage, query);
     }
 
     /**
@@ -83,8 +82,16 @@
         mCertificates = null;
         Preconditions.checkArgument(certificates != 0);
         mCertificatesArray = certificates;
-        mIdentifier = new StringBuilder(mProviderAuthority).append("-").append(mProviderPackage)
-                .append("-").append(mQuery).toString();
+        mIdentifier = createIdentifier(providerAuthority, providerPackage, query);
+    }
+
+    private String createIdentifier(
+            @NonNull String providerAuthority,
+            @NonNull String providerPackage,
+            @NonNull String query
+    ) {
+        return new StringBuilder(providerAuthority).append("-").append(providerPackage)
+                .append("-").append(query).toString();
     }
 
     /**
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequestThreadPool.java b/core/core/src/main/java/androidx/core/provider/FontRequestThreadPool.java
index 74e0970..fddad3c 100644
--- a/core/core/src/main/java/androidx/core/provider/FontRequestThreadPool.java
+++ b/core/core/src/main/java/androidx/core/provider/FontRequestThreadPool.java
@@ -16,7 +16,6 @@
 
 package androidx.core.provider;
 
-import android.os.Handler;
 import android.os.Process;
 
 import androidx.annotation.IntRange;
@@ -75,7 +74,6 @@
             @NonNull final Callable<T> callable,
             @NonNull final ReplyCallback<T> callback
     ) {
-        final Handler calleeHandler = CalleeHandler.create();
         mExecutor.execute(new Runnable() {
             @Override
             public void run() {
@@ -86,13 +84,7 @@
                     t = null;
                 }
                 final T result = t;
-
-                calleeHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        callback.onReply(result);
-                    }
-                });
+                callback.onReply(result);
             }
         });
     }
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
index e57f6c55..9d9ecbd 100644
--- a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
+++ b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
@@ -16,12 +16,14 @@
 
 package androidx.core.provider;
 
+import static androidx.core.provider.FontsContractCompat.FontFamilyResult.STATUS_OK;
 import static androidx.core.provider.FontsContractCompat.FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED;
 import static androidx.core.provider.FontsContractCompat.FontFamilyResult.STATUS_WRONG_CERTIFICATES;
 import static androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR;
 import static androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND;
 import static androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND;
 import static androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES;
+import static androidx.core.provider.FontsContractCompat.FontRequestCallback.RESULT_SUCCESS;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -78,10 +80,17 @@
             final @NonNull Handler handler
     ) {
         final Handler callerHandler = CalleeHandler.create();
+        final int defaultStyle = Typeface.NORMAL;
+
+        final String id = createRequestId(request, defaultStyle);
+        Typeface cached = sTypefaceCache.get(id);
+        if (cached != null) {
+            notifyRetrieved(callerHandler, callback, cached);
+        }
+
         handler.post(new Runnable() {
             @Override
             public void run() {
-                // TODO: Cache the result.
                 FontsContractCompat.FontFamilyResult result;
                 try {
                     result = FontProvider.getFontFamilyResult(appContext, request, null);
@@ -90,7 +99,7 @@
                     return;
                 }
 
-                if (result.getStatusCode() != FontsContractCompat.FontFamilyResult.STATUS_OK) {
+                if (result.getStatusCode() != STATUS_OK) {
                     switch (result.getStatusCode()) {
                         case STATUS_WRONG_CERTIFICATES:
                             notifyFailed(callerHandler, callback, FAIL_REASON_WRONG_CERTIFICATES);
@@ -129,7 +138,7 @@
                 final Typeface typeface = TypefaceCompat.createFromFontInfo(appContext,
                         null /* cancellationSignal */,
                         fonts,
-                        Typeface.NORMAL
+                        defaultStyle
                 );
 
                 if (typeface == null) {
@@ -139,6 +148,9 @@
                     return;
                 }
 
+                if (typeface != null) {
+                    sTypefaceCache.put(id, typeface);
+                }
                 notifyRetrieved(callerHandler, callback, typeface);
             }
         });
@@ -170,13 +182,23 @@
         });
     }
 
+    /**
+     * Used by TypefaceCompat and tests.
+     *
+     * @param context         Context
+     * @param request         FontRequest that defines the font to be loaded.
+     * @param fontCallback    the callback to be called for async loading
+     * @param handler         the Handler that the callback will be called on.
+     * @param isBlockingFetch when boolean the call will be synchronous
+     * @return Typeface the resulting Typeface if it is not an asynch request.
+     */
     static Typeface getTypeface(
             @NonNull final Context context,
             @NonNull final FontRequest request,
             @Nullable final ResourcesCompat.FontCallback fontCallback,
             @Nullable final Handler handler, boolean isBlockingFetch, int timeout,
             final int style) {
-        final String id = request.getId() + "-" + style;
+        final String id = createRequestId(request, style);
         Typeface cached = sTypefaceCache.get(id);
         if (cached != null) {
             if (fontCallback != null) {
@@ -185,26 +207,25 @@
             return cached;
         }
 
+        // when timeout is infinite, do not post to bg thread, since it will block other requests
         if (isBlockingFetch && timeout == FontResourcesParserCompat.INFINITE_TIMEOUT_VALUE) {
             // Wait forever. No need to post to the thread.
-            TypefaceResult typefaceResult = getFontInternal(context, request, style);
+            TypefaceResult typefaceResult = getFontInternal(id, context, request, style);
+            Typeface typeface = typefaceResult.mTypeface;
             if (fontCallback != null) {
-                if (typefaceResult.mResult == FontsContractCompat.FontFamilyResult.STATUS_OK) {
-                    fontCallback.callbackSuccessAsync(typefaceResult.mTypeface, handler);
+                if (typefaceResult.mResult == STATUS_OK) {
+                    fontCallback.callbackSuccessAsync(typeface, handler);
                 } else {
                     fontCallback.callbackFailAsync(typefaceResult.mResult, handler);
                 }
             }
-            return typefaceResult.mTypeface;
+            return typeface;
         }
 
         final Callable<TypefaceResult> fetcher = new Callable<TypefaceResult>() {
             @Override
             public TypefaceResult call() {
-                TypefaceResult typeface = getFontInternal(context, request, style);
-                if (typeface.mTypeface != null) {
-                    sTypefaceCache.put(id, typeface.mTypeface);
-                }
+                TypefaceResult typeface = getFontInternal(id, context, request, style);
                 return typeface;
             }
         };
@@ -223,8 +244,7 @@
                     if (typeface == null) {
                         fontCallback.callbackFailAsync(
                                 FAIL_REASON_FONT_NOT_FOUND, handler);
-                    } else if (typeface.mResult
-                            == FontsContractCompat.FontFamilyResult.STATUS_OK) {
+                    } else if (typeface.mResult == STATUS_OK) {
                         fontCallback.callbackSuccessAsync(typeface.mTypeface, handler);
                     } else {
                         fontCallback.callbackFailAsync(typeface.mResult, handler);
@@ -268,29 +288,40 @@
         }
     }
 
+    private static String createRequestId(@NonNull FontRequest request, int style) {
+        return request.getId() + "-" + style;
+    }
+
     /** Package protected to prevent synthetic accessor */
     @SuppressLint("WrongConstant")
     @NonNull
     static TypefaceResult getFontInternal(
+            @NonNull final String cacheId,
             @NonNull final Context context,
             @NonNull final FontRequest request,
-            int style) {
+            int style
+    ) {
         FontsContractCompat.FontFamilyResult result;
         try {
             result = FontProvider.getFontFamilyResult(context, request, null);
         } catch (PackageManager.NameNotFoundException e) {
             return new TypefaceResult(null, FAIL_REASON_PROVIDER_NOT_FOUND);
         }
-        if (result.getStatusCode() == FontsContractCompat.FontFamilyResult.STATUS_OK) {
+        if (result.getStatusCode() == STATUS_OK) {
             final Typeface typeface = TypefaceCompat.createFromFontInfo(
                     context, null /* CancellationSignal */, result.getFonts(), style);
+            if (typeface != null) {
+                sTypefaceCache.put(cacheId, typeface);
+            }
+
             return new TypefaceResult(typeface, typeface != null
-                    ? FontsContractCompat.FontRequestCallback.RESULT_SUCCESS
+                    ? RESULT_SUCCESS
                     : FAIL_REASON_FONT_LOAD_ERROR);
         }
         int resultCode = result.getStatusCode() == STATUS_WRONG_CERTIFICATES
                 ? FAIL_REASON_WRONG_CERTIFICATES
                 : FAIL_REASON_FONT_LOAD_ERROR;
+
         return new TypefaceResult(null, resultCode);
     }
 
diff --git a/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java b/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
index a878fd3..1d30889 100644
--- a/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
+++ b/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
@@ -53,7 +53,6 @@
 public class FontsContractCompat {
     private FontsContractCompat() { }
 
-    // TODO deprecated from here, move to TypefaceCompat
     /**
      * Build a Typeface from an array of {@link FontInfo}
      *
@@ -124,6 +123,16 @@
 
     /**
      * Used by TypefaceCompat and tests.
+     * @param context Context
+     * @param request FontRequest that defines the font to be loaded.
+     * @param fontCallback the callback to be called for async loading
+     * @param handler the Handler that the callback will be called on.
+     * @param isBlockingFetch when boolean the call will be synchronous
+     * @param timeout timeout the timeout for blocking requests
+     * @param style Typeface Style such as NORMAL, BOLD, ITALIC, BOLD_ITALIC
+     *
+     * @return Typeface the resulting Typeface if it is not an asynch request.
+     *
      * @hide
      */
     @RestrictTo(LIBRARY)
diff --git a/datastore/datastore-core/api/current.txt b/datastore/datastore-core/api/current.txt
index d0bb6a8..0b1e88c 100644
--- a/datastore/datastore-core/api/current.txt
+++ b/datastore/datastore-core/api/current.txt
@@ -27,8 +27,8 @@
 
   public interface Serializer<T> {
     method public T! getDefaultValue();
-    method public T! readFrom(java.io.InputStream input);
-    method public void writeTo(T? t, java.io.OutputStream output);
+    method public suspend Object? readFrom(java.io.InputStream input, kotlin.coroutines.Continuation<? super T> p);
+    method public suspend Object? writeTo(T? t, java.io.OutputStream output, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     property public abstract T! defaultValue;
   }
 
diff --git a/datastore/datastore-core/api/public_plus_experimental_current.txt b/datastore/datastore-core/api/public_plus_experimental_current.txt
index d0bb6a8..0b1e88c 100644
--- a/datastore/datastore-core/api/public_plus_experimental_current.txt
+++ b/datastore/datastore-core/api/public_plus_experimental_current.txt
@@ -27,8 +27,8 @@
 
   public interface Serializer<T> {
     method public T! getDefaultValue();
-    method public T! readFrom(java.io.InputStream input);
-    method public void writeTo(T? t, java.io.OutputStream output);
+    method public suspend Object? readFrom(java.io.InputStream input, kotlin.coroutines.Continuation<? super T> p);
+    method public suspend Object? writeTo(T? t, java.io.OutputStream output, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     property public abstract T! defaultValue;
   }
 
diff --git a/datastore/datastore-core/api/restricted_current.txt b/datastore/datastore-core/api/restricted_current.txt
index d0bb6a8..0b1e88c 100644
--- a/datastore/datastore-core/api/restricted_current.txt
+++ b/datastore/datastore-core/api/restricted_current.txt
@@ -27,8 +27,8 @@
 
   public interface Serializer<T> {
     method public T! getDefaultValue();
-    method public T! readFrom(java.io.InputStream input);
-    method public void writeTo(T? t, java.io.OutputStream output);
+    method public suspend Object? readFrom(java.io.InputStream input, kotlin.coroutines.Continuation<? super T> p);
+    method public suspend Object? writeTo(T? t, java.io.OutputStream output, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     property public abstract T! defaultValue;
   }
 
diff --git a/datastore/datastore-core/src/main/java/androidx/datastore/core/Serializer.kt b/datastore/datastore-core/src/main/java/androidx/datastore/core/Serializer.kt
index fdcda14..74ae35c 100644
--- a/datastore/datastore-core/src/main/java/androidx/datastore/core/Serializer.kt
+++ b/datastore/datastore-core/src/main/java/androidx/datastore/core/Serializer.kt
@@ -39,7 +39,7 @@
      *
      * @param input the InputStream with the data to deserialize
      */
-    public fun readFrom(input: InputStream): T
+    public suspend fun readFrom(input: InputStream): T
 
     /**
      *  Marshal object to a stream. Closing the provided OutputStream is a no-op.
@@ -47,7 +47,7 @@
      *  @param t the data to write to output
      *  @output the OutputStream to serialize data to
      */
-    public fun writeTo(t: T, output: OutputStream)
+    public suspend fun writeTo(t: T, output: OutputStream)
 }
 
 /**
diff --git a/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt b/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt
index 65f5551..bded0c8 100644
--- a/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt
+++ b/datastore/datastore-core/src/main/java/androidx/datastore/core/SingleProcessDataStore.kt
@@ -417,7 +417,7 @@
      * Internal only to prevent creation of synthetic accessor function. Do not call this from
      * outside this class.
      */
-    internal fun writeData(newData: T) {
+    internal suspend fun writeData(newData: T) {
         file.createParentDirectories()
 
         val scratchFile = File(file.absolutePath + SCRATCH_SUFFIX)
diff --git a/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreStressTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreStressTest.kt
index 2e6a069..182b8c1 100644
--- a/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreStressTest.kt
+++ b/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreStressTest.kt
@@ -234,14 +234,14 @@
         Serializer<Long> {
         override val defaultValue = 0L
 
-        override fun readFrom(input: InputStream): Long {
+        override suspend fun readFrom(input: InputStream): Long {
             if (failReads) {
                 throw IOException("failing read")
             }
             return DataInputStream(input).readLong()
         }
 
-        override fun writeTo(t: Long, output: OutputStream) {
+        override suspend fun writeTo(t: Long, output: OutputStream) {
             if (failWrites) {
                 throw IOException("failing write")
             }
diff --git a/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt
index a0f58ba4..33f6f74 100644
--- a/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt
+++ b/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreTest.kt
@@ -757,7 +757,7 @@
     fun testClosingOutputStreamDoesntCloseUnderlyingStream() = runBlockingTest {
         val delegate = TestingSerializer()
         val serializer = object : Serializer<Byte> by delegate {
-            override fun writeTo(t: Byte, output: OutputStream) {
+            override suspend fun writeTo(t: Byte, output: OutputStream) {
                 delegate.writeTo(t, output)
                 output.close() // This will be a no-op so the fd.sync() call will succeed.
             }
@@ -898,11 +898,11 @@
 
             override val defaultValue = ByteWrapper(delegate.defaultValue)
 
-            override fun readFrom(input: InputStream): ByteWrapper {
+            override suspend fun readFrom(input: InputStream): ByteWrapper {
                 return ByteWrapper(delegate.readFrom(input))
             }
 
-            override fun writeTo(t: ByteWrapper, output: OutputStream) {
+            override suspend fun writeTo(t: ByteWrapper, output: OutputStream) {
                 delegate.writeTo(t.byte, output)
             }
         }
diff --git a/datastore/datastore-core/src/test/java/androidx/datastore/core/TestingSerializer.kt b/datastore/datastore-core/src/test/java/androidx/datastore/core/TestingSerializer.kt
index 9ccfe72..3aea9d7 100644
--- a/datastore/datastore-core/src/test/java/androidx/datastore/core/TestingSerializer.kt
+++ b/datastore/datastore-core/src/test/java/androidx/datastore/core/TestingSerializer.kt
@@ -26,7 +26,7 @@
     @Volatile var failingWrite: Boolean = false,
     override val defaultValue: Byte = 0
 ) : Serializer<Byte> {
-    override fun readFrom(input: InputStream): Byte {
+    override suspend fun readFrom(input: InputStream): Byte {
         if (failReadWithCorruptionException) {
             throw CorruptionException(
                 "CorruptionException",
@@ -45,7 +45,7 @@
         return read.toByte()
     }
 
-    override fun writeTo(t: Byte, output: OutputStream) {
+    override suspend fun writeTo(t: Byte, output: OutputStream) {
         if (failingWrite) {
             throw IOException("I was asked to fail on writes")
         }
diff --git a/datastore/datastore-preferences-core/src/main/java/androidx/datastore/preferences/core/PreferencesSerializer.kt b/datastore/datastore-preferences-core/src/main/java/androidx/datastore/preferences/core/PreferencesSerializer.kt
index 00429d6..bd0a531 100644
--- a/datastore/datastore-preferences-core/src/main/java/androidx/datastore/preferences/core/PreferencesSerializer.kt
+++ b/datastore/datastore-preferences-core/src/main/java/androidx/datastore/preferences/core/PreferencesSerializer.kt
@@ -42,7 +42,7 @@
         }
 
     @Throws(IOException::class, CorruptionException::class)
-    override fun readFrom(input: InputStream): Preferences {
+    override suspend fun readFrom(input: InputStream): Preferences {
         val preferencesProto = PreferencesMapCompat.readFrom(input)
 
         val mutablePreferences = mutablePreferencesOf()
@@ -55,7 +55,7 @@
     }
 
     @Throws(IOException::class, CorruptionException::class)
-    override fun writeTo(t: Preferences, output: OutputStream) {
+    override suspend fun writeTo(t: Preferences, output: OutputStream) {
         val preferences = t.asMap()
         val protoBuilder = PreferenceMap.newBuilder()
 
diff --git a/datastore/datastore-preferences-core/src/test/java/androidx/datastore/preferences/core/PreferencesSerializerTest.kt b/datastore/datastore-preferences-core/src/test/java/androidx/datastore/preferences/core/PreferencesSerializerTest.kt
index a4b2dbf..b181c0f 100644
--- a/datastore/datastore-preferences-core/src/test/java/androidx/datastore/preferences/core/PreferencesSerializerTest.kt
+++ b/datastore/datastore-preferences-core/src/test/java/androidx/datastore/preferences/core/PreferencesSerializerTest.kt
@@ -17,6 +17,7 @@
 package androidx.datastore.preferences.core
 
 import androidx.datastore.core.CorruptionException
+import kotlinx.coroutines.test.runBlockingTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -42,7 +43,7 @@
     }
 
     @Test
-    fun testWriteAndReadString() {
+    fun testWriteAndReadString() = runBlockingTest {
         val stringKey = stringPreferencesKey("string_key")
 
         val prefs = preferencesOf(
@@ -61,7 +62,7 @@
     }
 
     @Test
-    fun testWriteAndReadStringSet() {
+    fun testWriteAndReadStringSet() = runBlockingTest {
         val stringSetKey =
             stringSetPreferencesKey("string_set_key")
 
@@ -81,7 +82,7 @@
     }
 
     @Test
-    fun testWriteAndReadLong() {
+    fun testWriteAndReadLong() = runBlockingTest {
         val longKey = longPreferencesKey("long_key")
 
         val prefs = preferencesOf(
@@ -100,7 +101,7 @@
     }
 
     @Test
-    fun testWriteAndReadInt() {
+    fun testWriteAndReadInt() = runBlockingTest {
         val intKey = intPreferencesKey("int_key")
 
         val prefs = preferencesOf(
@@ -119,7 +120,7 @@
     }
 
     @Test
-    fun testWriteAndReadBoolean() {
+    fun testWriteAndReadBoolean() = runBlockingTest {
         val booleanKey = booleanPreferencesKey("boolean_key")
 
         val prefs = preferencesOf(
@@ -138,7 +139,7 @@
     }
 
     @Test
-    fun testWriteAndReadFloat() {
+    fun testWriteAndReadFloat() = runBlockingTest {
         val floatKey = floatPreferencesKey("float_key")
 
         val prefs = preferencesOf(
@@ -157,7 +158,7 @@
     }
 
     @Test
-    fun testWriteAndReadDouble() {
+    fun testWriteAndReadDouble() = runBlockingTest {
         val maxDouble = doublePreferencesKey("max_double_key")
         val minDouble = doublePreferencesKey("min_double_key")
 
@@ -178,7 +179,7 @@
     }
 
     @Test
-    fun testThrowsCorruptionException() {
+    fun testThrowsCorruptionException() = runBlockingTest {
         // Not a valid proto - protos cannot start with a 0 byte.
         testFile.writeBytes(byteArrayOf(0, 1, 2, 3, 4))
 
diff --git a/datastore/datastore-proto/build.gradle b/datastore/datastore-proto/build.gradle
index 83313e5..fd54e14 100644
--- a/datastore/datastore-proto/build.gradle
+++ b/datastore/datastore-proto/build.gradle
@@ -32,10 +32,12 @@
 dependencies {
     api(project(":datastore:datastore-core"))
     api(PROTOBUF_LITE)
+    implementation(KOTLIN_COROUTINES_CORE)
 
     testImplementation(TRUTH)
     testImplementation(JUNIT)
     testImplementation(project(":internal-testutils-truth"))
+    testImplementation(KOTLIN_COROUTINES_TEST)
 }
 
 protobuf {
diff --git a/datastore/datastore-proto/src/main/java/androidx/datastore/protos/ProtoSerializer.kt b/datastore/datastore-proto/src/main/java/androidx/datastore/protos/ProtoSerializer.kt
index d164822..eb25438 100644
--- a/datastore/datastore-proto/src/main/java/androidx/datastore/protos/ProtoSerializer.kt
+++ b/datastore/datastore-proto/src/main/java/androidx/datastore/protos/ProtoSerializer.kt
@@ -36,7 +36,7 @@
 ) : Serializer<T> {
 
     @Suppress("UNCHECKED_CAST")
-    override fun readFrom(input: InputStream): T {
+    override suspend fun readFrom(input: InputStream): T {
         try {
             return defaultValue.parserForType.parseFrom(input, extensionRegistryLite) as T
         } catch (invalidProtocolBufferException: InvalidProtocolBufferException) {
@@ -46,7 +46,7 @@
         }
     }
 
-    override fun writeTo(t: T, output: OutputStream) {
+    override suspend fun writeTo(t: T, output: OutputStream) {
         t.writeTo(output)
     }
 }
\ No newline at end of file
diff --git a/datastore/datastore-proto/src/test/java/androidx/datastore/protos/ProtoSerializerTest.kt b/datastore/datastore-proto/src/test/java/androidx/datastore/protos/ProtoSerializerTest.kt
index aec23de6..0e05782 100644
--- a/datastore/datastore-proto/src/test/java/androidx/datastore/protos/ProtoSerializerTest.kt
+++ b/datastore/datastore-proto/src/test/java/androidx/datastore/protos/ProtoSerializerTest.kt
@@ -24,18 +24,21 @@
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
 import com.google.protobuf.ExtensionRegistryLite
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runBlockingTest
 import org.junit.Rule
 import org.junit.rules.TemporaryFolder
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
+@ExperimentalCoroutinesApi
 class ProtoSerializerTest {
     @get:Rule
     val temporaryFolder = TemporaryFolder()
 
     @Test
-    fun testReadWriteProto() {
+    fun testReadWriteProto() = runBlockingTest {
         val file = temporaryFolder.newFile("test_file.pb")
 
         val fooProtoWithText = FooProto.newBuilder().setText("abc").build()
@@ -55,7 +58,7 @@
     }
 
     @Test
-    fun testReadWriteProtoWithExtension() {
+    fun testReadWriteProtoWithExtension() = runBlockingTest {
         val file = temporaryFolder.newFile("test_file.pb")
 
         val registry = ExtensionRegistryLite.newInstance()
@@ -80,7 +83,7 @@
     }
 
     @Test
-    fun testThrowsCorruptionException() {
+    fun testThrowsCorruptionException() = runBlockingTest {
         val file = temporaryFolder.newFile("test_file.pb")
         file.writeBytes(byteArrayOf(0x00, 0x02)) // Protos cannot start with 0x00.
 
diff --git a/datastore/datastore-rxjava2/src/test-common/java/androidx/datastore/rxjava2/TestingSerializer.kt b/datastore/datastore-rxjava2/src/test-common/java/androidx/datastore/rxjava2/TestingSerializer.kt
index 77a9ab6..2dedd9a 100644
--- a/datastore/datastore-rxjava2/src/test-common/java/androidx/datastore/rxjava2/TestingSerializer.kt
+++ b/datastore/datastore-rxjava2/src/test-common/java/androidx/datastore/rxjava2/TestingSerializer.kt
@@ -27,7 +27,7 @@
     @Volatile var failingRead: Boolean = false,
     @Volatile var failingWrite: Boolean = false
 ) : Serializer<Byte> {
-    override fun readFrom(input: InputStream): Byte {
+    override suspend fun readFrom(input: InputStream): Byte {
         if (failReadWithCorruptionException) {
             throw CorruptionException(
                 "CorruptionException",
@@ -46,7 +46,7 @@
         return read.toByte()
     }
 
-    override fun writeTo(t: Byte, output: OutputStream) {
+    override suspend fun writeTo(t: Byte, output: OutputStream) {
         if (failingWrite) {
             throw IOException("I was asked to fail on writes")
         }
diff --git a/datastore/datastore-rxjava3/src/test-common/java/androidx/datastore/rxjava3/TestingSerializer.kt b/datastore/datastore-rxjava3/src/test-common/java/androidx/datastore/rxjava3/TestingSerializer.kt
index f1e02bb..5a51410 100644
--- a/datastore/datastore-rxjava3/src/test-common/java/androidx/datastore/rxjava3/TestingSerializer.kt
+++ b/datastore/datastore-rxjava3/src/test-common/java/androidx/datastore/rxjava3/TestingSerializer.kt
@@ -27,7 +27,7 @@
     @Volatile var failingRead: Boolean = false,
     @Volatile var failingWrite: Boolean = false
 ) : Serializer<Byte> {
-    override fun readFrom(input: InputStream): Byte {
+    override suspend fun readFrom(input: InputStream): Byte {
         if (failReadWithCorruptionException) {
             throw CorruptionException(
                 "CorruptionException",
@@ -46,7 +46,7 @@
         return read.toByte()
     }
 
-    override fun writeTo(t: Byte, output: OutputStream) {
+    override suspend fun writeTo(t: Byte, output: OutputStream) {
         if (failingWrite) {
             throw IOException("I was asked to fail on writes")
         }
diff --git a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/KotlinSerializationActivity.kt b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/KotlinSerializationActivity.kt
index 0228738..1569be7 100644
--- a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/KotlinSerializationActivity.kt
+++ b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/KotlinSerializationActivity.kt
@@ -119,7 +119,7 @@
     override val defaultValue: MySettings
         get() = MySettings()
 
-    override fun readFrom(input: InputStream): MySettings {
+    override suspend fun readFrom(input: InputStream): MySettings {
         try {
             return Json.decodeFromString<MySettings>(input.readBytes().decodeToString())
         } catch (serialization: SerializationException) {
@@ -127,7 +127,7 @@
         }
     }
 
-    override fun writeTo(t: MySettings, output: OutputStream) {
+    override suspend fun writeTo(t: MySettings, output: OutputStream) {
         output.write(Json.encodeToString(t).encodeToByteArray())
     }
 }
\ No newline at end of file
diff --git a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt
index 3d30e1a..1956c6b 100644
--- a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt
+++ b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt
@@ -109,7 +109,7 @@
 
         override val defaultValue: Settings = Settings.getDefaultInstance()
 
-        override fun readFrom(input: InputStream): Settings {
+        override suspend fun readFrom(input: InputStream): Settings {
             try {
                 return Settings.parseFrom(input)
             } catch (ipbe: InvalidProtocolBufferException) {
@@ -117,6 +117,6 @@
             }
         }
 
-        override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
+        override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
     }
 }
\ No newline at end of file
diff --git a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
index 91c8edd..4e31b91 100644
--- a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
+++ b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
@@ -149,7 +149,7 @@
 private object SettingsSerializer : Serializer<Settings> {
     override val defaultValue: Settings = Settings.getDefaultInstance()
 
-    override fun readFrom(input: InputStream): Settings {
+    override suspend fun readFrom(input: InputStream): Settings {
         try {
             return Settings.parseFrom(input)
         } catch (ipbe: InvalidProtocolBufferException) {
@@ -157,5 +157,5 @@
         }
     }
 
-    override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
+    override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
 }
\ No newline at end of file
diff --git a/datastore/datastore/src/androidTest/java/androidx/datastore/TestingSerializer.kt b/datastore/datastore/src/androidTest/java/androidx/datastore/TestingSerializer.kt
index 2448889..ffc73f3 100644
--- a/datastore/datastore/src/androidTest/java/androidx/datastore/TestingSerializer.kt
+++ b/datastore/datastore/src/androidTest/java/androidx/datastore/TestingSerializer.kt
@@ -29,7 +29,7 @@
 ) : Serializer<Byte> {
     override val defaultValue: Byte = 0
 
-    override fun readFrom(input: InputStream): Byte {
+    override suspend fun readFrom(input: InputStream): Byte {
         if (failReadWithCorruptionException) {
             throw CorruptionException(
                 "CorruptionException",
@@ -48,7 +48,7 @@
         return read.toByte()
     }
 
-    override fun writeTo(t: Byte, output: OutputStream) {
+    override suspend fun writeTo(t: Byte, output: OutputStream) {
         if (failingWrite) {
             throw IOException("I was asked to fail on writes")
         }
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
index 338ceac..f674bbd 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
@@ -30,8 +30,10 @@
 import androidx.fragment.test.R
 import androidx.lifecycle.ViewModelStore
 import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
 import org.junit.Test
@@ -109,8 +111,8 @@
     }
 
     @Test
-    fun testInflatedDialogFragmentShowsNow() {
-        val fragment = InflatedDialogFragment()
+    fun testInflatedFragmentTagDialogFragmentShowsNow() {
+        val fragment = InflatedDialogFragment(false)
 
         activityTestRule.runOnUiThread {
             fragment.showNow(activityTestRule.activity.supportFragmentManager, null)
@@ -121,6 +123,32 @@
             .isTrue()
     }
 
+    @Test
+    fun testInflatedFragmentContainerViewDialogFragmentShowsNow() {
+        with(ActivityScenario.launch(EmptyFragmentTestActivity::class.java)) {
+            val fragment = InflatedDialogFragment()
+
+            withActivity {
+                fragment.showNow(supportFragmentManager, "fragment1")
+            }
+
+            assertWithMessage("Dialog was not being shown")
+                .that(fragment.dialog?.isShowing)
+                .isTrue()
+
+            recreate()
+
+            val restoredFragment = withActivity {
+                val fm = supportFragmentManager
+                fm.findFragmentByTag("fragment1") as InflatedDialogFragment
+            }
+
+            assertWithMessage("Dialog was not being shown")
+                .that(restoredFragment.dialog?.isShowing)
+                .isTrue()
+        }
+    }
+
     @UiThreadTest
     @Test
     fun testDialogFragmentWithChild() {
@@ -423,9 +451,17 @@
         }
     }
 
-    class InflatedDialogFragment : DialogFragment() {
+    class InflatedDialogFragment(val useFragmentContainerView: Boolean = true) : DialogFragment() {
         override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
-            val view = layoutInflater.inflate(R.layout.inflated_fragment_tag, null, false)
+            val view = if (useFragmentContainerView) {
+                layoutInflater.inflate(
+                    R.layout.inflated_fragment_container_view_no_parent,
+                    null,
+                    false
+                )
+            } else {
+                layoutInflater.inflate(R.layout.inflated_fragment_tag, null, false)
+            }
             return AlertDialog.Builder(context)
                 .setTitle("Test")
                 .setMessage("Message")
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentFinishEarlyTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentFinishEarlyTest.kt
index a3f9af5..68ae0531 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentFinishEarlyTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentFinishEarlyTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.fragment.app
 
-import android.os.Build
 import android.os.Bundle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -43,7 +42,7 @@
     /**
      * FragmentActivity should not raise the state of a Fragment while it is being destroyed.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
+    @SdkSuppress(minSdkVersion = 24) // this is failing remotely for API 23 devices b/178692379
     @Test
     fun fragmentActivityFinishEarly() {
         val activity = activityRule.launchActivity(null)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
index 42f6704..3ffa1bb 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
@@ -1371,7 +1371,7 @@
     }
 
     @Test
-    fun inflatedFragmentAfterResume() {
+    fun inflatedFragmentTagAfterResume() {
         with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
             val fragment = withActivity {
                 setContentView(R.layout.activity_inflated_fragment)
@@ -1385,6 +1385,77 @@
     }
 
     @Test
+    fun inflatedFragmentContainerViewAfterResume() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            var fragment = withActivity {
+                setContentView(R.layout.inflated_fragment_container_view)
+                val fm = supportFragmentManager
+                fm.findFragmentById(R.id.fragment_container_view) as InflatedFragment
+            }
+
+            assertThat(fragment).isNotNull()
+            assertThat(fragment.isResumed).isTrue()
+
+            recreate()
+
+            fragment = withActivity {
+                setContentView(R.layout.inflated_fragment_container_view)
+                val fm = supportFragmentManager
+                fm.findFragmentById(R.id.fragment_container_view) as InflatedFragment
+            }
+
+            assertThat(fragment).isNotNull()
+            assertThat(fragment.requireView().parent).isNotNull()
+            assertThat(fragment.isResumed).isTrue()
+        }
+    }
+
+    @Test
+    fun inflatedFragmentContainerViewWithMultipleFragmentsAfterResume() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val addedFragment1 = StrictViewFragment()
+            val addedFragment2 = StrictViewFragment()
+            var fragment = withActivity {
+                setContentView(R.layout.inflated_fragment_container_view)
+                val fm = supportFragmentManager
+                fm.beginTransaction()
+                    .add(R.id.fragment_container_view, addedFragment1, "addedFragment1")
+                    .add(R.id.fragment_container_view, addedFragment2, "addedFragment2")
+                    .commitNow()
+                fm.findFragmentByTag("fragment1") as InflatedFragment
+            }
+
+            assertThat(fragment).isNotNull()
+            assertThat(fragment.isResumed).isTrue()
+            assertThat(addedFragment1.isResumed).isTrue()
+            assertThat(addedFragment2.isResumed).isTrue()
+
+            recreate()
+
+            val fm = withActivity {
+                setContentView(R.layout.inflated_fragment_container_view)
+                supportFragmentManager
+            }
+
+            fragment = fm.findFragmentByTag("fragment1") as InflatedFragment
+            val restoredAddedFragment1 =
+                fm.findFragmentByTag("addedFragment1") as StrictViewFragment
+            val restoredAddedFragment2 =
+                fm.findFragmentByTag("addedFragment2") as StrictViewFragment
+
+            assertThat(fragment).isNotNull()
+
+            assertThat(fragment.requireView().parent).isNotNull()
+            assertThat(restoredAddedFragment1.requireView().parent).isNotNull()
+            assertThat(restoredAddedFragment2.requireView().parent).isNotNull()
+
+            assertThat(fragment.isResumed).isTrue()
+            assertThat(restoredAddedFragment1.isResumed).isTrue()
+            assertThat(restoredAddedFragment2.isResumed).isTrue()
+        }
+    }
+
+    @Test
     @UiThreadTest
     fun testReplaceChildFragmentInViewCreated() {
         val viewModelStore = ViewModelStore()
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
index 256a07b..21201eb 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
@@ -60,9 +60,12 @@
         checkGetActivity()
         checkActivityNotDestroyed()
         checkState("onViewStateRestored", State.ACTIVITY_CREATED)
-        assertWithMessage("Fragment should have a view parent")
-            .that(requireView().parent)
-            .isNotNull()
+        // Restored fragments can get to this point without being attached b/149024125
+        if (!mRestored) {
+            assertWithMessage("Fragment should have a view parent")
+                .that(requireView().parent)
+                .isNotNull()
+        }
     }
 
     override fun onDestroyView() {
diff --git a/fragment/fragment/src/androidTest/res/layout/inflated_fragment_container_view_no_parent.xml b/fragment/fragment/src/androidTest/res/layout/inflated_fragment_container_view_no_parent.xml
new file mode 100644
index 0000000..f5096d6
--- /dev/null
+++ b/fragment/fragment/src/androidTest/res/layout/inflated_fragment_container_view_no_parent.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fragment_container_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:name="androidx.fragment.app.InflatedFragment"
+    android:tag="fragment1"/>
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
index 3e30902..27a1dfd 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
@@ -175,6 +175,7 @@
                     .add(this, containerFragment, tag)
                     .commitNowAllowingStateLoss();
         }
+        fm.onContainerAvailable(this);
     }
 
     /**
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 6b62941..c0f196d 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1037,6 +1037,19 @@
         return null;
     }
 
+    void onContainerAvailable(@NonNull FragmentContainerView container) {
+        for (FragmentStateManager fragmentStateManager:
+                mFragmentStore.getActiveFragmentStateManagers()) {
+            Fragment fragment = fragmentStateManager.getFragment();
+            if (fragment.mContainerId == container.getId() && fragment.mView != null
+                    && fragment.mView.getParent() == null
+            ) {
+                fragment.mContainer = container;
+                fragmentStateManager.addViewToContainer();
+            }
+        }
+    }
+
     /**
      * Recurse up the view hierarchy, looking for a FragmentManager
      *
diff --git a/paging/paging-compose/OWNERS b/paging/paging-compose/OWNERS
index 870a17c..64e60ba 100644
--- a/paging/paging-compose/OWNERS
+++ b/paging/paging-compose/OWNERS
@@ -1,2 +1 @@
 andreykulikov@google.com
-mburlaciuc@google.com
\ No newline at end of file
diff --git a/room/common/src/main/java/androidx/room/AutoMigration.java b/room/common/src/main/java/androidx/room/AutoMigration.java
new file mode 100644
index 0000000..4c9bc18
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/AutoMigration.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Automatic migration strategy for Room databases.
+ *
+ * @hide
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public @interface AutoMigration {
+    /**
+     * Version of the original database schema to migrate from.
+     *
+     * @return Version number of the original database schema.
+     */
+    int from();
+
+    /**
+     * Version of the new database schema to migrate to.
+     *
+     * @return Version number of the new database schema.
+     */
+    int to();
+}
diff --git a/room/common/src/main/java/androidx/room/Database.java b/room/common/src/main/java/androidx/room/Database.java
index f7a0f6f..2659bc8 100644
--- a/room/common/src/main/java/androidx/room/Database.java
+++ b/room/common/src/main/java/androidx/room/Database.java
@@ -16,6 +16,8 @@
 
 package androidx.room;
 
+import androidx.annotation.RestrictTo;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -101,4 +103,14 @@
      * {@code room.schemaLocation} argument is set. Defaults to {@code true}.
      */
     boolean exportSchema() default true;
+
+
+    /**
+     * List of AutoMigrations that can be performed on this Database.
+     *
+     * @return List of AutoMigrations.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    Class<?>[] autoMigrations() default {};
 }
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 ba1a146..8934d45 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
@@ -92,6 +92,7 @@
         ClassName.get("$ROOM_PACKAGE.util", "DBUtil")
     val CURSOR_UTIL: ClassName =
         ClassName.get("$ROOM_PACKAGE.util", "CursorUtil")
+    val MIGRATION: ClassName = ClassName.get("$ROOM_PACKAGE.migration", "Migration")
 }
 
 object PagingTypeNames {
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
new file mode 100644
index 0000000..cbdb379
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.AutoMigration
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.SchemaBundle.deserialize
+import androidx.room.util.DiffException
+import androidx.room.util.SchemaDiffer
+import androidx.room.vo.AutoMigrationResult
+import java.io.File
+
+class AutoMigrationProcessor(
+    val context: Context,
+    val element: XTypeElement,
+    val latestDbSchema: DatabaseBundle
+) {
+    // TODO: (b/180116756) Validate annotated class extends auto migration interface
+
+    /**
+     * Retrieves two schemas of the same database provided in the @AutoMigration annotation,
+     * detects the schema changes that occurred between the two versions.
+     *
+     * @return the AutoMigrationResult containing the schema changes detected
+     */
+    fun process(): AutoMigrationResult? {
+        if (!element.isAbstract()) {
+            context.logger.e(ProcessorErrors.AUTOMIGRATION_ANNOTATED_TYPE_ELEMENT_MUST_BE_ABSTRACT)
+            return null
+        }
+
+        val annotationBox = element.toAnnotationBox(AutoMigration::class)
+        if (annotationBox == null) {
+            context.logger.e(
+                element,
+                ProcessorErrors.AUTOMIGRATION_ANNOTATION_MISSING
+            )
+            return null
+        }
+
+        val from = annotationBox.value.from
+        val to = annotationBox.value.to
+
+        if (to <= from) {
+            context.logger.e(ProcessorErrors.autoMigrationToVersionMustBeGreaterThanFrom(to, from))
+            return null
+        }
+
+        val validatedFromSchemaFile = getValidatedSchemaFile(from) ?: return null
+        val fromSchemaBundle = validatedFromSchemaFile.inputStream().use {
+            deserialize(it).database
+        }
+
+        val validatedToSchemaFile = getValidatedSchemaFile(to) ?: return null
+        val toSchemaBundle = if (to == latestDbSchema.version) {
+            latestDbSchema
+        } else {
+            validatedToSchemaFile.inputStream().use {
+                deserialize(it).database
+            }
+        }
+
+        val schemaDiff = try {
+            SchemaDiffer(
+                fromSchemaBundle = fromSchemaBundle,
+                toSchemaBundle = toSchemaBundle
+            ).diffSchemas()
+        } catch (ex: DiffException) {
+            context.logger.e(ex.errorMessage)
+            return null
+        }
+
+        return AutoMigrationResult(
+            element = element,
+            from = fromSchemaBundle.version,
+            to = toSchemaBundle.version,
+            addedColumns = schemaDiff.added
+        )
+    }
+
+    // TODO: File bug for not supporting downgrades.
+    // TODO: (b/180389433) If the files don't exist the getSchemaFile() method should return
+    //  null and before calling process
+    private fun getValidatedSchemaFile(version: Int): File? {
+        val schemaFile = File(
+            context.schemaOutFolder,
+            "${element.qualifiedName}/$version.json"
+        )
+        if (!schemaFile.exists()) {
+            context.logger.e(
+                ProcessorErrors.autoMigrationSchemasNotFound(
+                    context.schemaOutFolder.toString(),
+                    "${element.qualifiedName}/$version.json"
+                )
+            )
+            return null
+        }
+        return schemaFile
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index f97846d..745bd1f 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -22,8 +22,10 @@
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.ext.RoomTypeNames
+import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.verifier.DatabaseVerificationErrors
 import androidx.room.verifier.DatabaseVerifier
+import androidx.room.vo.AutoMigrationResult
 import androidx.room.vo.Dao
 import androidx.room.vo.DaoMethod
 import androidx.room.vo.Database
@@ -115,9 +117,42 @@
             exportSchema = dbAnnotation.value.exportSchema,
             enableForeignKeys = hasForeignKeys
         )
+        database.autoMigrations = processAutoMigrations(element, database.bundle)
         return database
     }
 
+    private fun processAutoMigrations(
+        element: XTypeElement,
+        latestDbSchema: DatabaseBundle
+    ): List<AutoMigrationResult> {
+        val dbAnnotation = element.toAnnotationBox(androidx.room.Database::class)!!
+        val autoMigrationList = dbAnnotation.getAsTypeList("autoMigrations")
+        context.checker.check(
+            autoMigrationList.isEmpty() || dbAnnotation.value.exportSchema,
+            element,
+            ProcessorErrors.AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF
+        )
+
+        return autoMigrationList.mapNotNull {
+            val typeElement = it.typeElement
+            if (typeElement == null) {
+                context.logger.e(
+                    element,
+                    ProcessorErrors.invalidAutoMigrationTypeInDatabaseAnnotation(
+                        it.typeName
+                    )
+                )
+                null
+            } else {
+                AutoMigrationProcessor(
+                    context = context,
+                    element = typeElement,
+                    latestDbSchema = latestDbSchema
+                ).process()
+            }
+        }
+    }
+
     private fun validateForeignKeys(element: XTypeElement, entities: List<Entity>) {
         val byTableName = entities.associateBy { it.tableName }
         entities.forEach { entity ->
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 498357d..9d14b74 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -788,6 +788,11 @@
             "interface."
     }
 
+    fun invalidAutoMigrationTypeInDatabaseAnnotation(typeName: TypeName): String {
+        return "Invalid AutoMigration type: $typeName. An automigration in the database must be a" +
+            " class."
+    }
+
     val EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE = "The type of an Embedded field must be a " +
         "class or an interface."
     val RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE = "Entity type in a Relation must be a class " +
@@ -798,4 +803,51 @@
     ): String {
         return "Invalid query argument: $typeName. It must be a class or an interface."
     }
+
+    val AUTOMIGRATION_ANNOTATED_TYPE_ELEMENT_MUST_BE_ABSTRACT = "The @AutoMigration annotated " +
+        "type must be an abstract class."
+    val AUTOMIGRATION_ANNOTATION_MISSING = "The @AutoMigration annotation has not been found. " +
+        "Cannot generate auto migrations."
+
+    // TODO: (b/180389433) If the files don't exist the getSchemaFile() method should return
+    //  null and before calling process
+    fun autoMigrationToVersionMustBeGreaterThanFrom(to: Int, from: Int) =
+        if (from > to) {
+            "Downgrades are not supported in AutoMigration."
+        } else {
+            "The versions provided (to: $to, from: $from) are invalid. The To version must" +
+                " be greater than the From version."
+        }
+
+    fun autoMigrationSchemasNotFound(schemaOutFolderPath: String, versionFile: String): String {
+        return "Schemas required for migration are not found at path: " +
+            "$schemaOutFolderPath$versionFile. Cannot generate auto migrations."
+    }
+
+    fun newNotNullColumnMustHaveDefaultValue(columnName: String): String {
+        return "New NOT NULL " +
+            "column'$columnName' " +
+            "added with no default value specified. Please specify the default value using " +
+            "@ColumnInfo."
+    }
+
+    fun nullabilityOfColumnChangedNotNullColumnMustHaveDefaultValue(columnName: String): String {
+        return "The nullability of the " +
+            "column '$columnName' " +
+            "has been changed from NULL to NOT NULL with no default value specified. Please " +
+            "specify the default value using @ColumnInfo."
+    }
+
+    fun columnWithChangedSchemaFound(columnName: String): String {
+        return "Encountered column '$columnName' with a changed FieldBundle schema. This change " +
+            "is not currently supported by AutoMigration."
+    }
+
+    fun removedOrRenamedColumnFound(columnName: String): String {
+        return "Column '$columnName' has been either removed or " +
+            "renamed. This change is not currently supported by AutoMigration."
+    }
+
+    val AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF = "Cannot create auto-migrations when export " +
+        "schema is OFF."
 }
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt b/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt
new file mode 100644
index 0000000..80435f6
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.util
+
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.EntityBundle
+import androidx.room.processor.ProcessorErrors
+import androidx.room.vo.AutoMigrationResult
+
+/**
+ * This exception should be thrown to abandon processing an @AutoMigration.
+ */
+class DiffException(val errorMessage: String) : RuntimeException(errorMessage)
+
+/**
+ * Contains the added, changed and removed columns detected.
+ */
+data class SchemaDiffResult(
+    val added: List<AutoMigrationResult.AddedColumn>,
+    val changed: List<AutoMigrationResult.ChangedColumn>,
+    val removed: List<AutoMigrationResult.RemovedColumn>
+)
+
+/**
+ * Receives the two bundles, diffs and returns a @SchemaDiffResult.
+ *
+ * Throws an @AutoMigrationException with a detailed error message when an AutoMigration cannot
+ * be generated.
+ */
+class SchemaDiffer(
+    val fromSchemaBundle: DatabaseBundle,
+    val toSchemaBundle: DatabaseBundle
+) {
+
+    /**
+     * Compares the two versions of the database based on the schemas provided, and detects
+     * schema changes.
+     *
+     * @return the AutoMigrationResult containing the schema changes detected
+     */
+    fun diffSchemas(): SchemaDiffResult {
+        val addedTables = mutableListOf<EntityBundle>()
+        val removedTables = mutableListOf<EntityBundle>()
+
+        val addedColumns = mutableListOf<AutoMigrationResult.AddedColumn>()
+        val changedColumns = mutableListOf<AutoMigrationResult.ChangedColumn>()
+        val removedColumns = mutableListOf<AutoMigrationResult.RemovedColumn>()
+
+        // Check going from the original version of the schema to the new version for changed and
+        // removed columns/tables
+        fromSchemaBundle.entitiesByTableName.forEach { v1Table ->
+            val v2Table = toSchemaBundle.entitiesByTableName[v1Table.key]
+            if (v2Table == null) {
+                removedTables.add(v1Table.value)
+            } else {
+                val v1Columns = v1Table.value.fieldsByColumnName
+                val v2Columns = v2Table.fieldsByColumnName
+                v1Columns.entries.forEach { v1Column ->
+                    val match = v2Columns[v1Column.key]
+                    if (match != null && !match.isSchemaEqual(v1Column.value)) {
+                        changedColumns.add(
+                            AutoMigrationResult.ChangedColumn(
+                                v1Table.key,
+                                v1Column.value,
+                                match
+                            )
+                        )
+                    } else if (match == null) {
+                        removedColumns.add(
+                            AutoMigrationResult.RemovedColumn(
+                                v1Table.key,
+                                v1Column.value
+                            )
+                        )
+                    }
+                }
+            }
+        }
+        // Check going from the new version of the schema to the original version for added
+        // tables/columns. Skip the columns with the same name as the previous loop would have
+        // processed them already.
+        toSchemaBundle.entitiesByTableName.forEach { v2Table ->
+            val v1Table = fromSchemaBundle.entitiesByTableName[v2Table.key]
+            if (v1Table == null) {
+                addedTables.add(v2Table.value)
+            } else {
+                val v2Columns = v2Table.value.fieldsByColumnName
+                val v1Columns = v1Table.fieldsByColumnName
+                v2Columns.entries.forEach { v2Column ->
+                    val match = v1Columns[v2Column.key]
+                    if (match == null) {
+                        if (v2Column.value.isNonNull && v2Column.value.defaultValue == null) {
+                            throw DiffException(
+                                ProcessorErrors.newNotNullColumnMustHaveDefaultValue(v2Column.key)
+                            )
+                        }
+                        addedColumns.add(
+                            AutoMigrationResult.AddedColumn(
+                                v2Table.key,
+                                v2Column.value
+                            )
+                        )
+                    }
+                }
+            }
+        }
+
+        if (changedColumns.isNotEmpty()) {
+            changedColumns.forEach { changedColumn ->
+                throw DiffException(
+                    ProcessorErrors.columnWithChangedSchemaFound(
+                        changedColumn.originalFieldBundle.columnName
+                    )
+                )
+            }
+        } else if (removedColumns.isNotEmpty()) {
+            removedColumns.forEach { removedColumn ->
+                throw DiffException(
+                    ProcessorErrors.removedOrRenamedColumnFound(
+                        removedColumn.fieldBundle.columnName
+                    )
+                )
+            }
+        }
+
+        return SchemaDiffResult(
+            added = addedColumns,
+            changed = changedColumns,
+            removed = removedColumns
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
new file mode 100644
index 0000000..26d5230
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.migration.bundle.FieldBundle
+
+data class AutoMigrationResult(
+    val element: XTypeElement,
+    val from: Int?,
+    val to: Int?,
+    val addedColumns: List<AddedColumn>
+) {
+    /**
+     * Stores the table name and the relevant field bundle of a column that was added to a
+     * database in a newer version.
+     */
+    data class AddedColumn(val tableName: String, val fieldBundle: FieldBundle)
+
+    /**
+     * Stores the table name and the relevant field bundle of a column that was present in both
+     * the old and new version of the same database, but had a change in the field schema (e.g.
+     * change in affinity).
+     */
+    data class ChangedColumn(
+        val tableName: String,
+        val originalFieldBundle: FieldBundle,
+        val newFieldBundle: FieldBundle
+    )
+
+    /**
+     * Stores the table name and the relevant field bundle of a column that was present in the
+     * old version of a database but is not present in a new version of the same database, either
+     * because it was removed or renamed.
+     *
+     * In the current implementation, we cannot differ between whether the column was removed or
+     * renamed.
+     */
+    data class RemovedColumn(val tableName: String, val fieldBundle: FieldBundle)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
index a22fb9d..f9d3f27 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
@@ -38,6 +38,9 @@
     val exportSchema: Boolean,
     val enableForeignKeys: Boolean
 ) {
+    // This variable will be set once auto-migrations are processed given the DatabaseBundle from
+    // this object. This is necessary for tracking the versions involved in the auto-migration.
+    lateinit var autoMigrations: List<AutoMigrationResult>
     val typeName: ClassName by lazy { element.className }
 
     private val implClassName by lazy {
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
new file mode 100644
index 0000000..64d6733
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.EntityBundle
+import androidx.room.migration.bundle.FieldBundle
+import androidx.room.migration.bundle.PrimaryKeyBundle
+import androidx.room.migration.bundle.SchemaBundle
+import androidx.room.testing.context
+import org.junit.Test
+
+class AutoMigrationProcessorTest {
+    @Test
+    fun testElementIsAbstract() {
+        val source = Source.java(
+            "foo.bar.MyAutoMigration",
+            """
+            package foo.bar;
+            import androidx.room.AutoMigration;
+            @AutoMigration(from=1, to=2)
+            class MyAutoMigration implements AutoMigration {}
+            """.trimIndent()
+        )
+
+        runProcessorTest(listOf(source)) { invocation ->
+            AutoMigrationProcessor(
+                invocation.context,
+                invocation.processingEnv.requireTypeElement("foo.bar.MyAutoMigration"),
+                from.database
+            ).process()
+            invocation.assertCompilationResult {
+                hasError(ProcessorErrors.AUTOMIGRATION_ANNOTATED_TYPE_ELEMENT_MUST_BE_ABSTRACT)
+            }
+        }
+    }
+
+    /**
+     * Schemas for processor testing.
+     */
+    val from = SchemaBundle(
+        1,
+        DatabaseBundle(
+            1,
+            "",
+            mutableListOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 9cdfce9..8094bb1d 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -172,6 +172,16 @@
                 }
                 """
         )
+
+        val AUTOMIGRATION: JavaFileObject = JavaFileObjects.forSourceString(
+            "foo.bar.MyAutoMigration",
+            """
+                package foo.bar;
+                import androidx.room.AutoMigration;
+                @AutoMigration(from=41, to=42)
+                abstract class MyAutoMigration implements AutoMigration {}
+                """
+        )
     }
 
     @Test
@@ -1127,6 +1137,20 @@
         )
     }
 
+    @Test
+    fun autoMigrationDefinedButDatabaseSchemaExportOff() {
+        singleDb(
+            """
+                @Database(entities = {User.class}, version = 42, exportSchema = false,
+                autoMigrations = {MyAutoMigration.class})
+                public abstract class MyDb extends RoomDatabase {}
+                """,
+            USER, AUTOMIGRATION
+        ) { _, _ -> }
+            .failsToCompile()
+            .withErrorContaining(ProcessorErrors.AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF)
+    }
+
     private fun resolveDatabaseViews(
         views: Map<String, Set<String>>,
         body: (List<DatabaseView>) -> Unit
diff --git a/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt b/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
new file mode 100644
index 0000000..6be4eea
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.util
+
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.EntityBundle
+import androidx.room.migration.bundle.FieldBundle
+import androidx.room.migration.bundle.PrimaryKeyBundle
+import androidx.room.migration.bundle.SchemaBundle
+import androidx.room.processor.ProcessorErrors
+import com.google.common.truth.Truth.assertThat
+import com.ibm.icu.impl.Assert.fail
+import org.junit.Test
+
+class SchemaDifferTest {
+
+    @Test
+    fun testColumnAddedWithColumnInfoDefaultValue() {
+        val schemaDiffResult = SchemaDiffer(
+            fromSchemaBundle = from.database,
+            toSchemaBundle = toColumnAddedWithColumnInfoDefaultValue.database
+        ).diffSchemas()
+        assertThat(schemaDiffResult.added[0].fieldBundle.columnName).isEqualTo("artistId")
+    }
+
+    @Test
+    fun testColumnAddedWithNoDefaultValue() {
+        try {
+            SchemaDiffer(
+                fromSchemaBundle = from.database,
+                toSchemaBundle = toColumnAddedWithNoDefaultValue.database
+            ).diffSchemas()
+            fail("DiffException should have been thrown.")
+        } catch (ex: DiffException) {
+            assertThat(ex.errorMessage).isEqualTo(
+                ProcessorErrors.newNotNullColumnMustHaveDefaultValue("artistId")
+            )
+        }
+    }
+
+    @Test
+    fun testColumnRenamed() {
+        try {
+            SchemaDiffer(
+                fromSchemaBundle = from.database,
+                toSchemaBundle = toColumnRenamed.database
+            ).diffSchemas()
+            fail("DiffException should have been thrown.")
+        } catch (ex: DiffException) {
+            assertThat(ex.errorMessage).isEqualTo(
+                ProcessorErrors.removedOrRenamedColumnFound("length")
+            )
+        }
+    }
+
+    @Test
+    fun testColumnAffinityChanged() {
+        try {
+            SchemaDiffer(
+                fromSchemaBundle = from.database,
+                toSchemaBundle = toColumnAffinityChanged.database
+            ).diffSchemas()
+            fail("DiffException should have been thrown.")
+        } catch (ex: DiffException) {
+            assertThat(ex.errorMessage).isEqualTo(
+                ProcessorErrors.columnWithChangedSchemaFound("length")
+            )
+        }
+    }
+
+    @Test
+    fun testColumnRemoved() {
+        try {
+            SchemaDiffer(
+                fromSchemaBundle = from.database,
+                toSchemaBundle = toColumnRemoved.database
+            ).diffSchemas()
+            fail("DiffException should have been thrown.")
+        } catch (ex: DiffException) {
+            assertThat(ex.errorMessage).isEqualTo(
+                ProcessorErrors.removedOrRenamedColumnFound("length")
+            )
+        }
+    }
+
+    val from = SchemaBundle(
+        1,
+        DatabaseBundle(
+            1,
+            "",
+            mutableListOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+
+    /** Valid "to" Schemas */
+    val toColumnAddedWithColumnInfoDefaultValue = SchemaBundle(
+        2,
+        DatabaseBundle(
+            2,
+            "",
+            listOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, `artistId` " +
+                        "INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "artistId",
+                            "artistId",
+                            "INTEGER",
+                            true,
+                            "0"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    emptyList(),
+                    emptyList()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+
+    /** Invalid "to" Schemas (These are expected to throw an error.) */
+
+    /**
+     * The length column is removed from the first version. No other changes made.
+     *
+     */
+    val toColumnRemoved = SchemaBundle(
+        2,
+        DatabaseBundle(
+            2,
+            "",
+            listOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    emptyList(),
+                    emptyList()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+
+    /**
+     * If the user declared the default value in the SQL statement and not used a @ColumnInfo,
+     * Room will put null for that default value in the exported schema. In this case we
+     * can't migrate.
+     */
+    val toColumnAddedWithNoDefaultValue = SchemaBundle(
+        2,
+        DatabaseBundle(
+            2,
+            "",
+            listOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, `artistId` " +
+                        "INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "artistId",
+                            "artistId",
+                            "INTEGER",
+                            true,
+                            null
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    emptyList(),
+                    emptyList()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+
+    /**
+     * Renaming the length column to duration.
+     */
+    // TODO: We currently do not support column renames as we can't detect rename or deletion
+    //  yet.
+    val toColumnRenamed = SchemaBundle(
+        2,
+        DatabaseBundle(
+            2,
+            "",
+            mutableListOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT " +
+                        "NULL, `title` TEXT NOT NULL, `duration` INTEGER NOT NULL DEFAULT 0, " +
+                        "PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "duration",
+                            "duration",
+                            "INTEGER",
+                            true,
+                            "0"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+
+    /**
+     * The affinity of a length column is changed from Integer to Text. No columns are
+     * added/removed.
+     */
+    // TODO: We currently do not support column affinity changes.
+    val toColumnAffinityChanged = SchemaBundle(
+        2,
+        DatabaseBundle(
+            2,
+            "",
+            mutableListOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` TEXT NOT NULL DEFAULT length, " +
+                        "PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "TEXT",
+                            true,
+                            "length"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+}
\ No newline at end of file
diff --git a/samples/SupportSliceDemos/OWNERS b/samples/SupportSliceDemos/OWNERS
index c60f11f..8a9709c 100644
--- a/samples/SupportSliceDemos/OWNERS
+++ b/samples/SupportSliceDemos/OWNERS
@@ -1,4 +1,3 @@
-jmonk@google.com
 madym@google.com
 pinyaoting@google.com
 sunnygoyal@google.com
diff --git a/slidingpanelayout/slidingpanelayout/build.gradle b/slidingpanelayout/slidingpanelayout/build.gradle
index 5401758..7a23a69 100644
--- a/slidingpanelayout/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/slidingpanelayout/build.gradle
@@ -12,7 +12,7 @@
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.core:core:1.1.0")
     api(project(":customview:customview"))
-    implementation(project(":window:window"))
+    implementation("androidx.window:window:1.0.0-alpha03")
 
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/DatabaseLockingTest.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/DatabaseLockingTest.kt
index ca38758..67e4214 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/DatabaseLockingTest.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/DatabaseLockingTest.kt
@@ -44,6 +44,7 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executors.newSingleThreadExecutor
 import java.util.concurrent.TimeUnit.SECONDS
+import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicInteger
 import java.util.regex.Pattern.CASE_INSENSITIVE
 
@@ -288,6 +289,63 @@
         Unit
     }
 
+    @Test
+    fun test_endToEnd_inspector_lock_query_unlock_walOn() =
+        test_endToEnd_inspector_lock_query_unlock(writeAheadLoggingEnabled = true)
+
+    @Test
+    fun test_endToEnd_inspector_lock_query_unlock_walOff() =
+        test_endToEnd_inspector_lock_query_unlock(writeAheadLoggingEnabled = false)
+
+    private fun test_endToEnd_inspector_lock_query_unlock(
+        writeAheadLoggingEnabled: Boolean
+    ) = runBlocking {
+        // create database
+        val db = database.createInstance(temporaryFolder, writeAheadLoggingEnabled)
+        insertValue(db, value1)
+        assertThat(getValueSum(db)).isEqualTo(value1)
+
+        // inspect database
+        val dbId = testEnvironment.inspectDatabase(db)
+
+        // establish a lock
+        val lockId =
+            testEnvironment.sendCommand(acquireLockCommand(dbId)).acquireDatabaseLock.lockId
+        assertThat(lockId).isGreaterThan(0) // check that locking succeeded
+
+        // try to insert a value on app thread
+        val appInsertDone = AtomicBoolean(false)
+        val appInsertTask = applicationThread.submit {
+            insertValue(db, value2)
+            appInsertDone.set(true)
+        }
+        assertThat(appInsertDone.get()).isFalse()
+
+        // query schema
+        testEnvironment.sendCommand(MessageFactory.createGetSchemaCommand(dbId)).let {
+            assertThat(it.getSchema.tablesList.map { t -> t.name }).isEqualTo(listOf(table.name))
+        }
+
+        // repeat lock (testing simultaneous locks)
+        testEnvironment.sendCommand(acquireLockCommand(dbId)).let {
+            assertThat(it.acquireDatabaseLock.lockId).isEqualTo(lockId) // the same lock id expected
+        }
+
+        // query table
+        testEnvironment.issueQuery(dbId, "select sum(${column.name}) from ${table.name}").let {
+            assertThat(appInsertDone.get()).isFalse()
+            assertThat(it.rowsList.single().valuesList.single().longValue.toInt()).isEqualTo(value1)
+        }
+
+        // release all locks and verify the app thread insert operation succeeds
+        testEnvironment.sendCommand(releaseLockCommand(lockId))
+        assertThat(appInsertDone.get()).isFalse()
+        testEnvironment.sendCommand(releaseLockCommand(lockId))
+        appInsertTask.get(2, SECONDS)
+        assertThat(appInsertDone.get()).isTrue()
+        assertThat(getValueSum(db)).isEqualTo(value1 + value2)
+    }
+
     @Suppress("SameParameterValue")
     private fun withLockingTimeoutOverride(overrideMs: Int, block: () -> Any) {
         val current = DatabaseLockRegistry.sTimeoutMs
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseLockRegistry.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseLockRegistry.java
index 9c30e2d..3f4b9af 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseLockRegistry.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseLockRegistry.java
@@ -21,7 +21,9 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.sqlite.inspection.SqliteInspector.DatabaseConnection;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -99,6 +101,19 @@
     }
 
     /**
+     * @return `null` if the database is not locked; the database and the executor that locked the
+     * database otherwise
+     */
+    @Nullable DatabaseConnection getConnection(int databaseId) {
+        synchronized (mLock) {
+            Lock lock = mDatabaseIdToLockMap.get(databaseId);
+            return (lock == null)
+                    ? null
+                    : new DatabaseConnection(lock.mDatabase, mExecutor);
+        }
+    }
+
+    /**
      * Starts a database transaction and acquires an extra database reference to keep the database
      * open while the lock is in place.
      */
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseRegistry.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseRegistry.java
index fb0bd81..2ff137d 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseRegistry.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseRegistry.java
@@ -250,17 +250,15 @@
      * Consumer of this method must release the reference when done using it.
      * Thread-safe
      */
-    // TODO: rename as can be confused with {@link SQLiteDatabase#acquireReference}.
     @Nullable
-    SQLiteDatabase acquireReference(int databaseId) {
+    SQLiteDatabase getConnection(int databaseId) {
         synchronized (mLock) {
-            return acquireReferenceImpl(databaseId);
+            return getConnectionImpl(databaseId);
         }
     }
 
-    // TODO: rename as can be confused with {@link SQLiteDatabase#acquireReference}.
     @GuardedBy("mLock")
-    private SQLiteDatabase acquireReferenceImpl(int databaseId) {
+    private SQLiteDatabase getConnectionImpl(int databaseId) {
         KeepOpenReference keepOpenReference = mKeepOpenReferences.get(databaseId);
         if (keepOpenReference != null) {
             return keepOpenReference.mDatabase;
@@ -313,7 +311,7 @@
         }
 
         // Try secure a keep-open reference
-        SQLiteDatabase reference = acquireReferenceImpl(id);
+        SQLiteDatabase reference = getConnectionImpl(id);
         if (reference != null) {
             mKeepOpenReferences.put(id, new KeepOpenReference(reference));
         }
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
index ef524ed..0472a2d 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
@@ -295,21 +295,27 @@
         }
     }
 
+    /**
+     * Secures a lock (transaction) on the database. Note that while the lock is in place, no
+     * changes to the database are possible:
+     * - the lock prevents other threads from modifying the database,
+     * - lock thread, on releasing the lock, rolls-back all changes (transaction is rolled-back).
+     */
     @SuppressWarnings("FutureReturnValueIgnored") // code inside the future is exception-proofed
     private void handleAcquireDatabaseLock(
             AcquireDatabaseLockCommand command,
             final CommandCallback callback) {
         final int databaseId = command.getDatabaseId();
-        final SQLiteDatabase reference = acquireReference(databaseId, callback);
-        if (reference == null) return;
+        final DatabaseConnection connection = acquireConnection(databaseId, callback);
+        if (connection == null) return;
 
-        // TODO(161081452): consider cancellations
+        // Timeout is covered by mDatabaseLockRegistry
         SqliteInspectionExecutors.submit(mIOExecutor, new Runnable() {
             @Override
             public void run() {
                 int lockId;
                 try {
-                    lockId = mDatabaseLockRegistry.acquireLock(databaseId, reference);
+                    lockId = mDatabaseLockRegistry.acquireLock(databaseId, connection.mDatabase);
                 } catch (Exception e) {
                     processLockingException(callback, e, true);
                     return;
@@ -326,7 +332,7 @@
     private void handleReleaseDatabaseLock(
             final ReleaseDatabaseLockCommand command,
             final CommandCallback callback) {
-        // TODO(161081452): add cancellations when adding export job cancellations
+        // Timeout is covered by mDatabaseLockRegistry
         SqliteInspectionExecutors.submit(mIOExecutor, new Runnable() {
             @Override
             public void run() {
@@ -590,25 +596,34 @@
         ).build().toByteArray());
     }
 
-    private void handleGetSchema(GetSchemaCommand command, CommandCallback callback) {
-        SQLiteDatabase reference = acquireReference(command.getDatabaseId(), callback);
-        if (reference == null) return;
+    @SuppressWarnings("FutureReturnValueIgnored") // code inside the future is exception-proofed
+    private void handleGetSchema(GetSchemaCommand command, final CommandCallback callback) {
+        final DatabaseConnection connection = acquireConnection(command.getDatabaseId(), callback);
+        if (connection == null) return;
 
-        callback.reply(querySchema(reference).toByteArray());
+        // TODO: consider a timeout
+        SqliteInspectionExecutors.submit(connection.mExecutor, new Runnable() {
+            @Override
+            public void run() {
+                callback.reply(querySchema(connection.mDatabase).toByteArray());
+            }
+        });
     }
 
     private void handleQuery(final QueryCommand command, final CommandCallback callback) {
-        final SQLiteDatabase reference = acquireReference(command.getDatabaseId(), callback);
-        if (reference == null) return;
+        final DatabaseConnection connection = acquireConnection(command.getDatabaseId(), callback);
+        if (connection == null) return;
 
         final CancellationSignal cancellationSignal = new CancellationSignal();
-        final Future<?> future = SqliteInspectionExecutors.submit(mIOExecutor, new Runnable() {
+        final Executor executor = connection.mExecutor;
+        // TODO: consider a timeout
+        final Future<?> future = SqliteInspectionExecutors.submit(executor, new Runnable() {
             @Override
             public void run() {
                 String[] params = parseQueryParameterValues(command);
                 Cursor cursor = null;
                 try {
-                    cursor = rawQuery(reference, command.getQuery(), params,
+                    cursor = rawQuery(connection.mDatabase, command.getQuery(), params,
                             cancellationSignal);
                     List<String> columnNames = Arrays.asList(cursor.getColumnNames());
                     callback.reply(Response.newBuilder()
@@ -715,18 +730,36 @@
      * Tries to find a database for an id. If no such database is found, it replies with an
      * {@link ErrorOccurredResponse} via the {@code callback} provided.
      *
-     * Consumer of this method must release the reference when done using it.
+     * TODO: remove race condition (affects WAL=off)
+     * - lock request is received and in the process of being secured
+     * - query request is received and since no lock in place, receives an IO Executor
+     * - lock request completes and holds a lock on the database
+     * - query cannot run because there is a lock in place
+     *
+     * The race condition can be mitigated by clients by securing a lock synchronously with no
+     * other queries in place.
      *
      * @return null if no database found for the provided id. A database reference otherwise.
      */
     @Nullable
-    private SQLiteDatabase acquireReference(int databaseId, CommandCallback callback) {
-        SQLiteDatabase database = mDatabaseRegistry.acquireReference(databaseId);
+    private DatabaseConnection acquireConnection(int databaseId, CommandCallback callback) {
+        DatabaseConnection connection = mDatabaseLockRegistry.getConnection(databaseId);
+        if (connection != null) {
+            // With WAL enabled, we prefer to use the IO executor. With WAL off we don't have a
+            // choice and must use the executor that has a lock (transaction) on the database.
+            return connection.mDatabase.isWriteAheadLoggingEnabled()
+                    ? new DatabaseConnection(connection.mDatabase, mIOExecutor)
+                    : connection;
+        }
+
+        SQLiteDatabase database = mDatabaseRegistry.getConnection(databaseId);
         if (database == null) {
             replyNoDatabaseWithId(callback, databaseId);
             return null;
         }
-        return database;
+
+        // Given no lock, IO executor is appropriate.
+        return new DatabaseConnection(database, mIOExecutor);
     }
 
     private static List<Row> convert(Cursor cursor) {
@@ -916,4 +949,20 @@
         String path = file.getPath();
         return path.endsWith("-journal") || path.endsWith("-shm") || path.endsWith("-wal");
     }
+
+    /**
+     * Provides a reference to the database and an executor to access the database.
+     *
+     * Executor is relevant in the context of locking, where a locked database with WAL disabled
+     * needs to run queries on the thread that locked it.
+     */
+    static final class DatabaseConnection {
+        @NonNull final SQLiteDatabase mDatabase;
+        @NonNull final Executor mExecutor;
+
+        DatabaseConnection(@NonNull SQLiteDatabase database, @NonNull Executor executor) {
+            mDatabase = database;
+            mExecutor = executor;
+        }
+    }
 }
diff --git a/transition/transition-ktx/OWNERS b/transition/transition-ktx/OWNERS
index e450f4c..e69de29 100644
--- a/transition/transition-ktx/OWNERS
+++ b/transition/transition-ktx/OWNERS
@@ -1 +0,0 @@
-jakew@google.com
diff --git a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index c7d3a21..92f2a52 100644
--- a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -339,9 +339,7 @@
     internal suspend fun getPreviewData(
         providerInfoRetriever: ProviderInfoRetriever,
         providerInfo: ComplicationProviderInfo?
-    ): ComplicationData? = TraceEvent(
-        "BaseEditorSession.getPreviewData for ${providerInfo?.providerComponentName}"
-    ).use {
+    ): ComplicationData? = TraceEvent("BaseEditorSession.getPreviewData").use {
         if (providerInfo == null) {
             return null
         }
diff --git a/window/window-extensions/build.gradle b/window/window-extensions/build.gradle
index 6e84dbc..e4db938 100644
--- a/window/window-extensions/build.gradle
+++ b/window/window-extensions/build.gradle
@@ -33,7 +33,7 @@
 
 android {
     defaultConfig {
-        minSdkVersion 16
+        minSdkVersion 14
     }
 }
 
diff --git a/window/window-sidecar/build.gradle b/window/window-sidecar/build.gradle
index 5c704f4..6c81f6e 100644
--- a/window/window-sidecar/build.gradle
+++ b/window/window-sidecar/build.gradle
@@ -26,7 +26,7 @@
 
 android {
     defaultConfig {
-        minSdkVersion 16
+        minSdkVersion 14
     }
 }
 
diff --git a/window/window/build.gradle b/window/window/build.gradle
index db51a1c..706f6f6 100644
--- a/window/window/build.gradle
+++ b/window/window/build.gradle
@@ -36,7 +36,7 @@
 
 android {
     defaultConfig {
-        minSdkVersion 16
+        minSdkVersion 14
     }
     buildTypes.all {
         consumerProguardFiles "proguard-rules.pro"
diff --git a/window/window/src/androidTest/java/androidx/window/TestActivity.java b/window/window/src/androidTest/java/androidx/window/TestActivity.java
index 65d1a81..b5632cd 100644
--- a/window/window/src/androidTest/java/androidx/window/TestActivity.java
+++ b/window/window/src/androidTest/java/androidx/window/TestActivity.java
@@ -27,7 +27,6 @@
 
 public class TestActivity extends Activity implements View.OnLayoutChangeListener {
 
-    private int mRootViewId;
     private CountDownLatch mLayoutLatch = new CountDownLatch(1);
     private static CountDownLatch sResumeLatch = new CountDownLatch(1);
 
@@ -35,8 +34,6 @@
     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);
diff --git a/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java b/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
index 5db5039..c320d4d 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
+++ b/window/window/src/androidTest/java/androidx/window/WindowBoundsHelperTest.java
@@ -17,6 +17,7 @@
 package androidx.window;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
@@ -286,6 +287,8 @@
             Point realDisplaySize = WindowBoundsHelper.getRealSizeForDisplay(display);
 
             Rect bounds = WindowBoundsHelper.getInstance().computeCurrentWindowBounds(activity);
+            assertNotEquals("Device can not have zero width", 0, realDisplaySize.x);
+            assertNotEquals("Device can not have zero height", 0, realDisplaySize.y);
             assertEquals("Window bounds width does not match real display width",
                     realDisplaySize.x, bounds.width());
             assertEquals("Window bounds height does not match real display height",
diff --git a/window/window/src/main/java/androidx/window/WindowBoundsHelper.java b/window/window/src/main/java/androidx/window/WindowBoundsHelper.java
index d2db428..c71b772 100644
--- a/window/window/src/main/java/androidx/window/WindowBoundsHelper.java
+++ b/window/window/src/main/java/androidx/window/WindowBoundsHelper.java
@@ -16,7 +16,7 @@
 
 package androidx.window;
 
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
+import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.N;
 import static android.os.Build.VERSION_CODES.P;
@@ -118,7 +118,7 @@
         } else if (Build.VERSION.SDK_INT >= N) {
             return computeWindowBoundsN(activity);
         } else {
-            return computeWindowBoundsJellyBean(activity);
+            return computeWindowBoundsIceCreamSandwich(activity);
         }
     }
 
@@ -296,14 +296,18 @@
      * size which should match the window size of a full-screen app.
      */
     @NonNull
-    @RequiresApi(JELLY_BEAN)
-    private static Rect computeWindowBoundsJellyBean(Activity activity) {
+    @RequiresApi(ICE_CREAM_SANDWICH)
+    private static Rect computeWindowBoundsIceCreamSandwich(Activity activity) {
         Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
         Point realDisplaySize = getRealSizeForDisplay(defaultDisplay);
 
         Rect bounds = new Rect();
-        bounds.right = realDisplaySize.x;
-        bounds.bottom = realDisplaySize.y;
+        if (realDisplaySize.x == 0 || realDisplaySize.y == 0) {
+            defaultDisplay.getRectSize(bounds);
+        } else {
+            bounds.right = realDisplaySize.x;
+            bounds.bottom = realDisplaySize.y;
+        }
         return bounds;
     }
 
@@ -319,7 +323,7 @@
      */
     @NonNull
     @VisibleForTesting
-    @RequiresApi(JELLY_BEAN)
+    @RequiresApi(ICE_CREAM_SANDWICH)
     static Point getRealSizeForDisplay(Display display) {
         Point size = new Point();
         if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {