Merge "Revert "Rename DisposableEffectDisposable to DisposableEffectResult"" into androidx-main
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 621b0e7..ef65ab0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -44,7 +44,7 @@
     val CAR_APP = Version("1.0.0-alpha01")
     val COLLECTION = Version("1.2.0-alpha02")
     val CONTENTPAGER = Version("1.1.0-alpha01")
-    val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.0.0-alpha11")
+    val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.0.0-alpha12")
     val COORDINATORLAYOUT = Version("1.2.0-alpha01")
     val CORE = Version("1.5.0-beta01")
     val CORE_ANIMATION = Version("1.0.0-alpha03")
@@ -83,9 +83,9 @@
     val MEDIA2 = Version("1.2.0-alpha01")
     val MEDIAROUTER = Version("1.3.0-alpha01")
     val NAVIGATION = Version("2.4.0-alpha01")
-    val NAVIGATION_COMPOSE = Version("1.0.0-alpha06")
+    val NAVIGATION_COMPOSE = Version("1.0.0-alpha07")
     val PAGING = Version("3.0.0-alpha13")
-    val PAGING_COMPOSE = Version("1.0.0-alpha06")
+    val PAGING_COMPOSE = Version("1.0.0-alpha07")
     val PALETTE = Version("1.1.0-alpha01")
     val PRINT = Version("1.1.0-beta01")
     val PERCENTLAYOUT = Version("1.1.0-alpha01")
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java
index b00fd48..125acc5 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageSaverTest.java
@@ -233,9 +233,27 @@
     }
 
     @Test
-    public void canSaveYuvImage() throws InterruptedException, IOException {
+    public void canSaveYuvImage_withNonExistingFile() throws InterruptedException {
+        File saveLocation = new File(ApplicationProvider.getApplicationContext().getCacheDir(),
+                "test" + System.currentTimeMillis() + ".jpg");
+        saveLocation.deleteOnExit();
+        // make sure file does not exist
+        if (saveLocation.exists()) {
+            saveLocation.delete();
+        }
+        assertThat(!saveLocation.exists());
+
+        getDefaultImageSaver(mMockYuvImage, saveLocation).run();
+        mSemaphore.acquire();
+
+        verify(mMockCallback).onImageSaved(any());
+    }
+
+    @Test
+    public void canSaveYuvImage_withExistingFile() throws InterruptedException, IOException {
         File saveLocation = File.createTempFile("test", ".jpg");
         saveLocation.deleteOnExit();
+        assertThat(saveLocation.exists());
 
         getDefaultImageSaver(mMockYuvImage, saveLocation).run();
         mSemaphore.acquire();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaver.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageSaver.java
index fdd5ca9..f514eab 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageSaver.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageSaver.java
@@ -37,7 +37,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
-import java.nio.channels.FileLock;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
 
@@ -99,7 +99,13 @@
     private File saveImageToTempFile() {
         File tempFile;
         try {
-            tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX);
+            if (isSaveToFile()) {
+                // For saving to file, write to the target folder and rename for better performance.
+                tempFile = new File(mOutputFileOptions.getFile().getParent(),
+                        TEMP_FILE_PREFIX + UUID.randomUUID().toString() + TEMP_FILE_SUFFIX);
+            } else {
+                tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX);
+            }
         } catch (IOException e) {
             postError(SaveError.FILE_IO_FAILED, "Failed to create temp file", e);
             return null;
@@ -210,13 +216,16 @@
             } else if (isSaveToOutputStream()) {
                 copyTempFileToOutputStream(tempFile, mOutputFileOptions.getOutputStream());
             } else if (isSaveToFile()) {
-                try (FileOutputStream fileOutputStream = new FileOutputStream(
-                        mOutputFileOptions.getFile())) {
-                    // Lock the file as a precaution. If concurrent access happens, it will
-                    // provide a meaningful error.
-                    FileLock fileLock = fileOutputStream.getChannel().lock();
-                    copyTempFileToOutputStream(tempFile, fileOutputStream);
-                    fileLock.release();
+                File targetFile = mOutputFileOptions.getFile();
+                // Normally File#renameTo will overwrite the targetFile even if it already exists.
+                // Just in case of unexpected behavior on certain platforms or devices, delete the
+                // target file before renaming.
+                if (targetFile.exists()) {
+                    targetFile.delete();
+                }
+                if (!tempFile.renameTo(targetFile)) {
+                    saveError = SaveError.FILE_IO_FAILED;
+                    errorMessage = "Failed to rename file.";
                 }
             }
         } catch (IOException | IllegalArgumentException e) {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoRecordEvent.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoRecordEvent.java
new file mode 100644
index 0000000..36a6cbd
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoRecordEvent.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * VideoRecordEvent is used to report the video recording events and status.
+ *
+ * <p>There are {@link Start}, {@link Stop} and {@link Status} events. The {@link #getEventType()}
+ * can be used to check what type of event is.
+ *
+ * Example: typical way to determine the event type and cast to the event class
+ *
+ * <pre>{@code
+ *
+ * VideoRecordEvent videoRecordEvent = obtainVideoRecordEvent();
+ * switch (videoRecordEvent.getEventType()) {
+ * case START:
+ *     VideoRecordEvent.Start start = (VideoRecordEvent.Start) videoRecordEvent;
+ *     break;
+ * case STOP:
+ *     VideoRecordEvent.Stop stop = (VideoRecordEvent.Stop) videoRecordEvent;
+ *     break;
+ * case STATUS:
+ *     VideoRecordEvent.Status status = (VideoRecordEvent.Status) videoRecordEvent;
+ *     break;
+ * }
+ *
+ * }</pre>
+ *
+ * <p>When a video recording is requested, {@link Start} event will be reported at first and
+ * {@link Stop} event will be reported when the recording is finished. The stop reason can be
+ * obtained via {@link Stop#getError()}. {@link #ERROR_NONE} means that the video was recorded
+ * successfully, and other error code indicate the recording is failed or stopped due to a certain
+ * reason. Please note that a failed result does not mean that the video file has not been
+ * generated. In some cases, the file can still be successfully generated. For example,
+ * the result {@link #ERROR_INSUFFICIENT_DISK} will still have video file.
+ *
+ * <p>The {@link Status} event will be triggered continuously during the recording process,
+ * {@link Status#getNumBytesRecorded()} can be used to get the total record size when reporting
+ * status. And {@link Status#getRecordedDurationNs()} can be used to get the total duration.
+ */
+public abstract class VideoRecordEvent {
+
+    /** The event types. */
+    public enum EventType {
+        /**
+         * Indicates the start of recording.
+         *
+         * @see Start
+         */
+        START,
+
+        /**
+         * Indicates the stop of recording.
+         *
+         * @see Stop
+         */
+        STOP,
+
+        /**
+         * The status report of the recording in progress.
+         *
+         * @see Status
+         */
+        STATUS
+    }
+
+    /**
+     * No error. The recording succeeds.
+     */
+    public static final int ERROR_NONE = 0;
+
+    /**
+     * Unknown error.
+     */
+    public static final int ERROR_UNKNOWN = 1;
+
+    /**
+     * The recording failed due to file size limitation.
+     */
+    // TODO(b/167481981): add more descriptions about the restrictions after getting into more
+    //  details.
+    public static final int ERROR_FILE_SIZE_LIMIT_REACHED = 2;
+
+    /**
+     * The recording failed due to insufficient disk space.
+     */
+    // TODO(b/167484136): add more descriptions about the restrictions after getting into more
+    //  details.
+    public static final int ERROR_INSUFFICIENT_DISK = 3;
+
+    /**
+     * The recording failed because the camera was closed.
+     *
+     * <p>One case is that camera has been closed due to lifecycle has stopped, so video
+     * recording cannot be started.
+     */
+    public static final int ERROR_CAMERA_CLOSED = 4;
+
+    /**
+     * Describes the error that occurred during a video recording.
+     *
+     * <p>This is the error code returning from {@link Stop#getError()}.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {ERROR_NONE, ERROR_UNKNOWN, ERROR_FILE_SIZE_LIMIT_REACHED,
+            ERROR_INSUFFICIENT_DISK, ERROR_CAMERA_CLOSED})
+    public @interface VideoRecordError {
+    }
+
+    /**
+     * Gets the event type.
+     */
+    @NonNull
+    public abstract EventType getEventType();
+
+    /**
+     * Indicates the start of recording.
+     *
+     * <p>When a video recording is requested, start event will be reported at first.
+     */
+    public static final class Start extends VideoRecordEvent {
+        /** {@inheritDoc} */
+        @NonNull
+        @Override
+        public EventType getEventType() {
+            return EventType.START;
+        }
+    }
+
+    /**
+     * Indicates the stop of recording.
+     *
+     * <p>The stop event will be triggered regardless of whether the recording succeeds or
+     * fails. Use {@link Stop#getError()} to obtain the error type and {@link Stop#getCause()} to
+     * get the error cause. If there is no error, {@link #ERROR_NONE} will be returned. Other
+     * error code indicate the recording is failed or stopped due to a certain reason. Please
+     * note that a failed result does not mean that the video file has not been generated. In
+     * some cases, the file can still be successfully generated. For example, the result
+     * {@link #ERROR_INSUFFICIENT_DISK} will still have video file.
+     */
+    public static final class Stop extends VideoRecordEvent {
+        @VideoRecordError
+        private final int mError;
+        private final Throwable mCause;
+
+        Stop(@VideoRecordError int error, @Nullable Throwable cause) {
+            mError = error;
+            mCause = cause;
+        }
+
+        /** {@inheritDoc} */
+        @NonNull
+        @Override
+        public EventType getEventType() {
+            return EventType.STOP;
+        }
+
+        /**
+         * Gets the error type for a video recording.
+         *
+         * <p>Returns {@link #ERROR_NONE} if the recording did not stop due to an error.
+         */
+        @VideoRecordError
+        public int getError() {
+            return mError;
+        }
+
+        /**
+         * Gets the error cause. Returns {@code null} if {@link #getError()} returns
+         * {@link #ERROR_NONE}.
+         */
+        @Nullable
+        public Throwable getCause() {
+            return mCause;
+        }
+    }
+
+    /**
+     * The status report of the recording in progress.
+     */
+    public static final class Status extends VideoRecordEvent {
+        private final long mDurationNs;
+        private final long mBytes;
+
+        Status(long durationNs, long bytes) {
+            mDurationNs = durationNs;
+            mBytes = bytes;
+        }
+
+        /** {@inheritDoc} */
+        @NonNull
+        @Override
+        public EventType getEventType() {
+            return EventType.STATUS;
+        }
+
+        /**
+         * Gets the total recording duration in nanoseconds.
+         *
+         * <p>The duration will not include the duration of pause.
+         */
+        public long getRecordedDurationNs() {
+            return mDurationNs;
+        }
+
+        /**
+         * Gets the total recorded byte count.
+         */
+        public long getNumBytesRecorded() {
+            return mBytes;
+        }
+    }
+}
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index f9c39c9..9c7eaee 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -66,7 +66,7 @@
     field public static final int LENGTH_SHORT = 0; // 0x0
   }
 
-  public class FailureResponse {
+  public final class FailureResponse {
     ctor public FailureResponse(Throwable);
     method public int getErrorType();
     method public String getStackTrace();
@@ -79,19 +79,19 @@
     field public static final int UNKNOWN_ERROR = 0; // 0x0
   }
 
-  public class HandshakeInfo {
+  public final class HandshakeInfo {
     ctor public HandshakeInfo(String, int);
     method public int getHostCarAppApiLevel();
     method public String getHostPackageName();
   }
 
-  public class HostException extends java.lang.RuntimeException {
+  public final class HostException extends java.lang.RuntimeException {
     ctor public HostException(String);
     ctor public HostException(String, Throwable);
     ctor public HostException(Throwable);
   }
 
-  public class HostInfo {
+  public final class HostInfo {
     ctor public HostInfo(String, int);
     method public String getPackageName();
     method public int getUid();
@@ -145,7 +145,7 @@
     method public void onVisibleAreaChanged(android.graphics.Rect);
   }
 
-  public class SurfaceContainer {
+  public final class SurfaceContainer {
     ctor public SurfaceContainer(android.view.Surface?, int, int, int);
     method public int getDpi();
     method public int getHeight();
@@ -192,12 +192,12 @@
     method public androidx.car.app.model.Action.Builder setTitle(CharSequence?);
   }
 
-  public class ActionList {
+  public final class ActionList {
     method public static androidx.car.app.model.ActionList create(java.util.List<androidx.car.app.model.Action!>);
     method public java.util.List<androidx.car.app.model.Action!> getList();
   }
 
-  public class ActionStrip {
+  public final class ActionStrip {
     method public static androidx.car.app.model.ActionStrip.Builder builder();
     method public java.util.List<androidx.car.app.model.Action!> getActionList();
     method @Deprecated public java.util.List<java.lang.Object!> getActions();
@@ -210,7 +210,7 @@
     method public androidx.car.app.model.ActionStrip build();
   }
 
-  public class CarColor {
+  public final class CarColor {
     method public static androidx.car.app.model.CarColor createCustom(@ColorInt int, @ColorInt int);
     method @ColorInt public int getColor();
     method @ColorInt public int getColorDark();
@@ -232,7 +232,7 @@
     field public static final androidx.car.app.model.CarColor YELLOW;
   }
 
-  public class CarIcon {
+  public final class CarIcon {
     method public static androidx.car.app.model.CarIcon.Builder builder(androidx.core.graphics.drawable.IconCompat);
     method public androidx.core.graphics.drawable.IconCompat? getIcon();
     method public androidx.car.app.model.CarColor? getTint();
@@ -257,7 +257,7 @@
     method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor?);
   }
 
-  public class CarIconSpan extends androidx.car.app.model.CarSpan {
+  public final class CarIconSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon);
     method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon, int);
     method public int getAlignment();
@@ -279,7 +279,7 @@
     method public void updateDrawState(android.text.TextPaint);
   }
 
-  public class CarText {
+  public final class CarText {
     method public static androidx.car.app.model.CarText create(CharSequence);
     method @Deprecated public java.util.List<androidx.car.app.model.CarText.SpanWrapper!> getSpans();
     method @Deprecated public String getText();
@@ -296,7 +296,7 @@
     method public int getStart();
   }
 
-  public class DateTimeWithZone {
+  public final class DateTimeWithZone {
     method public static androidx.car.app.model.DateTimeWithZone create(long, @IntRange(from=0xffff02e0, to=64800) int, String);
     method public static androidx.car.app.model.DateTimeWithZone create(long, java.util.TimeZone);
     method @RequiresApi(26) public static androidx.car.app.model.DateTimeWithZone create(java.time.ZonedDateTime);
@@ -318,23 +318,23 @@
     field public static final int UNIT_YARDS = 7; // 0x7
   }
 
-  public class DistanceSpan extends androidx.car.app.model.CarSpan {
+  public final class DistanceSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.DistanceSpan create(androidx.car.app.model.Distance);
     method public androidx.car.app.model.Distance getDistance();
   }
 
-  public class DurationSpan extends androidx.car.app.model.CarSpan {
+  public final class DurationSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.DurationSpan create(long);
     method @RequiresApi(26) public static androidx.car.app.model.DurationSpan create(java.time.Duration);
     method public long getDurationSeconds();
   }
 
-  public class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
+  public final class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.ForegroundCarColorSpan create(androidx.car.app.model.CarColor);
     method public androidx.car.app.model.CarColor getColor();
   }
 
-  public class GridItem implements androidx.car.app.model.Item {
+  public final class GridItem implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.GridItem.Builder builder();
     method public androidx.car.app.model.CarIcon? getImage();
     method public int getImageType();
@@ -431,6 +431,7 @@
   }
 
   public static final class ListTemplate.Builder {
+    ctor public ListTemplate.Builder();
     method @Deprecated public androidx.car.app.model.ListTemplate.Builder addList(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.ListTemplate.Builder addSectionedList(androidx.car.app.model.SectionedItemList);
     method public androidx.car.app.model.ListTemplate build();
@@ -465,7 +466,7 @@
     method public androidx.car.app.model.MessageTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class Metadata {
+  public final class Metadata {
     method public static androidx.car.app.model.Metadata.Builder builder();
     method public androidx.car.app.model.Place? getPlace();
     method @Deprecated public androidx.car.app.model.Metadata.Builder newBuilder();
@@ -557,7 +558,7 @@
     method public void onClick();
   }
 
-  public class Place {
+  public final class Place {
     method @Deprecated public static androidx.car.app.model.Place.Builder builder(androidx.car.app.model.LatLng);
     method @Deprecated public androidx.car.app.model.LatLng getLatLng();
     method public androidx.car.app.model.CarLocation getLocation();
@@ -596,7 +597,7 @@
     method public androidx.car.app.model.PlaceListMapTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class PlaceMarker {
+  public final class PlaceMarker {
     method public static androidx.car.app.model.PlaceMarker.Builder builder();
     method public androidx.car.app.model.CarColor? getColor();
     method public static androidx.car.app.model.PlaceMarker getDefault();
@@ -616,7 +617,7 @@
     method public androidx.car.app.model.PlaceMarker.Builder setLabel(CharSequence?);
   }
 
-  public class Row implements androidx.car.app.model.Item {
+  public final class Row implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.Row.Builder builder();
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.Metadata getMetadata();
@@ -687,7 +688,7 @@
     method public void onSearchTextChanged(String);
   }
 
-  public class SectionedItemList {
+  public final class SectionedItemList {
     method @Deprecated public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, androidx.car.app.model.CarText);
     method public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.CarText getHeader();
@@ -718,7 +719,7 @@
     method public static androidx.car.app.model.TemplateWrapper wrap(androidx.car.app.model.Template, String);
   }
 
-  public class Toggle {
+  public final class Toggle {
     method public static androidx.car.app.model.Toggle.Builder builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
     method public androidx.car.app.model.OnCheckedChangeDelegate getOnCheckedChangeDelegate();
     method @Deprecated public androidx.car.app.model.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
@@ -867,7 +868,7 @@
     method public androidx.car.app.navigation.model.Maneuver.Builder setRoundaboutExitNumber(@IntRange(from=1) int);
   }
 
-  public class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+  public final class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
     method public static androidx.car.app.navigation.model.MessageInfo.Builder builder(CharSequence);
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.CarText? getText();
@@ -882,7 +883,7 @@
     method public androidx.car.app.navigation.model.MessageInfo.Builder setTitle(CharSequence);
   }
 
-  public class NavigationTemplate implements androidx.car.app.model.Template {
+  public final class NavigationTemplate implements androidx.car.app.model.Template {
     method public static androidx.car.app.navigation.model.NavigationTemplate.Builder builder();
     method public androidx.car.app.model.ActionStrip getActionStrip();
     method public androidx.car.app.model.CarColor? getBackgroundColor();
@@ -942,7 +943,7 @@
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+  public final class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
     method public static androidx.car.app.navigation.model.RoutingInfo.Builder builder();
     method public androidx.car.app.model.Distance? getCurrentDistance();
     method public androidx.car.app.navigation.model.Step? getCurrentStep();
@@ -1025,7 +1026,7 @@
 
 package androidx.car.app.notification {
 
-  public class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
+  public final class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
     ctor public CarAppExtender(android.app.Notification);
     method public static androidx.car.app.notification.CarAppExtender.Builder builder();
     method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
@@ -1098,7 +1099,7 @@
 
 package androidx.car.app.versioning {
 
-  public class CarAppApiLevels {
+  public final class CarAppApiLevels {
     method public static int getLatest();
     method public static int getOldest();
     field public static final int LEVEL_1 = 1; // 0x1
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index f9c39c9..9c7eaee 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -66,7 +66,7 @@
     field public static final int LENGTH_SHORT = 0; // 0x0
   }
 
-  public class FailureResponse {
+  public final class FailureResponse {
     ctor public FailureResponse(Throwable);
     method public int getErrorType();
     method public String getStackTrace();
@@ -79,19 +79,19 @@
     field public static final int UNKNOWN_ERROR = 0; // 0x0
   }
 
-  public class HandshakeInfo {
+  public final class HandshakeInfo {
     ctor public HandshakeInfo(String, int);
     method public int getHostCarAppApiLevel();
     method public String getHostPackageName();
   }
 
-  public class HostException extends java.lang.RuntimeException {
+  public final class HostException extends java.lang.RuntimeException {
     ctor public HostException(String);
     ctor public HostException(String, Throwable);
     ctor public HostException(Throwable);
   }
 
-  public class HostInfo {
+  public final class HostInfo {
     ctor public HostInfo(String, int);
     method public String getPackageName();
     method public int getUid();
@@ -145,7 +145,7 @@
     method public void onVisibleAreaChanged(android.graphics.Rect);
   }
 
-  public class SurfaceContainer {
+  public final class SurfaceContainer {
     ctor public SurfaceContainer(android.view.Surface?, int, int, int);
     method public int getDpi();
     method public int getHeight();
@@ -192,12 +192,12 @@
     method public androidx.car.app.model.Action.Builder setTitle(CharSequence?);
   }
 
-  public class ActionList {
+  public final class ActionList {
     method public static androidx.car.app.model.ActionList create(java.util.List<androidx.car.app.model.Action!>);
     method public java.util.List<androidx.car.app.model.Action!> getList();
   }
 
-  public class ActionStrip {
+  public final class ActionStrip {
     method public static androidx.car.app.model.ActionStrip.Builder builder();
     method public java.util.List<androidx.car.app.model.Action!> getActionList();
     method @Deprecated public java.util.List<java.lang.Object!> getActions();
@@ -210,7 +210,7 @@
     method public androidx.car.app.model.ActionStrip build();
   }
 
-  public class CarColor {
+  public final class CarColor {
     method public static androidx.car.app.model.CarColor createCustom(@ColorInt int, @ColorInt int);
     method @ColorInt public int getColor();
     method @ColorInt public int getColorDark();
@@ -232,7 +232,7 @@
     field public static final androidx.car.app.model.CarColor YELLOW;
   }
 
-  public class CarIcon {
+  public final class CarIcon {
     method public static androidx.car.app.model.CarIcon.Builder builder(androidx.core.graphics.drawable.IconCompat);
     method public androidx.core.graphics.drawable.IconCompat? getIcon();
     method public androidx.car.app.model.CarColor? getTint();
@@ -257,7 +257,7 @@
     method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor?);
   }
 
-  public class CarIconSpan extends androidx.car.app.model.CarSpan {
+  public final class CarIconSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon);
     method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon, int);
     method public int getAlignment();
@@ -279,7 +279,7 @@
     method public void updateDrawState(android.text.TextPaint);
   }
 
-  public class CarText {
+  public final class CarText {
     method public static androidx.car.app.model.CarText create(CharSequence);
     method @Deprecated public java.util.List<androidx.car.app.model.CarText.SpanWrapper!> getSpans();
     method @Deprecated public String getText();
@@ -296,7 +296,7 @@
     method public int getStart();
   }
 
-  public class DateTimeWithZone {
+  public final class DateTimeWithZone {
     method public static androidx.car.app.model.DateTimeWithZone create(long, @IntRange(from=0xffff02e0, to=64800) int, String);
     method public static androidx.car.app.model.DateTimeWithZone create(long, java.util.TimeZone);
     method @RequiresApi(26) public static androidx.car.app.model.DateTimeWithZone create(java.time.ZonedDateTime);
@@ -318,23 +318,23 @@
     field public static final int UNIT_YARDS = 7; // 0x7
   }
 
-  public class DistanceSpan extends androidx.car.app.model.CarSpan {
+  public final class DistanceSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.DistanceSpan create(androidx.car.app.model.Distance);
     method public androidx.car.app.model.Distance getDistance();
   }
 
-  public class DurationSpan extends androidx.car.app.model.CarSpan {
+  public final class DurationSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.DurationSpan create(long);
     method @RequiresApi(26) public static androidx.car.app.model.DurationSpan create(java.time.Duration);
     method public long getDurationSeconds();
   }
 
-  public class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
+  public final class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.ForegroundCarColorSpan create(androidx.car.app.model.CarColor);
     method public androidx.car.app.model.CarColor getColor();
   }
 
-  public class GridItem implements androidx.car.app.model.Item {
+  public final class GridItem implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.GridItem.Builder builder();
     method public androidx.car.app.model.CarIcon? getImage();
     method public int getImageType();
@@ -431,6 +431,7 @@
   }
 
   public static final class ListTemplate.Builder {
+    ctor public ListTemplate.Builder();
     method @Deprecated public androidx.car.app.model.ListTemplate.Builder addList(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.ListTemplate.Builder addSectionedList(androidx.car.app.model.SectionedItemList);
     method public androidx.car.app.model.ListTemplate build();
@@ -465,7 +466,7 @@
     method public androidx.car.app.model.MessageTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class Metadata {
+  public final class Metadata {
     method public static androidx.car.app.model.Metadata.Builder builder();
     method public androidx.car.app.model.Place? getPlace();
     method @Deprecated public androidx.car.app.model.Metadata.Builder newBuilder();
@@ -557,7 +558,7 @@
     method public void onClick();
   }
 
-  public class Place {
+  public final class Place {
     method @Deprecated public static androidx.car.app.model.Place.Builder builder(androidx.car.app.model.LatLng);
     method @Deprecated public androidx.car.app.model.LatLng getLatLng();
     method public androidx.car.app.model.CarLocation getLocation();
@@ -596,7 +597,7 @@
     method public androidx.car.app.model.PlaceListMapTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class PlaceMarker {
+  public final class PlaceMarker {
     method public static androidx.car.app.model.PlaceMarker.Builder builder();
     method public androidx.car.app.model.CarColor? getColor();
     method public static androidx.car.app.model.PlaceMarker getDefault();
@@ -616,7 +617,7 @@
     method public androidx.car.app.model.PlaceMarker.Builder setLabel(CharSequence?);
   }
 
-  public class Row implements androidx.car.app.model.Item {
+  public final class Row implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.Row.Builder builder();
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.Metadata getMetadata();
@@ -687,7 +688,7 @@
     method public void onSearchTextChanged(String);
   }
 
-  public class SectionedItemList {
+  public final class SectionedItemList {
     method @Deprecated public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, androidx.car.app.model.CarText);
     method public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.CarText getHeader();
@@ -718,7 +719,7 @@
     method public static androidx.car.app.model.TemplateWrapper wrap(androidx.car.app.model.Template, String);
   }
 
-  public class Toggle {
+  public final class Toggle {
     method public static androidx.car.app.model.Toggle.Builder builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
     method public androidx.car.app.model.OnCheckedChangeDelegate getOnCheckedChangeDelegate();
     method @Deprecated public androidx.car.app.model.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
@@ -867,7 +868,7 @@
     method public androidx.car.app.navigation.model.Maneuver.Builder setRoundaboutExitNumber(@IntRange(from=1) int);
   }
 
-  public class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+  public final class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
     method public static androidx.car.app.navigation.model.MessageInfo.Builder builder(CharSequence);
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.CarText? getText();
@@ -882,7 +883,7 @@
     method public androidx.car.app.navigation.model.MessageInfo.Builder setTitle(CharSequence);
   }
 
-  public class NavigationTemplate implements androidx.car.app.model.Template {
+  public final class NavigationTemplate implements androidx.car.app.model.Template {
     method public static androidx.car.app.navigation.model.NavigationTemplate.Builder builder();
     method public androidx.car.app.model.ActionStrip getActionStrip();
     method public androidx.car.app.model.CarColor? getBackgroundColor();
@@ -942,7 +943,7 @@
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+  public final class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
     method public static androidx.car.app.navigation.model.RoutingInfo.Builder builder();
     method public androidx.car.app.model.Distance? getCurrentDistance();
     method public androidx.car.app.navigation.model.Step? getCurrentStep();
@@ -1025,7 +1026,7 @@
 
 package androidx.car.app.notification {
 
-  public class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
+  public final class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
     ctor public CarAppExtender(android.app.Notification);
     method public static androidx.car.app.notification.CarAppExtender.Builder builder();
     method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
@@ -1098,7 +1099,7 @@
 
 package androidx.car.app.versioning {
 
-  public class CarAppApiLevels {
+  public final class CarAppApiLevels {
     method public static int getLatest();
     method public static int getOldest();
     field public static final int LEVEL_1 = 1; // 0x1
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index f9c39c9..9c7eaee 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -66,7 +66,7 @@
     field public static final int LENGTH_SHORT = 0; // 0x0
   }
 
-  public class FailureResponse {
+  public final class FailureResponse {
     ctor public FailureResponse(Throwable);
     method public int getErrorType();
     method public String getStackTrace();
@@ -79,19 +79,19 @@
     field public static final int UNKNOWN_ERROR = 0; // 0x0
   }
 
-  public class HandshakeInfo {
+  public final class HandshakeInfo {
     ctor public HandshakeInfo(String, int);
     method public int getHostCarAppApiLevel();
     method public String getHostPackageName();
   }
 
-  public class HostException extends java.lang.RuntimeException {
+  public final class HostException extends java.lang.RuntimeException {
     ctor public HostException(String);
     ctor public HostException(String, Throwable);
     ctor public HostException(Throwable);
   }
 
-  public class HostInfo {
+  public final class HostInfo {
     ctor public HostInfo(String, int);
     method public String getPackageName();
     method public int getUid();
@@ -145,7 +145,7 @@
     method public void onVisibleAreaChanged(android.graphics.Rect);
   }
 
-  public class SurfaceContainer {
+  public final class SurfaceContainer {
     ctor public SurfaceContainer(android.view.Surface?, int, int, int);
     method public int getDpi();
     method public int getHeight();
@@ -192,12 +192,12 @@
     method public androidx.car.app.model.Action.Builder setTitle(CharSequence?);
   }
 
-  public class ActionList {
+  public final class ActionList {
     method public static androidx.car.app.model.ActionList create(java.util.List<androidx.car.app.model.Action!>);
     method public java.util.List<androidx.car.app.model.Action!> getList();
   }
 
-  public class ActionStrip {
+  public final class ActionStrip {
     method public static androidx.car.app.model.ActionStrip.Builder builder();
     method public java.util.List<androidx.car.app.model.Action!> getActionList();
     method @Deprecated public java.util.List<java.lang.Object!> getActions();
@@ -210,7 +210,7 @@
     method public androidx.car.app.model.ActionStrip build();
   }
 
-  public class CarColor {
+  public final class CarColor {
     method public static androidx.car.app.model.CarColor createCustom(@ColorInt int, @ColorInt int);
     method @ColorInt public int getColor();
     method @ColorInt public int getColorDark();
@@ -232,7 +232,7 @@
     field public static final androidx.car.app.model.CarColor YELLOW;
   }
 
-  public class CarIcon {
+  public final class CarIcon {
     method public static androidx.car.app.model.CarIcon.Builder builder(androidx.core.graphics.drawable.IconCompat);
     method public androidx.core.graphics.drawable.IconCompat? getIcon();
     method public androidx.car.app.model.CarColor? getTint();
@@ -257,7 +257,7 @@
     method public androidx.car.app.model.CarIcon.Builder setTint(androidx.car.app.model.CarColor?);
   }
 
-  public class CarIconSpan extends androidx.car.app.model.CarSpan {
+  public final class CarIconSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon);
     method public static androidx.car.app.model.CarIconSpan create(androidx.car.app.model.CarIcon, int);
     method public int getAlignment();
@@ -279,7 +279,7 @@
     method public void updateDrawState(android.text.TextPaint);
   }
 
-  public class CarText {
+  public final class CarText {
     method public static androidx.car.app.model.CarText create(CharSequence);
     method @Deprecated public java.util.List<androidx.car.app.model.CarText.SpanWrapper!> getSpans();
     method @Deprecated public String getText();
@@ -296,7 +296,7 @@
     method public int getStart();
   }
 
-  public class DateTimeWithZone {
+  public final class DateTimeWithZone {
     method public static androidx.car.app.model.DateTimeWithZone create(long, @IntRange(from=0xffff02e0, to=64800) int, String);
     method public static androidx.car.app.model.DateTimeWithZone create(long, java.util.TimeZone);
     method @RequiresApi(26) public static androidx.car.app.model.DateTimeWithZone create(java.time.ZonedDateTime);
@@ -318,23 +318,23 @@
     field public static final int UNIT_YARDS = 7; // 0x7
   }
 
-  public class DistanceSpan extends androidx.car.app.model.CarSpan {
+  public final class DistanceSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.DistanceSpan create(androidx.car.app.model.Distance);
     method public androidx.car.app.model.Distance getDistance();
   }
 
-  public class DurationSpan extends androidx.car.app.model.CarSpan {
+  public final class DurationSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.DurationSpan create(long);
     method @RequiresApi(26) public static androidx.car.app.model.DurationSpan create(java.time.Duration);
     method public long getDurationSeconds();
   }
 
-  public class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
+  public final class ForegroundCarColorSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.ForegroundCarColorSpan create(androidx.car.app.model.CarColor);
     method public androidx.car.app.model.CarColor getColor();
   }
 
-  public class GridItem implements androidx.car.app.model.Item {
+  public final class GridItem implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.GridItem.Builder builder();
     method public androidx.car.app.model.CarIcon? getImage();
     method public int getImageType();
@@ -431,6 +431,7 @@
   }
 
   public static final class ListTemplate.Builder {
+    ctor public ListTemplate.Builder();
     method @Deprecated public androidx.car.app.model.ListTemplate.Builder addList(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.ListTemplate.Builder addSectionedList(androidx.car.app.model.SectionedItemList);
     method public androidx.car.app.model.ListTemplate build();
@@ -465,7 +466,7 @@
     method public androidx.car.app.model.MessageTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class Metadata {
+  public final class Metadata {
     method public static androidx.car.app.model.Metadata.Builder builder();
     method public androidx.car.app.model.Place? getPlace();
     method @Deprecated public androidx.car.app.model.Metadata.Builder newBuilder();
@@ -557,7 +558,7 @@
     method public void onClick();
   }
 
-  public class Place {
+  public final class Place {
     method @Deprecated public static androidx.car.app.model.Place.Builder builder(androidx.car.app.model.LatLng);
     method @Deprecated public androidx.car.app.model.LatLng getLatLng();
     method public androidx.car.app.model.CarLocation getLocation();
@@ -596,7 +597,7 @@
     method public androidx.car.app.model.PlaceListMapTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class PlaceMarker {
+  public final class PlaceMarker {
     method public static androidx.car.app.model.PlaceMarker.Builder builder();
     method public androidx.car.app.model.CarColor? getColor();
     method public static androidx.car.app.model.PlaceMarker getDefault();
@@ -616,7 +617,7 @@
     method public androidx.car.app.model.PlaceMarker.Builder setLabel(CharSequence?);
   }
 
-  public class Row implements androidx.car.app.model.Item {
+  public final class Row implements androidx.car.app.model.Item {
     method public static androidx.car.app.model.Row.Builder builder();
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.Metadata getMetadata();
@@ -687,7 +688,7 @@
     method public void onSearchTextChanged(String);
   }
 
-  public class SectionedItemList {
+  public final class SectionedItemList {
     method @Deprecated public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, androidx.car.app.model.CarText);
     method public static androidx.car.app.model.SectionedItemList create(androidx.car.app.model.ItemList, CharSequence);
     method public androidx.car.app.model.CarText getHeader();
@@ -718,7 +719,7 @@
     method public static androidx.car.app.model.TemplateWrapper wrap(androidx.car.app.model.Template, String);
   }
 
-  public class Toggle {
+  public final class Toggle {
     method public static androidx.car.app.model.Toggle.Builder builder(androidx.car.app.model.Toggle.OnCheckedChangeListener);
     method public androidx.car.app.model.OnCheckedChangeDelegate getOnCheckedChangeDelegate();
     method @Deprecated public androidx.car.app.model.OnCheckedChangeListenerWrapper getOnCheckedChangeListener();
@@ -867,7 +868,7 @@
     method public androidx.car.app.navigation.model.Maneuver.Builder setRoundaboutExitNumber(@IntRange(from=1) int);
   }
 
-  public class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+  public final class MessageInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
     method public static androidx.car.app.navigation.model.MessageInfo.Builder builder(CharSequence);
     method public androidx.car.app.model.CarIcon? getImage();
     method public androidx.car.app.model.CarText? getText();
@@ -882,7 +883,7 @@
     method public androidx.car.app.navigation.model.MessageInfo.Builder setTitle(CharSequence);
   }
 
-  public class NavigationTemplate implements androidx.car.app.model.Template {
+  public final class NavigationTemplate implements androidx.car.app.model.Template {
     method public static androidx.car.app.navigation.model.NavigationTemplate.Builder builder();
     method public androidx.car.app.model.ActionStrip getActionStrip();
     method public androidx.car.app.model.CarColor? getBackgroundColor();
@@ -942,7 +943,7 @@
     method public androidx.car.app.navigation.model.RoutePreviewNavigationTemplate.Builder setTitle(CharSequence?);
   }
 
-  public class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
+  public final class RoutingInfo implements androidx.car.app.navigation.model.NavigationTemplate.NavigationInfo {
     method public static androidx.car.app.navigation.model.RoutingInfo.Builder builder();
     method public androidx.car.app.model.Distance? getCurrentDistance();
     method public androidx.car.app.navigation.model.Step? getCurrentStep();
@@ -1025,7 +1026,7 @@
 
 package androidx.car.app.notification {
 
-  public class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
+  public final class CarAppExtender implements androidx.core.app.NotificationCompat.Extender {
     ctor public CarAppExtender(android.app.Notification);
     method public static androidx.car.app.notification.CarAppExtender.Builder builder();
     method public androidx.core.app.NotificationCompat.Builder extend(androidx.core.app.NotificationCompat.Builder);
@@ -1098,7 +1099,7 @@
 
 package androidx.car.app.versioning {
 
-  public class CarAppApiLevels {
+  public final class CarAppApiLevels {
     method public static int getLatest();
     method public static int getOldest();
     field public static final int LEVEL_1 = 1; // 0x1
diff --git a/car/app/app/src/main/java/androidx/car/app/AppManager.java b/car/app/app/src/main/java/androidx/car/app/AppManager.java
index 086b6a3..169b87a 100644
--- a/car/app/app/src/main/java/androidx/car/app/AppManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/AppManager.java
@@ -53,8 +53,8 @@
      * using {@link Looper#getMainLooper()}.
      *
      * @throws SecurityException if the app does not have the required permissions to access the
-     *                           surface.
-     * @throws HostException     if the remote call fails.
+     *                           surface
+     * @throws HostException     if the remote call fails
      */
     @SuppressLint("ExecutorRegistration")
     public void setSurfaceCallback(@Nullable SurfaceCallback surfaceCallback) {
@@ -71,7 +71,7 @@
      * Requests the current template to be invalidated, which eventually triggers a call to {@link
      * Screen#onGetTemplate} to get the new template to display.
      *
-     * @throws HostException if the remote call fails.
+     * @throws HostException if the remote call fails
      */
     public void invalidate() {
         mHostDispatcher.dispatch(
@@ -86,9 +86,10 @@
     /**
      * Shows a toast on the car screen.
      *
-     * @param text     the text to show.
-     * @param duration how long to display the message.
-     * @throws HostException if the remote call fails.
+     * @param text     the text to show
+     * @param duration how long to display the message
+     *
+     * @throws HostException if the remote call fails
      */
     public void showToast(@NonNull CharSequence text, int duration) {
         mHostDispatcher.dispatch(
diff --git a/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java b/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java
index 236ebc1..4e27ee0 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java
@@ -80,7 +80,7 @@
     /**
      * Checks that the car app has the given {@code permission} granted.
      *
-     * @throws SecurityException if the app does not have a required permission granted.
+     * @throws SecurityException if the app does not have a required permission granted
      */
     public static void checkHasPermission(@NonNull Context context, @NonNull String permission) {
         if (context.getPackageManager().checkPermission(permission, context.getPackageName())
@@ -98,7 +98,7 @@
      * <p>In contrast to {@link #checkHasPermission}, this method will validate that the app has at
      * least declared the permission requested.
      *
-     * @throws SecurityException if the app does not have the required permission declared.
+     * @throws SecurityException if the app does not have the required permission declared
      */
     public static void checkHasLibraryPermission(
             @NonNull Context context, @NonNull @LibraryPermission String permission) {
diff --git a/car/app/app/src/main/java/androidx/car/app/CarContext.java b/car/app/app/src/main/java/androidx/car/app/CarContext.java
index 1ad033c..ec1e87a 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarContext.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarContext.java
@@ -157,9 +157,11 @@
      * @param name The name of the car service requested. This should be one of
      *             {@link #APP_SERVICE},
      *             {@link #NAVIGATION_SERVICE} or {@link #SCREEN_SERVICE}.
-     * @return The car service instance.
-     * @throws IllegalArgumentException if {@code name} does not refer to a valid car service.
-     * @throws NullPointerException     if {@code name} is {@code null}.
+     *
+     * @return The car service instance
+     *
+     * @throws IllegalArgumentException if {@code name} does not refer to a valid car service
+     * @throws NullPointerException     if {@code name} is {@code null}
      */
     // This is kept for the testing library.
     @NonNull
@@ -184,11 +186,13 @@
      * <p>Currently supported classes are: {@link AppManager}, {@link NavigationManager}, {@link
      * ScreenManager}.
      *
-     * @param serviceClass the class of the requested service.
-     * @return The car service instance.
+     * @param serviceClass the class of the requested service
+     *
+     * @return The car service instance
+     *
      * @throws IllegalArgumentException if {@code serviceClass} is not the class of a supported car
-     *                                  service.
-     * @throws NullPointerException     if {@code serviceClass} is {@code null}.
+     *                                  service
+     * @throws NullPointerException     if {@code serviceClass} is {@code null}
      */
     @NonNull
     public <T> T getCarService(@NonNull Class<T> serviceClass) {
@@ -198,11 +202,14 @@
     /**
      * Gets the name of the car service that is represented by the specified class.
      *
-     * @param serviceClass the class of the requested service.
-     * @return the car service name to use with {@link #getCarService(String)}.
+     * @param serviceClass the class of the requested service
+     *
+     * @return the car service name to use with {@link #getCarService(String)}
+     *
      * @throws IllegalArgumentException if {@code serviceClass} is not the class of a supported car
-     *                                  service.
-     * @throws NullPointerException     if {@code serviceClass} is {@code null}.
+     *                                  service
+     * @throws NullPointerException     if {@code serviceClass} is {@code null}
+     *
      * @see #getCarService
      */
     @NonNull
@@ -246,12 +253,12 @@
      *       method will throw a {@link SecurityException}.
      * </dl>
      *
-     * @param intent the {@link Intent} to send to the target application.
+     * @param intent the {@link Intent} to send to the target application
+     *
      * @throws SecurityException         if the app attempts to start a different app explicitly or
-     *                                   does not
-     *                                   have permissions for the requested action.
-     * @throws InvalidParameterException if {@code intent} does not meet the criteria defined.
-     * @throws NullPointerException      if {@code intent} is {@code null}.
+     *                                   does not have permissions for the requested action
+     * @throws InvalidParameterException if {@code intent} does not meet the criteria defined
+     * @throws NullPointerException      if {@code intent} is {@code null}
      */
     public void startCarApp(@NonNull Intent intent) {
         requireNonNull(intent);
@@ -271,15 +278,16 @@
      * <p>Use this method if the app has received a broadcast due to a notification action.
      *
      * @param notificationIntent the {@link Intent} that the app received via broadcast due to a
-     *                           user taking an action on a notification in the car.
+     *                           user taking an action on a notification in the car
      * @param appIntent          the {@link Intent} to use for starting the car app. See {@link
      *                           #startCarApp(Intent)} for the documentation on valid
-     *                           {@link Intent}s.
+     *                           {@link Intent}s
+     *
      * @throws InvalidParameterException if {@code notificationIntent} is not an {@link Intent}
      *                                   received from a broadcast, due to an action taken by the
-     *                                   user in the car.
+     *                                   user in the car
      * @throws NullPointerException      if either {@code notificationIntent} or {@code appIntent
-     *                                   } are {@code null}.
+     *                                   } are {@code null}
      */
     public static void startCarApp(@NonNull Intent notificationIntent, @NonNull Intent appIntent) {
         requireNonNull(notificationIntent);
@@ -468,10 +476,11 @@
      *
      * @return a value between {@link AppInfo#getMinCarAppApiLevel()} and
      * {@link AppInfo#getLatestCarAppApiLevel()}. In case of incompatibility, the host will
-     * disconnect from the service before completing the handshake.
+     * disconnect from the service before completing the handshake
+     *
      * @throws IllegalStateException if invoked before the connection handshake with the host has
      *                               been completed (for example, before
-     *                               {@link Session#onCreateScreen(Intent)}).
+     *                               {@link Session#onCreateScreen(Intent)})
      */
     @CarAppApiLevel
     public int getCarAppApiLevel() {
diff --git a/car/app/app/src/main/java/androidx/car/app/CarToast.java b/car/app/app/src/main/java/androidx/car/app/CarToast.java
index 0a904fe..c852365 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarToast.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarToast.java
@@ -62,7 +62,7 @@
      * Constructs an empty Toast object. You <strong>MUST</strong> call {@link #setText} before you
      * can call {@link #show}.
      *
-     * @throws NullPointerException if {@code carContext} is {@code null}.
+     * @throws NullPointerException if {@code carContext} is {@code null}
      */
     @VisibleForTesting
     CarToast(@NonNull CarContext carContext) {
@@ -73,10 +73,11 @@
      * Creates and sets the text and duration for the toast view.
      *
      * @param textResId the resource id for the text to show. If the {@code textResId} is 0, the
-     *                  text will be set to empty.
+     *                  text will be set to empty
      * @param duration  how long to display the message. Either {@link #LENGTH_SHORT} or {@link
      *                  #LENGTH_LONG}
-     * @throws NullPointerException if {@code carContext} is {@code null}.
+     *
+     * @throws NullPointerException if {@code carContext} is {@code null}
      */
     @NonNull
     public static CarToast makeText(
@@ -93,8 +94,9 @@
      * @param text     the text to show
      * @param duration how long to display the message. Either {@link #LENGTH_SHORT} or {@link
      *                 #LENGTH_LONG}
+     *
      * @throws NullPointerException if either the {@code carContext} or the {@code text} are {@code
-     *                              null}.
+     *                              null}
      */
     @NonNull
     public static CarToast makeText(
@@ -109,7 +111,7 @@
      * Sets the text for the toast.
      *
      * @param textResId the resource id for the text. If the {@code textResId} is 0, the text
-     *                  will be set to empty.
+     *                  will be set to empty
      */
     public void setText(@StringRes int textResId) {
         mText = textResId == 0 ? "" : mCarContext.getString(textResId);
@@ -118,7 +120,7 @@
     /**
      * Sets the text for the toast.
      *
-     * @throws NullPointerException if {@code text} is {@code null}.
+     * @throws NullPointerException if {@code text} is {@code null}
      */
     public void setText(@NonNull CharSequence text) {
         this.mText = requireNonNull(text);
@@ -137,7 +139,7 @@
     /**
      * Shows the toast with the specified text for the specified duration.
      *
-     * @throws HostException if the remote call fails.
+     * @throws HostException if the remote call fails
      */
     public void show() {
         CharSequence text = this.mText;
diff --git a/car/app/app/src/main/java/androidx/car/app/FailureResponse.java b/car/app/app/src/main/java/androidx/car/app/FailureResponse.java
index e066ce2..72bd18b 100644
--- a/car/app/app/src/main/java/androidx/car/app/FailureResponse.java
+++ b/car/app/app/src/main/java/androidx/car/app/FailureResponse.java
@@ -40,7 +40,7 @@
  *
  * <p>This is used for the failure response for a IOnDoneCallback.
  */
-public class FailureResponse {
+public final class FailureResponse {
     /**
      * The exception type of the failure.
      *
diff --git a/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java b/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java
index 7349c40..b025e54 100644
--- a/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/HandshakeInfo.java
@@ -26,7 +26,7 @@
  * A container for the information conveyed by the host after the handshake with the app is
  * completed.
  */
-public class HandshakeInfo {
+public final class HandshakeInfo {
     @Keep
     @Nullable
     private final String mHostPackageName;
diff --git a/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java b/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java
index 3453d3b..890b0c8 100644
--- a/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java
+++ b/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java
@@ -41,7 +41,7 @@
  * @hide
  */
 @RestrictTo(LIBRARY_GROUP) // Restrict to testing library
-public class HostDispatcher {
+public final class HostDispatcher {
     @Nullable
     private ICarHost mCarHost;
     @Nullable
@@ -52,12 +52,13 @@
     /**
      * Dispatches the {@code call} to the host for the given {@code hostType}.
      *
-     * @param hostType the service to dispatch to.
-     * @param call     the request to dispatch.
-     * @param callName the name of the call for logging purposes.
-     * @throws SecurityException if the host has thrown it.
+     * @param hostType the service to dispatch to
+     * @param call     the request to dispatch
+     * @param callName the name of the call for logging purposes
+     *
+     * @throws SecurityException if the host has thrown it
      * @throws HostException     if the host throws any exception other than
-     *                           {@link SecurityException}.
+     *                           {@link SecurityException}
      */
     @Nullable
     @SuppressWarnings({"unchecked", "cast.unsafe"}) // Cannot check if instanceof ServiceT
diff --git a/car/app/app/src/main/java/androidx/car/app/HostException.java b/car/app/app/src/main/java/androidx/car/app/HostException.java
index 092ac41..bc8bb8c 100644
--- a/car/app/app/src/main/java/androidx/car/app/HostException.java
+++ b/car/app/app/src/main/java/androidx/car/app/HostException.java
@@ -19,8 +19,7 @@
 import androidx.annotation.NonNull;
 
 /** Exceptions that happen on calls to the host. */
-// Developers can catch this exception so keeping it.
-public class HostException extends RuntimeException {
+public final class HostException extends RuntimeException {
     /**
      * Creates an instance of {@link HostException} with the given {@code message}.
      *
diff --git a/car/app/app/src/main/java/androidx/car/app/HostInfo.java b/car/app/app/src/main/java/androidx/car/app/HostInfo.java
index 9accd50..79f8db4 100644
--- a/car/app/app/src/main/java/androidx/car/app/HostInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/HostInfo.java
@@ -33,7 +33,7 @@
  * <p>The host API level can be used to adjust the models exchanged with the host to those valid
  * for the specific host version the app is connected to.
  */
-public class HostInfo {
+public final class HostInfo {
     @NonNull
     private final String mPackageName;
     private final int mUid;
diff --git a/car/app/app/src/main/java/androidx/car/app/Screen.java b/car/app/app/src/main/java/androidx/car/app/Screen.java
index b2cfdea..6b2d513 100644
--- a/car/app/app/src/main/java/androidx/car/app/Screen.java
+++ b/car/app/app/src/main/java/androidx/car/app/Screen.java
@@ -99,7 +99,7 @@
      * <p>To avoid race conditions with calls to {@link #onGetTemplate} you should call this method
      * with the main thread.
      *
-     * @throws HostException if the remote call fails.
+     * @throws HostException if the remote call fails
      */
     public final void invalidate() {
         if (getLifecycle().getCurrentState().isAtLeast(State.STARTED)) {
diff --git a/car/app/app/src/main/java/androidx/car/app/ScreenManager.java b/car/app/app/src/main/java/androidx/car/app/ScreenManager.java
index 9d67e6c..2ccb296 100644
--- a/car/app/app/src/main/java/androidx/car/app/ScreenManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/ScreenManager.java
@@ -57,8 +57,8 @@
      * @throws NullPointerException  if the method is called before a {@link Screen} has been
      *                               pushed to the stack via {@link #push}, or
      *                               {@link #pushForResult}, or returning a {@link Screen} from
-     *                               {@link Session#onCreateScreen}.
-     * @throws IllegalStateException if the current thread is not the main thread.
+     *                               {@link Session#onCreateScreen}
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     @NonNull
     public Screen getTop() {
@@ -72,8 +72,8 @@
      * <p>If the {@code screen} pushed is already in the stack it will be moved to the top of the
      * stack.
      *
-     * @throws NullPointerException  if {@code screen} is {@code null}.
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws NullPointerException  if {@code screen} is {@code null}
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     public void push(@NonNull Screen screen) {
         checkMainThread();
@@ -87,13 +87,14 @@
      * callback to {@link OnScreenResultListener#onScreenResult} with the result that the pushed
      * {@code screen} set via {@link Screen#setResult}.
      *
-     * @param screen                 the {@link Screen} to push on top of the stack.
+     * @param screen                 the {@link Screen} to push on top of the stack
      * @param onScreenResultListener the listener that will be executed with the result pushed by
      *                               the {@code screen} through {@link Screen#setResult}. This
-     *                               callback will be executed on the main thread.
+     *                               callback will be executed on the main thread
+     *
      * @throws NullPointerException  if either the {@code screen} or the {@code
-     *                               onScreenResultCallback} are {@code null}.
-     * @throws IllegalStateException if the current thread is not the main thread.
+     *                               onScreenResultCallback} are {@code null}
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     @SuppressLint("ExecutorRegistration")
     public void pushForResult(
@@ -108,7 +109,7 @@
      *
      * <p>If the top {@link Screen} is the only {@link Screen} in the stack, it will not be removed.
      *
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     public void pop() {
         checkMainThread();
@@ -123,8 +124,9 @@
      *
      * <p>The root {@link Screen} will not be popped.
      *
-     * @throws NullPointerException  if {@code marker} is {@code null}.
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws NullPointerException  if {@code marker} is {@code null}
+     * @throws IllegalStateException if the current thread is not the main thread
+     *
      * @see Screen#setMarker
      */
     public void popTo(@NonNull String marker) {
@@ -148,7 +150,7 @@
     /**
      * Removes all screens from the stack until the root has been reached.
      *
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     public void popToRoot() {
         checkMainThread();
@@ -171,8 +173,8 @@
      *
      * <p>If the {@code screen} is the only {@link Screen} in the stack, it will not be removed.
      *
-     * @throws NullPointerException  if {@code screen} is {@code null}.
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws NullPointerException  if {@code screen} is {@code null}
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     public void remove(@NonNull Screen screen) {
         checkMainThread();
diff --git a/car/app/app/src/main/java/androidx/car/app/SurfaceContainer.java b/car/app/app/src/main/java/androidx/car/app/SurfaceContainer.java
index f4b65ee..aad74ca 100644
--- a/car/app/app/src/main/java/androidx/car/app/SurfaceContainer.java
+++ b/car/app/app/src/main/java/androidx/car/app/SurfaceContainer.java
@@ -22,7 +22,7 @@
 import androidx.annotation.Nullable;
 
 /** A container for the {@link Surface} created by the host and its associated properties. */
-public class SurfaceContainer {
+public final class SurfaceContainer {
     @Keep
     @Nullable
     private final Surface mSurface;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Action.java b/car/app/app/src/main/java/androidx/car/app/model/Action.java
index 53772a5..54beb5f 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Action.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Action.java
@@ -372,13 +372,16 @@
          * <h4>Requirements</h4>
          *
          * <p>The host may ignore this color and use the default instead if the color does not
-         * pass the
-         * contrast requirements.
+         * pass the contrast requirements.
+         *
+         * <p>Note the color of the text cannot be specified. Host implementations may pick the
+         * dark or light versions of the given background color as needed.
          *
          * @param backgroundColor the {@link CarColor} to set as background. Use {@link
-         *                        CarColor#DEFAULT} to let the host pick a default.
-         * @throws IllegalArgumentException if {@code backgroundColor} is not a standard color.
-         * @throws NullPointerException     if {@code backgroundColor} is {@code null}.
+         *                        CarColor#DEFAULT} to let the host pick a default
+         *
+         * @throws IllegalArgumentException if {@code backgroundColor} is not a standard color
+         * @throws NullPointerException     if {@code backgroundColor} is {@code null}
          */
         @NonNull
         public Builder setBackgroundColor(@NonNull CarColor backgroundColor) {
@@ -391,11 +394,9 @@
          * Constructs the {@link Action} defined by this builder.
          *
          * @throws IllegalStateException if the action is not a standard action and does not have an
-         *                               icon or a title.
-         * @throws IllegalStateException if a listener is set on either {@link #APP_ICON} or {@link
-         *                               #BACK}.
-         * @throws IllegalStateException if an icon or title is set on either {@link #APP_ICON} or
-         *                               {@link #BACK}.
+         *                               icon or a title, if a listener is set on either
+         *                               {@link #APP_ICON} or {@link #BACK}, or if an icon or
+         *                               title is set on either {@link #APP_ICON} or {@link #BACK}
          */
         @NonNull
         public Action build() {
@@ -430,7 +431,7 @@
          * Returns a {@link Builder} instance configured with the same data as the given
          * {@link Action} instance.
          *
-         * @throws NullPointerException if {@code icon} is {@code null}.
+         * @throws NullPointerException if {@code icon} is {@code null}
          */
         @SuppressWarnings("deprecation")
         Builder(@NonNull Action action) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ActionList.java b/car/app/app/src/main/java/androidx/car/app/model/ActionList.java
index c524260..7a68796 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ActionList.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ActionList.java
@@ -33,7 +33,7 @@
  * <p>This model is intended for internal and host use only, as a transport artifact for
  * homogeneous lists of {@link Action} items.
  */
-public class ActionList {
+public final class ActionList {
     @Keep
     private final List<Action> mList;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java b/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java
index 89639e1..1284c40 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ActionStrip.java
@@ -41,7 +41,7 @@
  * <p>See the documentation of individual {@link Template}s on restrictions around what actions are
  * supported.
  */
-public class ActionStrip {
+public final class ActionStrip {
     @Keep
     private final List<Action> mActions;
 
@@ -132,9 +132,9 @@
         /**
          * Adds an {@link Action} to the list.
          *
-         * @throws IllegalArgumentException if the background color of the action is specified.
-         * @throws IllegalArgumentException if {@code action} is a standard action and an action of
-         *                                  the same type has already been added.
+         * @throws IllegalArgumentException if the background color of the action is specified,
+         *                                  or if {@code action} is a standard action and an
+         *                                  action of the same type has already been added.
          * @throws NullPointerException     if {@code action} is {@code null}.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarColor.java b/car/app/app/src/main/java/androidx/car/app/model/CarColor.java
index 24020e2..b452602 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarColor.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarColor.java
@@ -87,7 +87,7 @@
  * #createCustom}. Wherever custom colors are used by the app, the host may use a default color
  * instead if the custom color does not pass the contrast requirements.
  */
-public class CarColor {
+public final class CarColor {
     /**
      * The type of color represented by the {@link CarColor} instance.
      *
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java b/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
index b858168..847fbc6 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarIcon.java
@@ -92,7 +92,7 @@
  * </resources>
  * }</pre>
  */
-public class CarIcon {
+public final class CarIcon {
     /** Matches with {@link android.graphics.drawable.Icon#TYPE_RESOURCE} */
     private static final int TYPE_RESOURCE = 2;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java b/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java
index 96ee074..cd542c1 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java
@@ -58,7 +58,7 @@
  *
  * @see CarIcon
  */
-public class CarIconSpan extends CarSpan {
+public final class CarIconSpan extends CarSpan {
     /**
      * Indicates how to align a car icon span with its surrounding text.
      *
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarText.java b/car/app/app/src/main/java/androidx/car/app/model/CarText.java
index 5216441..698ea9e 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarText.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarText.java
@@ -33,7 +33,7 @@
 /**
  * A model used to send text with attached spans to the host.
  */
-public class CarText {
+public final class CarText {
     @Keep
     private final String mText;
     @Keep
diff --git a/car/app/app/src/main/java/androidx/car/app/model/DateTimeWithZone.java b/car/app/app/src/main/java/androidx/car/app/model/DateTimeWithZone.java
index 557dd5b..b62f7b9 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/DateTimeWithZone.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/DateTimeWithZone.java
@@ -51,7 +51,7 @@
  * library such as Joda time, {@link #create(long, int, String)} can be used.
  */
 @SuppressWarnings("MissingSummary")
-public class DateTimeWithZone {
+public final class DateTimeWithZone {
     /** The maximum allowed offset for a time zone, in seconds. */
     private static final long MAX_ZONE_OFFSET_SECONDS = 18 * HOURS.toSeconds(1);
 
@@ -132,11 +132,10 @@
      *                             Pacific Standard Time. This string may be used to display to
      *                             the user along with the date when needed, for example, if this
      *                             time zone is different than the current system time zone.
-     * @throws IllegalArgumentException if {@code timeSinceEpochMillis} is a negative value.
-     * @throws IllegalArgumentException if {@code zoneOffsetSeconds} is no within the required
-     *                                  range.
+     * @throws IllegalArgumentException if {@code timeSinceEpochMillis} is a negative value, if
+     *                                  {@code zoneOffsetSeconds} is not within the required range,
+     *                                  or if {@code zoneShortName} is empty.
      * @throws NullPointerException     if {@code zoneShortName} is {@code null}.
-     * @throws IllegalArgumentException if {@code zoneShortName} is empty.
      */
     @NonNull
     public static DateTimeWithZone create(
@@ -161,11 +160,10 @@
      * @param timeSinceEpochMillis The number of milliseconds from the epoch of
      *                             1970-01-01T00:00:00Z.
      * @param timeZone             The time zone at the date specified by {@code timeInUtcMillis}.
-     *                             The abbreviated
-     *                             name of this time zone, formatted using the default locale, may
-     *                             be displayed to the user
-     *                             when needed, for example, if this time zone is different than
-     *                             the current system time zone.
+     *                             The abbreviated name of this time zone, formatted using the
+     *                             default locale, may be displayed to the user when needed, for
+     *                             example, if this time zone is different than the current
+     *                             system time zone.
      * @throws IllegalArgumentException if {@code timeSinceEpochMillis} is a negative value.
      * @throws NullPointerException     if {@code timeZone} is {@code null}.
      */
diff --git a/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java b/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java
index be73daf..ab42230 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/DistanceSpan.java
@@ -57,7 +57,7 @@
  * string.setSpan(ForegroundCarColorSpan.create(CarColor.BLUE), 0, 1, SPAN_EXCLUSIVE_EXCLUSIVE);
  * }</pre>
  */
-public class DistanceSpan extends CarSpan {
+public final class DistanceSpan extends CarSpan {
     @Nullable
     @Keep
     private final Distance mDistance;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java b/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java
index d1b1765..83000b7 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/DurationSpan.java
@@ -52,7 +52,7 @@
  * string.setSpan(ForegroundCarColorSpan.create(CarColor.BLUE), 0, 1, SPAN_EXCLUSIVE_EXCLUSIVE);
  * }</pre>
  */
-public class DurationSpan extends CarSpan {
+public final class DurationSpan extends CarSpan {
     @Keep
     private final long mDurationSeconds;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ForegroundCarColorSpan.java b/car/app/app/src/main/java/androidx/car/app/model/ForegroundCarColorSpan.java
index 587fa6e..01d8057 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ForegroundCarColorSpan.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ForegroundCarColorSpan.java
@@ -46,7 +46,7 @@
  * @see CarColor
  * @see ForegroundColorSpan
  */
-public class ForegroundCarColorSpan extends CarSpan {
+public final class ForegroundCarColorSpan extends CarSpan {
     @Keep
     private final CarColor mCarColor;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
index 5874967..f521808 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
@@ -38,7 +38,7 @@
 /**
  * Represents a grid item with an image and an optional title.
  */
-public class GridItem implements Item {
+public final class GridItem implements Item {
     /**
      * The type of images supported within grid items.
      *
@@ -329,10 +329,9 @@
         /**
          * Constructs the {@link GridItem} defined by this builder.
          *
-         * @throws IllegalStateException if the grid item's title is not set.
-         * @throws IllegalStateException if the grid item's image is set when it is loading and vice
-         *                               versa.
-         * @throws IllegalStateException if the grid item is loading but the click listener is set.
+         * @throws IllegalStateException if the grid item's title is not set, if the grid item's
+         *                               image is set when it is loading or vice versa, or if
+         *                               the grid item is loading but the click listener is set.
          */
         @NonNull
         public GridItem build() {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
index f9f327d..2c162b5 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridTemplate.java
@@ -261,11 +261,10 @@
          * <p>Either a header {@link Action} or title must be set on the template.
          *
          * @throws IllegalStateException    if the template is in a loading state but there are
-         *                                  lists added, or vice versa.
+         *                                  lists added, or vice versa, or if the template does not
+         *                                  have either a title or header {@link Action} set.
          * @throws IllegalArgumentException if the added {@link ItemList} does not meet the
          *                                  template's requirements.
-         * @throws IllegalStateException    if the template does not have either a title or header
-         *                                  {@link Action} set.
          */
         @NonNull
         public GridTemplate build() {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
index d9ae559..7d7cfc1 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
@@ -373,11 +373,11 @@
         /**
          * Constructs the item list defined by this builder.
          *
-         * @throws IllegalStateException if the list is selectable but does not have any items.
-         * @throws IllegalStateException if the selected index is greater or equal to the size of
-         *                               the list.
-         * @throws IllegalStateException if the list is selectable and any items have either one of
-         *                               their {@link OnClickListener} or {@link Toggle} set.
+         * @throws IllegalStateException if the list is selectable but does not have any items, if
+         *                               the selected index is greater or equal to the size of the
+         *                               list, or if the list is selectable and any items have
+         *                               either one of their {@link OnClickListener} or
+         *                               {@link Toggle} set.
          */
         @NonNull
         public ItemList build() {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
index 39d84f5..cd1694f 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ListTemplate.java
@@ -236,6 +236,7 @@
          * multiple lists were previously added, they will be cleared.
          *
          * @throws NullPointerException if {@code list} is null.
+         *
          * @see #addList(ItemList, CharSequence)
          */
         @NonNull
@@ -257,13 +258,11 @@
          * <p>If the added {@link ItemList} contains a {@link ItemList.OnSelectedListener}, then it
          * cannot be added alongside other {@link ItemList}(s).
          *
-         * @throws NullPointerException     if {@code list} is null.
-         * @throws IllegalArgumentException if {@code list} is empty.
-         * @throws IllegalArgumentException if {@code list}'s {@link
-         *                                  ItemList.OnItemVisibilityChangedListener} is set.
-         * @throws NullPointerException     if {@code header} is null.
-         * @throws IllegalArgumentException if {@code header} is empty.
-         * @throws IllegalArgumentException if a selectable list is added alongside other lists.
+         * @throws NullPointerException     if {@code list} or {@code header} is {@code null}.
+         * @throws IllegalArgumentException if {@code list} is empty, if {@code list}'s {@link
+         *                                  ItemList.OnItemVisibilityChangedListener} is set, if
+         *                                  {@code header} is empty, or if a selectable list is
+         *                                  added alongside other lists.
          *
          * @deprecated use {@link #addSectionedList}  instead.
          */
@@ -288,13 +287,11 @@
          * {@link ItemList.OnSelectedListener}, then it cannot be added alongside other
          * {@link SectionedItemList}(s).
          *
-         * @throws NullPointerException     if {@code list} is null.
-         * @throws IllegalArgumentException if {@code list} is empty.
-         * @throws IllegalArgumentException if {@code list}'s {@link
-         *                                  ItemList.OnItemVisibilityChangedListener} is set.
-         * @throws NullPointerException     if {@code header} is null.
-         * @throws IllegalArgumentException if {@code header} is empty.
-         * @throws IllegalArgumentException if a selectable list is added alongside other lists.
+         * @throws NullPointerException     if {@code list} or {@code header} is {@code null}.
+         * @throws IllegalArgumentException if {@code list} is empty, if {@code list}'s {@link
+         *                                  ItemList.OnItemVisibilityChangedListener} is set, if
+         *                                  {@code header} is empty, or if a selectable list is
+         *                                  added alongside other lists.
          */
         @NonNull
         public Builder addSectionedList(@NonNull SectionedItemList list) {
@@ -356,11 +353,10 @@
          * <p>Either a header {@link Action} or the title must be set on the template.
          *
          * @throws IllegalStateException    if the template is in a loading state but there are
-         *                                  lists added, or vice versa.
+         *                                  lists added or vice versa, or if the template does
+         *                                  not have either a title or header {@link Action} set.
          * @throws IllegalArgumentException if the added {@link ItemList}(s) do not meet the
          *                                  template's requirements.
-         * @throws IllegalStateException    if the template does not have either a title or header
-         *                                  {@link Action} set.
          */
         @NonNull
         public ListTemplate build() {
@@ -386,7 +382,7 @@
         }
 
         /** Returns an empty {@link Builder} instance. */
-        Builder() {
+        public Builder() {
         }
     }
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
index a3f02f2..1e25805 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
@@ -322,9 +322,8 @@
          *
          * <p>Either a header {@link Action} or title must be set on the template.
          *
-         * @throws IllegalStateException if the message is empty.
-         * @throws IllegalStateException if the template does not have either a title or header
-         *                               {@link Action} set.
+         * @throws IllegalStateException if the message is empty, or if the template does not have
+         *                               either a title or header {@link Action} set.
          */
         @NonNull
         public MessageTemplate build() {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Metadata.java b/car/app/app/src/main/java/androidx/car/app/model/Metadata.java
index 9235aa9..02ee02b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Metadata.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Metadata.java
@@ -25,7 +25,7 @@
 import java.util.Objects;
 
 /** A metadata class used for attaching additional properties to models. */
-public class Metadata {
+public final class Metadata {
     /** An empty {@link Metadata} instance. */
     public static final Metadata EMPTY_METADATA = new Builder().build();
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ModelUtils.java b/car/app/app/src/main/java/androidx/car/app/model/ModelUtils.java
index 9892f0c..67bcd64 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ModelUtils.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ModelUtils.java
@@ -37,9 +37,9 @@
      * Checks whether all non-browsable rows have attached at least one {@link DistanceSpan} in
      * either the title or secondary text.
      *
-     * @throws IllegalArgumentException if the input list contains any non-Row instances.
-     * @throws IllegalArgumentException if any non-browsable row does not have a
-     *                                  {@link DistanceSpan} instance.
+     * @throws IllegalArgumentException if the input list contains any non-Row instances, or if any
+     *                                  non-browsable row does not have a {@link DistanceSpan}
+     *                                  instance.
      */
     public static void validateAllNonBrowsableRowsHaveDistance(@NonNull List<Item> rows) {
         int spanSetCount = 0;
@@ -70,8 +70,8 @@
      * Checks whether all rows have attached at least one {@link DurationSpan} or
      * {@link DistanceSpan }in either the title or secondary text.
      *
-     * @throws IllegalArgumentException if the input list contains any non-Row instances.
-     * @throws IllegalArgumentException if any non-browsable row does not have either a {@link
+     * @throws IllegalArgumentException if the input list contains any non-Row instances, or if any
+     *                                  non-browsable row does not have either a {@link
      *                                  DurationSpan} or {@link DistanceSpan} instance.
      */
     public static void validateAllRowsHaveDistanceOrDuration(@NonNull List<Item> rows) {
@@ -93,9 +93,8 @@
     /**
      * Checks whether all rows have only small-sized images if they are set.
      *
-     * @throws IllegalArgumentException if the input list contains any non-Row instances.
-     * @throws IllegalArgumentException if an image set in any rows is using {@link
-     *                                  Row#IMAGE_TYPE_LARGE}.
+     * @throws IllegalArgumentException if the input list contains any non-Row instances, or if an
+     *                                  image set in any rows is using {@link Row#IMAGE_TYPE_LARGE}.
      */
     public static void validateAllRowsHaveOnlySmallImages(@NonNull List<Item> rows) {
         for (Item rowObj : rows) {
@@ -112,8 +111,8 @@
     /**
      * Checks whether any rows have both a marker and an image.
      *
-     * @throws IllegalArgumentException if the input list contains any non-Row instances.
-     * @throws IllegalArgumentException if both a marker and an image are set in a row.
+     * @throws IllegalArgumentException if the input list contains any non-Row instances, or if
+     *                                  both a marker and an image are set in a row.
      */
     public static void validateNoRowsHaveBothMarkersAndImages(@NonNull List<Item> rows) {
         for (Item rowObj : rows) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/OnCheckedChangeListenerWrapperImpl.java b/car/app/app/src/main/java/androidx/car/app/model/OnCheckedChangeListenerWrapperImpl.java
index 5219705..d8fd6659 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/OnCheckedChangeListenerWrapperImpl.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/OnCheckedChangeListenerWrapperImpl.java
@@ -37,7 +37,7 @@
 // TODO(b/177591476): remove after host references have been cleaned up.
 @SuppressWarnings("deprecation")
 @RestrictTo(LIBRARY)
-public class OnCheckedChangeListenerWrapperImpl implements OnCheckedChangeListenerWrapper {
+public final class OnCheckedChangeListenerWrapperImpl implements OnCheckedChangeListenerWrapper {
 
     @Keep
     private final IOnCheckedChangeListener mStub;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapperImpl.java b/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapperImpl.java
index ea376ad..845691f 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapperImpl.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/OnClickListenerWrapperImpl.java
@@ -36,7 +36,7 @@
 // TODO(b/177591476): remove after host references have been cleaned up.
 @SuppressWarnings("deprecation")
 @RestrictTo(LIBRARY)
-public class OnClickListenerWrapperImpl implements OnClickListenerWrapper {
+public final class OnClickListenerWrapperImpl implements OnClickListenerWrapper {
 
     @Keep
     private final boolean mIsParkedOnly;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/OnItemVisibilityChangedListenerWrapperImpl.java b/car/app/app/src/main/java/androidx/car/app/model/OnItemVisibilityChangedListenerWrapperImpl.java
index d954b73..c1982f4 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/OnItemVisibilityChangedListenerWrapperImpl.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/OnItemVisibilityChangedListenerWrapperImpl.java
@@ -37,7 +37,7 @@
 // TODO(b/177591476): remove after host references have been cleaned up.
 @SuppressWarnings("deprecation")
 @RestrictTo(LIBRARY)
-public class OnItemVisibilityChangedListenerWrapperImpl implements
+public final class OnItemVisibilityChangedListenerWrapperImpl implements
         OnItemVisibilityChangedListenerWrapper {
 
     @Keep
diff --git a/car/app/app/src/main/java/androidx/car/app/model/OnSelectedListenerWrapperImpl.java b/car/app/app/src/main/java/androidx/car/app/model/OnSelectedListenerWrapperImpl.java
index 50fff22..8ff7485 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/OnSelectedListenerWrapperImpl.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/OnSelectedListenerWrapperImpl.java
@@ -37,7 +37,7 @@
 // TODO(b/177591476): remove after host references have been cleaned up.
 @SuppressWarnings("deprecation")
 @RestrictTo(LIBRARY)
-public class OnSelectedListenerWrapperImpl implements OnSelectedListenerWrapper {
+public final class OnSelectedListenerWrapperImpl implements OnSelectedListenerWrapper {
 
     @Keep
     private final IOnSelectedListener mStub;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Pane.java b/car/app/app/src/main/java/androidx/car/app/model/Pane.java
index e0e91e3..8467609 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Pane.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Pane.java
@@ -212,7 +212,7 @@
          * Constructs the row list defined by this builder.
          *
          * @throws IllegalStateException if the pane is in loading state and also contains rows, or
-         *                               vice-versa.
+         *                               vice versa.
          */
         @NonNull
         public Pane build() {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Place.java b/car/app/app/src/main/java/androidx/car/app/model/Place.java
index c680fd7..f4db4a1 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Place.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Place.java
@@ -25,7 +25,7 @@
 import java.util.Objects;
 
 /** Represents a geographical location and additional information on how to display it. */
-public class Place {
+public final class Place {
     @Keep
     @Nullable
     private final CarLocation mLocation;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java b/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java
index a0e00a7..892a841 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java
@@ -31,7 +31,7 @@
 import java.util.Objects;
 
 /** Describes how a place is to be displayed on a map. */
-public class PlaceMarker {
+public final class PlaceMarker {
     /**
      * Describes the type of image a marker icon represents.
      *
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Row.java b/car/app/app/src/main/java/androidx/car/app/model/Row.java
index 9a10377..17a42d2 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Row.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Row.java
@@ -42,7 +42,7 @@
  * Represents a row with a title, several lines of text, an optional image, and an optional action
  * or switch.
  */
-public class Row implements Item {
+public final class Row implements Item {
     /**
      * The type of images supported within rows.
      *
@@ -484,11 +484,10 @@
         /**
          * Constructs the {@link Row} defined by this builder.
          *
-         * @throws IllegalStateException if the row's title is not set.
-         * @throws IllegalStateException if the row is a browsable row and has a {@link Toggle}.
-         * @throws IllegalStateException if the row is a browsable row but does not have a {@link
-         *                               OnClickListener}.
-         * @throws IllegalStateException if the row has both a {@link OnClickListener} and a {@link
+         * @throws IllegalStateException if the row's title is not set, if it is a browsable
+         *                               row and has a {@link Toggle}, if it is a browsable
+         *                               row but does not have a {@link OnClickListener}, or if
+         *                               it has both a {@link OnClickListener} and a {@link
          *                               Toggle}.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/SearchCallbackWrapperImpl.java b/car/app/app/src/main/java/androidx/car/app/model/SearchCallbackWrapperImpl.java
index 97dfb19..ebafa4f 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/SearchCallbackWrapperImpl.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/SearchCallbackWrapperImpl.java
@@ -37,7 +37,7 @@
 // TODO(b/177591476): remove after host references have been cleaned up.
 @SuppressWarnings("deprecation")
 @RestrictTo(LIBRARY)
-public class SearchCallbackWrapperImpl implements SearchCallbackWrapper {
+public final class SearchCallbackWrapperImpl implements SearchCallbackWrapper {
 
     @Keep
     private final ISearchCallback mStubCallback;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/SectionedItemList.java b/car/app/app/src/main/java/androidx/car/app/model/SectionedItemList.java
index c06189c..40003cf 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/SectionedItemList.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/SectionedItemList.java
@@ -27,7 +27,7 @@
 /**
  * Represents an {@link ItemList} that is contained inside a section, for internal use only.
  */
-public class SectionedItemList {
+public final class SectionedItemList {
     @Keep
     @Nullable
     private final ItemList mItemList;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java b/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java
index 21031ab..5aa2755 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/TemplateWrapper.java
@@ -64,17 +64,13 @@
     @NonNull
     public static TemplateWrapper wrap(@NonNull Template template) {
         // Assign a random ID to the template. This should be unique so that the host knows the
-        // template
-        // is a new step. We are not using hashCode() here as we might override template's hash
-        // codes in
-        // the future.
+        // template is a new step. We are not using hashCode() here as we might override
+        // template's hash codes in the future.
         //
         // Note: There is a chance of collision here, in which case the host will reset the
         // task step to the value of a previous template that has the colliding ID. The chance of
-        // this
-        // happening should be negligible given we are dealing with a very small number of
-        // templates in
-        // the stack.
+        // this happening should be negligible given we are dealing with a very small number of
+        // templates in the stack.
         return wrap(template, createRandomId());
     }
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Toggle.java b/car/app/app/src/main/java/androidx/car/app/model/Toggle.java
index f25f357..b218dde 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Toggle.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Toggle.java
@@ -26,7 +26,7 @@
 import androidx.annotation.Nullable;
 
 /** Represents a toggle that can have either a checked or unchecked state. */
-public class Toggle {
+public final class Toggle {
     /** A listener for handling checked state change events. */
     public interface OnCheckedChangeListener {
         /** Notifies that the checked state has changed. */
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
index 0745a1b..b2a80b2 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
@@ -39,7 +39,7 @@
  * @hide
  */
 @RestrictTo(Scope.LIBRARY)
-public class ActionsConstraints {
+public final class ActionsConstraints {
 
     /** Conservative constraints for most template types. */
     @NonNull
@@ -111,11 +111,10 @@
     /**
      * Validates the input list of {@link Action}s against this {@link ActionsConstraints} instance.
      *
-     * @throws IllegalArgumentException if the actions has more actions than allowed.
-     * @throws IllegalArgumentException if the actions has more actions with custom titles than
-     *                                  allowed.
-     * @throws IllegalArgumentException if the actions does not contain all required types.
-     * @throws IllegalArgumentException if the actions contain any disallowed types.
+     * @throws IllegalArgumentException if the actions has more actions than allowed, if it has
+     *                                  more actions with custom titles than allowed, if the
+     *                                  actions do not contain all required types, or if the
+     *                                  actions contain any disallowed types.
      */
     public void validateOrThrow(@NonNull List<Action> actions) {
         int maxAllowedActions = mMaxActions;
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/CarColorConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/CarColorConstraints.java
index ae9e383..258d8f3 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/CarColorConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/CarColorConstraints.java
@@ -31,7 +31,7 @@
  * @hide
  */
 @RestrictTo(Scope.LIBRARY)
-public class CarColorConstraints {
+public final class CarColorConstraints {
 
     @NonNull
     public static final CarColorConstraints UNCONSTRAINED =
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/CarIconConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/CarIconConstraints.java
index c9cafe0..dc504f8 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/CarIconConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/CarIconConstraints.java
@@ -30,7 +30,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-public class CarIconConstraints {
+public final class CarIconConstraints {
     /** Allow all custom icon types. */
     @NonNull
     public static final CarIconConstraints UNCONSTRAINED =
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
index a64e6d9..ee01e9df 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowConstraints.java
@@ -29,7 +29,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-public class RowConstraints {
+public final class RowConstraints {
     @NonNull
     public static final RowConstraints UNCONSTRAINED = new RowConstraints.Builder().build();
 
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
index 073ef90..1554c5c 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/RowListConstraints.java
@@ -41,7 +41,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-public class RowListConstraints {
+public final class RowListConstraints {
     /** Conservative constraints for all types lists. */
     @NonNull
     public static final RowListConstraints ROW_LIST_CONSTRAINTS_CONSERVATIVE =
@@ -113,8 +113,8 @@
     /**
      * Validates that the {@link ItemList} satisfies this {@link RowListConstraints} instance.
      *
-     * @throws IllegalArgumentException if the constraints are not met.
-     * @throws IllegalArgumentException if the list contains non-Row instances.
+     * @throws IllegalArgumentException if the constraints are not met, or if the list contains
+     *                                  non-Row instances.
      */
     public void validateOrThrow(@NonNull ItemList itemList) {
         if (itemList.getOnSelectedDelegate() != null && !mAllowSelectableLists) {
@@ -128,8 +128,8 @@
      * Validates that the list of {@link SectionedItemList}s satisfies this
      * {@link RowListConstraints} instance.
      *
-     * @throws IllegalArgumentException if the constraints are not met.
-     * @throws IllegalArgumentException if the lists contain any non-Row instances.
+     * @throws IllegalArgumentException if the constraints are not met or if the lists contain
+     *                                  any non-Row instances.
      */
     public void validateOrThrow(@NonNull List<SectionedItemList> sections) {
         List<Item> combinedLists = new ArrayList<>();
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
index efed8b9..c5c7624 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
@@ -94,13 +94,14 @@
      * displays the associated icon may be shown.
      *
      * @param trip destination, steps, and trip estimates to be sent to the host
+     *
      * @throws HostException            if the call is invoked by an app that is not declared as
      *                                  a navigation app in the manifest
-     * @throws IllegalStateException    if the call occurs when navigation is not started. See
-     *                                  {@link #navigationStarted()} for more info
+     * @throws IllegalStateException    if the call occurs when navigation is not started (see
+     *                                  {@link #navigationStarted()} for more info), or if the
+     *                                  current thread is not the main thread
      * @throws IllegalArgumentException if any of the destinations, steps, or trip position is
      *                                  not well formed
-     * @throws IllegalStateException    if the current thread is not the main thread
      */
     @MainThread
     public void updateTrip(@NonNull Trip trip) {
@@ -133,6 +134,7 @@
      * {@link #setNavigationManagerCallback(Executor, NavigationManagerCallback)}.
      *
      * @param callback the {@link NavigationManagerCallback} to use
+     *
      * @throws IllegalStateException if the current thread is not the main thread
      */
     @SuppressLint("ExecutorRegistration")
@@ -148,7 +150,8 @@
      *
      * @param executor the executor which will be used for invoking the callback
      * @param callback the {@link NavigationManagerCallback} to use
-     * @throws IllegalStateException if the current thread is not the main thread.
+     *
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     @MainThread
     public void setNavigationManagerCallback(@NonNull /* @CallbackExecutor */ Executor executor,
@@ -165,9 +168,8 @@
     /**
      * Clears the callback for receiving navigation manager events.
      *
-     * @throws IllegalStateException if navigation is started. See
-     *                               {@link #navigationStarted()} for more info.
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws IllegalStateException if navigation is started (see {@link #navigationStarted()}
+     *                               for more info), or if the current thread is not the main thread
      */
     @MainThread
     public void clearNavigationManagerCallback() {
@@ -194,8 +196,8 @@
      *
      * <p>This method is idempotent.
      *
-     * @throws IllegalStateException if no navigation manager callback has been set.
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws IllegalStateException if no navigation manager callback has been set, or if the
+     *                               current thread is not the main thread
      */
     @MainThread
     public void navigationStarted() {
@@ -225,7 +227,7 @@
      *
      * <p>This method is idempotent.
      *
-     * @throws IllegalStateException if the current thread is not the main thread.
+     * @throws IllegalStateException if the current thread is not the main thread
      */
     @MainThread
     public void navigationEnded() {
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java
index e1ee2bf..0017584 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java
@@ -40,8 +40,7 @@
     /**
      * Constructs a new builder of {@link Destination} with the given name and address.
      *
-     * @throws NullPointerException if {@code name} is {@code null}.
-     * @throws NullPointerException if {@code address} is {@code null}.
+     * @throws NullPointerException if {@code name} or {@code address} is {@code null}.
      */
     // TODO(b/175827428): remove once host is changed to use new public ctor.
     @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
index a4eab2b..9bd91b3 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
@@ -675,8 +675,8 @@
          * the one to the left would be exit #3 and the one used by the driver to join the
          * roundabout would be exit #4.
          *
-         * @throws IllegalArgumentException if {@code type} does not include a exit number.
-         * @throws IllegalArgumentException if {@code roundaboutExitNumber} is not greater than
+         * @throws IllegalArgumentException if {@code type} does not include a exit number, or
+         *                                  if {@code roundaboutExitNumber} is not greater than
          *                                  zero.
          */
         @NonNull
@@ -704,9 +704,9 @@
          * would be at 90 degrees, exit 2 at 180, exit 3 at 270 and exit 4 at 360. However if the
          * exits are irregular then a different angle could be provided.
          *
-         * @throws IllegalArgumentException if {@code type} does not include a exit angle.
-         * @throws IllegalArgumentException if {@code roundaboutExitAngle} is not greater than
-         *                                  zero and less than or equal to 360 degrees.
+         * @throws IllegalArgumentException if {@code type} does not include a exit angle or if
+         *                                  {@code roundaboutExitAngle} is not greater than zero
+         *                                  and less than or equal to 360 degrees.
          */
         @NonNull
         public Builder setRoundaboutExitAngle(
@@ -726,9 +726,8 @@
          * Constructs the {@link Maneuver} defined by this builder.
          *
          * @throws IllegalArgumentException if {@code type} includes an exit number and one has
-         *                                  not been set.
-         * @throws IllegalArgumentException if {@code type} includes an exit angle and one has
-         *                                  not been set.
+         *                                  not been set, or if it includes an exit angle and one
+         *                                  has not been set.
          */
         @NonNull
         public Maneuver build() {
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
index e0ea9ab..39123b1 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
@@ -29,7 +29,7 @@
 import java.util.Objects;
 
 /** Represents a message that can be shown in the {@link NavigationTemplate}. */
-public class MessageInfo implements NavigationInfo {
+public final class MessageInfo implements NavigationInfo {
     @Keep
     @Nullable
     private final CarText mTitle;
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
index 1d56012..ec22e9b 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/NavigationTemplate.java
@@ -66,7 +66,7 @@
  * <p>In order to use this template your car app <b>MUST</b> declare that it uses the {@code
  * androidx.car.app.NAVIGATION_TEMPLATES} permission in the manifest.
  */
-public class NavigationTemplate implements Template {
+public final class NavigationTemplate implements Template {
 
     /**
      * Represents navigation information such as routing instructions or navigation-related
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
index fbce905..9c9e740 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
@@ -281,7 +281,7 @@
          * Either a header {@link Action} or title must be set on the template.
          *
          * @throws IllegalArgumentException if the template is in a loading state but the list is
-         *                                  set, or vice-versa.
+         *                                  set, or vice versa.
          * @throws IllegalStateException    if the template does not have either a title or header
          *                                  {@link Action} set.
          */
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
index d052e9f..ecede4f 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
@@ -326,11 +326,9 @@
          * Either a header {@link Action} or title must be set on the template.
          *
          * @throws IllegalStateException if the template is in a loading state but the list is
-         *                               set, or vice-versa.
-         * @throws IllegalStateException if the template is not loading and the navigation action
-         *                               is not set.
-         * @throws IllegalStateException if the template does not have either a title or header
-         *                               {@link Action} set.
+         *                               set or vice versa, if the template is not loading and
+         *                               the navigation action is not set, or if the template
+         *                               does not have either a title or header {@link Action} set.
          */
         @NonNull
         public RoutePreviewNavigationTemplate build() {
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java
index 3852820..001f5b5 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutingInfo.java
@@ -32,7 +32,7 @@
  * Represents routing information that can be shown in the {@link NavigationTemplate} during
  * navigation
  */
-public class RoutingInfo implements NavigationInfo {
+public final class RoutingInfo implements NavigationInfo {
     @Keep
     @Nullable
     private final Step mCurrentStep;
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
index 5507a94..8b77058 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
@@ -67,8 +67,8 @@
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided
      *                                 for the destination.
      * @throws IllegalArgumentException if {@code remainingTimeSeconds} is a negative value.
-     * @throws NullPointerException     if {@code remainingDistance} is {@code null}
-     * @throws NullPointerException     if {@code arrivalTimeAtDestination} is {@code null}
+     * @throws NullPointerException     if {@code remainingDistance} or {@code
+     *                                  arrivalTimeAtDestination} are {@code null}
      */
     @NonNull
     public static TravelEstimate create(
@@ -91,9 +91,8 @@
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided for
      *                                 the destination.
      * @throws IllegalArgumentException if {@code remainingTime} contains a negative duration.
-     * @throws NullPointerException     if {@code remainingDistance} is {@code null}
-     * @throws NullPointerException     if {@code remainingTime} is {@code null}
-     * @throws NullPointerException     if {@code arrivalTimeAtDestination} is {@code null}
+     * @throws NullPointerException     if {@code remainingDistance}, {@code remainingTime}, or
+     *                                  {@code arrivalTimeAtDestination} are {@code null}
      */
     @RequiresApi(26)
     @SuppressWarnings("AndroidJdkLibsChecker")
@@ -113,8 +112,8 @@
      *                                 the destination.
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided
      *                                 for the destination.
-     * @throws NullPointerException if {@code remainingDistance} is {@code null}
-     * @throws NullPointerException if {@code arrivalTimeAtDestination} is {@code null}
+     * @throws NullPointerException if {@code remainingDistance} or {@code
+     *                              arrivalTimeAtDestination} are {@code null}
      */
     // TODO(b/175827428): remove once host is changed to use new public ctor.
     @NonNull
@@ -133,8 +132,8 @@
      *                                 the destination.
      * @param arrivalTimeAtDestination The arrival time with the time zone information provided for
      *                                 the destination.
-     * @throws NullPointerException if {@code remainingDistance} is {@code null}
-     * @throws NullPointerException if {@code arrivalTimeAtDestination} is {@code null}
+     * @throws NullPointerException if {@code remainingDistance} or
+     *                              {@code arrivalTimeAtDestination} are {@code null}
      */
     @NonNull
     @RequiresApi(26)
@@ -251,8 +250,8 @@
          * @param arrivalTimeAtDestination The arrival time with the time zone information
          *                                 provided for
          *                                 the destination.
-         * @throws NullPointerException if {@code remainingDistance} is {@code null}
-         * @throws NullPointerException if {@code arrivalTimeAtDestination} is {@code null}
+         * @throws NullPointerException if {@code remainingDistance} or
+         *                              {@code arrivalTimeAtDestination} are {@code null}
          */
         public Builder(
                 @NonNull Distance remainingDistance,
@@ -270,8 +269,8 @@
          * @param arrivalTimeAtDestination The arrival time with the time zone information
          *                                 provided for
          *                                 the destination.
-         * @throws NullPointerException if {@code remainingDistance} is {@code null}
-         * @throws NullPointerException if {@code arrivalTimeAtDestination} is {@code null}
+         * @throws NullPointerException if {@code remainingDistance} or
+         *                              {@code arrivalTimeAtDestination} are {@code null}
          */
         // TODO(rampara): Move API 26 calls into separate class.
         @SuppressLint("UnsafeNewApiCall")
diff --git a/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java b/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
index 872d777..4cb442f 100644
--- a/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
+++ b/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
@@ -118,7 +118,7 @@
  * NotificationCompat.Builder#setOnlyAlertOnce(true)} unless there is a significant navigation turn
  * event.
  */
-public class CarAppExtender implements NotificationCompat.Extender {
+public final class CarAppExtender implements NotificationCompat.Extender {
     private static final String TAG = "CarAppExtender";
 
     private static final String EXTRA_CAR_EXTENDER = "android.car.app.EXTENSIONS";
@@ -525,8 +525,7 @@
          *               navigation notifications in the rail widget, this intent will be sent
          *               when the user taps on the action icon in the rail
          *               widget.
-         * @throws NullPointerException if {@code title} is {@code null}.
-         * @throws NullPointerException if {@code intent} is {@code null}.
+         * @throws NullPointerException if {@code title} or {@code intent} are {@code null}.
          */
         // TODO(rampara): Update to remove use of deprecated Action API
         @SuppressWarnings("deprecation")
diff --git a/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java b/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
index 7f325a0..38e746c 100644
--- a/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
+++ b/car/app/app/src/main/java/androidx/car/app/serialization/Bundler.java
@@ -74,7 +74,7 @@
  * @hide
  */
 @RestrictTo(LIBRARY)
-public class Bundler {
+public final class Bundler {
     @VisibleForTesting
     static final String ICON_COMPAT_ANDROIDX = "androidx.core.graphics.drawable.IconCompat";
 
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/StringUtils.java b/car/app/app/src/main/java/androidx/car/app/utils/StringUtils.java
index 5ef4b75..a1defdb 100644
--- a/car/app/app/src/main/java/androidx/car/app/utils/StringUtils.java
+++ b/car/app/app/src/main/java/androidx/car/app/utils/StringUtils.java
@@ -27,7 +27,7 @@
  * @hide
  */
 @RestrictTo(LIBRARY)
-public class StringUtils {
+public final class StringUtils {
     private static final int MAX_SHORT_STRING_LENGTH = 16;
 
     /**
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/ThreadUtils.java b/car/app/app/src/main/java/androidx/car/app/utils/ThreadUtils.java
index 1fef405..e240da6 100644
--- a/car/app/app/src/main/java/androidx/car/app/utils/ThreadUtils.java
+++ b/car/app/app/src/main/java/androidx/car/app/utils/ThreadUtils.java
@@ -30,7 +30,7 @@
  * @hide
  */
 @RestrictTo(LIBRARY)
-public class ThreadUtils {
+public final class ThreadUtils {
     private static final Handler HANDLER = new Handler(Looper.getMainLooper());
 
     /** Executes the {@code action} on the main thread. */
diff --git a/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
index 3d96294..213485d3 100644
--- a/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
+++ b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
@@ -27,7 +27,7 @@
  *
  * @see CarContext#getCarAppApiLevel()
  */
-public class CarAppApiLevels {
+public final class CarAppApiLevels {
 
     /**
      * Initial API level.
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt
index c21c5e6..befa884 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt
@@ -112,6 +112,7 @@
                 import android.widget.LinearLayout
                 import android.content.Context
                 import androidx.compose.ui.node.UiApplier
+                import kotlin.coroutines.EmptyCoroutineContext
 
                 $src
 
@@ -127,7 +128,7 @@
                     val container = LinearLayout(__context!!)
                     return Composer(
                         UiApplier(container),
-                        Recomposer.current()
+                        Recomposer(EmptyCoroutineContext)
                     )
                 }
 
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt
index 6e67739..de2a775 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RobolectricComposeTester.kt
@@ -21,20 +21,27 @@
 import android.os.Looper.getMainLooper
 import android.view.ViewGroup
 import android.widget.LinearLayout
+import androidx.activity.ComponentActivity
 import androidx.compose.runtime.Composer
 import androidx.compose.runtime.Composition
+import androidx.compose.runtime.EmbeddingContext
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.compositionFor
 import androidx.compose.ui.node.UiApplier
 import androidx.compose.ui.platform.AmbientContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.launch
 import org.robolectric.Robolectric
 import org.robolectric.RuntimeEnvironment
 import org.robolectric.Shadows.shadowOf
 
 const val ROOT_ID = 18284847
 
-private class TestActivity : Activity() {
+private class TestActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(LinearLayout(this).apply { id = ROOT_ID })
@@ -99,8 +106,9 @@
             endProviders.invoke(composer)
         }
 
+        @Suppress("DEPRECATION")
         @OptIn(ExperimentalComposeApi::class)
-        val composition = compositionFor(root, UiApplier(root), Recomposer.current())
+        val composition = compositionFor(root, UiApplier(root), recomposer)
         fun setContent() {
             setContentMethod.invoke(composition, realComposable)
         }
@@ -111,4 +119,24 @@
         val advanceFn = advance ?: { setContent() }
         return ActiveTest(activity, advanceFn)
     }
+
+    companion object {
+        @OptIn(ExperimentalCoroutinesApi::class)
+        private val recomposer = run {
+            val embeddingContext = EmbeddingContext()
+            val mainScope = CoroutineScope(
+                NonCancellable + embeddingContext.mainThreadCompositionContext()
+            )
+
+            Recomposer(mainScope.coroutineContext).also {
+                // NOTE: Launching undispatched so that compositions created with the
+                // singleton instance can assume the recomposer is running
+                // when they perform initial composition. The relevant Recomposer code is
+                // appropriately thread-safe for this.
+                mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
+                    it.runRecomposeAndApplyChanges()
+                }
+            }
+        }
+    }
 }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index 0dff666..ed8aed3 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -35,20 +35,21 @@
             1900 to "1.0.0-alpha08",
             2000 to "1.0.0-alpha09",
             2100 to "1.0.0-alpha10",
-            2200 to "1.0.0-alpha11"
+            2200 to "1.0.0-alpha11",
+            2300 to "1.0.0-alpha12",
         )
 
         /**
          * The minimum version int that this compiler is guaranteed to be compatible with. Typically
          * this will match the version int that is in ComposeVersion.kt in the runtime.
          */
-        private val minimumRuntimeVersionInt: Int = 2200
+        private val minimumRuntimeVersionInt: Int = 2300
 
         /**
          * The maven version string of this compiler. This string should be updated before/after every
          * release.
          */
-        val compilerVersion: String = "1.0.0-alpha11"
+        val compilerVersion: String = "1.0.0-alpha12"
         private val minimumRuntimeVersion: String
             get() = versionTable[minimumRuntimeVersionInt] ?: "unknown"
     }
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt
index bd875f3..28b5cdc 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutTest.kt
@@ -27,14 +27,14 @@
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.layout.AlignmentLine
-import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.node.Ref
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.node.Ref
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.platform.setContent
@@ -50,6 +50,8 @@
 import androidx.compose.ui.unit.hasFixedWidth
 import androidx.compose.ui.unit.isFinite
 import androidx.compose.ui.unit.offset
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
@@ -89,10 +91,16 @@
     internal fun show(composable: @Composable () -> Unit) {
         val runnable: Runnable = object : Runnable {
             override fun run() {
-                activity.setContent(Recomposer.current(), composable)
+                activity.setContent(content = composable)
             }
         }
         activityTestRule.runOnUiThread(runnable)
+        // Wait for the frame to complete before continuing
+        runBlocking {
+            Recomposer.runningRecomposers.value.forEach { recomposer ->
+                recomposer.state.first { it <= Recomposer.State.Idle }
+            }
+        }
     }
 
     internal fun findComposeView(): View {
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index eefc439a..5a9af8b 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -160,14 +160,6 @@
     method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.foundation.ScrollState,?> Saver(androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, androidx.compose.foundation.InteractionState? interactionState);
   }
 
-  public final class TextKt {
-    method @Deprecated @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Text-TPAwlIA(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Text-Vh6c2nE(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public static androidx.compose.ui.text.TextStyle currentTextStyle();
-    method @Deprecated public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.text.TextStyle> getAmbientTextStyle();
-  }
-
 }
 
 package androidx.compose.foundation.animation {
@@ -251,15 +243,6 @@
     ctor public GestureCancellationException();
   }
 
-  public final class MultitouchGestureDetectorKt {
-    method public static long calculateCentroid(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
-    method public static float calculateCentroidSize(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
-    method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-  }
-
   public interface PressGestureScope extends androidx.compose.ui.unit.Density {
     method public suspend Object? awaitRelease(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public suspend Object? tryAwaitRelease(kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
@@ -293,6 +276,15 @@
     method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
   }
 
+  public final class TransformGestureDetectorKt {
+    method public static long calculateCentroid(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
+    method public static float calculateCentroidSize(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
+    method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static suspend Object? detectTransformGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+  }
+
   public final class ZoomableController {
     ctor public ZoomableController(androidx.compose.animation.core.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
     method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
@@ -442,8 +434,8 @@
     ctor public CornerBasedShape(androidx.compose.foundation.shape.CornerSize topLeft, androidx.compose.foundation.shape.CornerSize topRight, androidx.compose.foundation.shape.CornerSize bottomRight, androidx.compose.foundation.shape.CornerSize bottomLeft);
     method public abstract androidx.compose.foundation.shape.CornerBasedShape copy(optional androidx.compose.foundation.shape.CornerSize topLeft, optional androidx.compose.foundation.shape.CornerSize topRight, optional androidx.compose.foundation.shape.CornerSize bottomRight, optional androidx.compose.foundation.shape.CornerSize bottomLeft);
     method public final androidx.compose.foundation.shape.CornerBasedShape copy(androidx.compose.foundation.shape.CornerSize all);
+    method public final androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
     method public abstract androidx.compose.ui.graphics.Outline createOutline-hMawgr0(long size, float topLeft, float topRight, float bottomRight, float bottomLeft);
-    method public final androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
     method public final androidx.compose.foundation.shape.CornerSize getBottomLeft();
     method public final androidx.compose.foundation.shape.CornerSize getBottomRight();
     method public final androidx.compose.foundation.shape.CornerSize getTopLeft();
@@ -482,9 +474,8 @@
   }
 
   public final class GenericShape implements androidx.compose.ui.graphics.Shape {
-    ctor public GenericShape(kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,kotlin.Unit> builder);
-    method public androidx.compose.foundation.shape.GenericShape copy(kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,kotlin.Unit> builder);
-    method public androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    ctor public GenericShape(kotlin.jvm.functions.Function3<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,? super androidx.compose.ui.unit.LayoutDirection,kotlin.Unit> builder);
+    method public androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
   public final class RoundedCornerShape extends androidx.compose.foundation.shape.CornerBasedShape {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index eefc439a..5a9af8b 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -160,14 +160,6 @@
     method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.foundation.ScrollState,?> Saver(androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, androidx.compose.foundation.InteractionState? interactionState);
   }
 
-  public final class TextKt {
-    method @Deprecated @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Text-TPAwlIA(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Text-Vh6c2nE(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public static androidx.compose.ui.text.TextStyle currentTextStyle();
-    method @Deprecated public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.text.TextStyle> getAmbientTextStyle();
-  }
-
 }
 
 package androidx.compose.foundation.animation {
@@ -251,15 +243,6 @@
     ctor public GestureCancellationException();
   }
 
-  public final class MultitouchGestureDetectorKt {
-    method public static long calculateCentroid(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
-    method public static float calculateCentroidSize(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
-    method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-  }
-
   public interface PressGestureScope extends androidx.compose.ui.unit.Density {
     method public suspend Object? awaitRelease(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public suspend Object? tryAwaitRelease(kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
@@ -293,6 +276,15 @@
     method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
   }
 
+  public final class TransformGestureDetectorKt {
+    method public static long calculateCentroid(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
+    method public static float calculateCentroidSize(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
+    method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static suspend Object? detectTransformGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+  }
+
   public final class ZoomableController {
     ctor public ZoomableController(androidx.compose.animation.core.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
     method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
@@ -442,8 +434,8 @@
     ctor public CornerBasedShape(androidx.compose.foundation.shape.CornerSize topLeft, androidx.compose.foundation.shape.CornerSize topRight, androidx.compose.foundation.shape.CornerSize bottomRight, androidx.compose.foundation.shape.CornerSize bottomLeft);
     method public abstract androidx.compose.foundation.shape.CornerBasedShape copy(optional androidx.compose.foundation.shape.CornerSize topLeft, optional androidx.compose.foundation.shape.CornerSize topRight, optional androidx.compose.foundation.shape.CornerSize bottomRight, optional androidx.compose.foundation.shape.CornerSize bottomLeft);
     method public final androidx.compose.foundation.shape.CornerBasedShape copy(androidx.compose.foundation.shape.CornerSize all);
+    method public final androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
     method public abstract androidx.compose.ui.graphics.Outline createOutline-hMawgr0(long size, float topLeft, float topRight, float bottomRight, float bottomLeft);
-    method public final androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
     method public final androidx.compose.foundation.shape.CornerSize getBottomLeft();
     method public final androidx.compose.foundation.shape.CornerSize getBottomRight();
     method public final androidx.compose.foundation.shape.CornerSize getTopLeft();
@@ -482,9 +474,8 @@
   }
 
   public final class GenericShape implements androidx.compose.ui.graphics.Shape {
-    ctor public GenericShape(kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,kotlin.Unit> builder);
-    method public androidx.compose.foundation.shape.GenericShape copy(kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,kotlin.Unit> builder);
-    method public androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    ctor public GenericShape(kotlin.jvm.functions.Function3<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,? super androidx.compose.ui.unit.LayoutDirection,kotlin.Unit> builder);
+    method public androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
   public final class RoundedCornerShape extends androidx.compose.foundation.shape.CornerBasedShape {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index eefc439a..5a9af8b 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -160,14 +160,6 @@
     method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.foundation.ScrollState,?> Saver(androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, androidx.compose.foundation.InteractionState? interactionState);
   }
 
-  public final class TextKt {
-    method @Deprecated @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Text-TPAwlIA(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Text-Vh6c2nE(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableContract(readonly=true) public static androidx.compose.ui.text.TextStyle currentTextStyle();
-    method @Deprecated public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.text.TextStyle> getAmbientTextStyle();
-  }
-
 }
 
 package androidx.compose.foundation.animation {
@@ -251,15 +243,6 @@
     ctor public GestureCancellationException();
   }
 
-  public final class MultitouchGestureDetectorKt {
-    method public static long calculateCentroid(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
-    method public static float calculateCentroidSize(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
-    method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
-    method public static suspend Object? detectMultitouchGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-  }
-
   public interface PressGestureScope extends androidx.compose.ui.unit.Density {
     method public suspend Object? awaitRelease(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public suspend Object? tryAwaitRelease(kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
@@ -293,6 +276,15 @@
     method public static suspend Object? waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange> p);
   }
 
+  public final class TransformGestureDetectorKt {
+    method public static long calculateCentroid(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
+    method public static float calculateCentroidSize(androidx.compose.ui.input.pointer.PointerEvent, optional boolean useCurrent);
+    method public static long calculatePan(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static float calculateRotation(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static float calculateZoom(androidx.compose.ui.input.pointer.PointerEvent);
+    method public static suspend Object? detectTransformGestures(androidx.compose.ui.input.pointer.PointerInputScope, optional boolean panZoomLock, kotlin.jvm.functions.Function4<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.geometry.Offset,? super java.lang.Float,? super java.lang.Float,kotlin.Unit> onGesture, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+  }
+
   public final class ZoomableController {
     ctor public ZoomableController(androidx.compose.animation.core.AnimationClockObservable animationClock, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onZoomDelta);
     method public kotlin.jvm.functions.Function1<java.lang.Float,kotlin.Unit> getOnZoomDelta();
@@ -442,8 +434,8 @@
     ctor public CornerBasedShape(androidx.compose.foundation.shape.CornerSize topLeft, androidx.compose.foundation.shape.CornerSize topRight, androidx.compose.foundation.shape.CornerSize bottomRight, androidx.compose.foundation.shape.CornerSize bottomLeft);
     method public abstract androidx.compose.foundation.shape.CornerBasedShape copy(optional androidx.compose.foundation.shape.CornerSize topLeft, optional androidx.compose.foundation.shape.CornerSize topRight, optional androidx.compose.foundation.shape.CornerSize bottomRight, optional androidx.compose.foundation.shape.CornerSize bottomLeft);
     method public final androidx.compose.foundation.shape.CornerBasedShape copy(androidx.compose.foundation.shape.CornerSize all);
+    method public final androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
     method public abstract androidx.compose.ui.graphics.Outline createOutline-hMawgr0(long size, float topLeft, float topRight, float bottomRight, float bottomLeft);
-    method public final androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
     method public final androidx.compose.foundation.shape.CornerSize getBottomLeft();
     method public final androidx.compose.foundation.shape.CornerSize getBottomRight();
     method public final androidx.compose.foundation.shape.CornerSize getTopLeft();
@@ -482,9 +474,8 @@
   }
 
   public final class GenericShape implements androidx.compose.ui.graphics.Shape {
-    ctor public GenericShape(kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,kotlin.Unit> builder);
-    method public androidx.compose.foundation.shape.GenericShape copy(kotlin.jvm.functions.Function2<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,kotlin.Unit> builder);
-    method public androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    ctor public GenericShape(kotlin.jvm.functions.Function3<? super androidx.compose.ui.graphics.Path,? super androidx.compose.ui.geometry.Size,? super androidx.compose.ui.unit.LayoutDirection,kotlin.Unit> builder);
+    method public androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
   public final class RoundedCornerShape extends androidx.compose.foundation.shape.CornerBasedShape {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
index 0268724..f55948a 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
@@ -22,7 +22,7 @@
 import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.gestures.detectHorizontalDragGestures
-import androidx.compose.foundation.gestures.detectMultitouchGestures
+import androidx.compose.foundation.gestures.detectTransformGestures
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.detectVerticalDragGestures
 import androidx.compose.foundation.gestures.forEachGesture
@@ -443,7 +443,7 @@
     MultitouchArea(
         "Zoom, Pan, and Rotate"
     ) {
-        detectMultitouchGestures(
+        detectTransformGestures(
             panZoomLock = false,
             onGesture = it
         )
@@ -460,7 +460,7 @@
     MultitouchArea(
         "Zoom, Pan, and Rotate Locking to Zoom"
     ) {
-        detectMultitouchGestures(
+        detectTransformGestures(
             panZoomLock = true,
             onGesture = it
         )
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MultitouchGestureSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformGestureSamples.kt
similarity index 97%
rename from compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MultitouchGestureSamples.kt
rename to compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformGestureSamples.kt
index 6986101..929a81f 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MultitouchGestureSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformGestureSamples.kt
@@ -23,7 +23,7 @@
 import androidx.compose.foundation.gestures.calculatePan
 import androidx.compose.foundation.gestures.calculateRotation
 import androidx.compose.foundation.gestures.calculateZoom
-import androidx.compose.foundation.gestures.detectMultitouchGestures
+import androidx.compose.foundation.gestures.detectTransformGestures
 import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.layout.Box
@@ -45,7 +45,7 @@
 
 @Composable
 @Sampled
-fun DetectMultitouchGestures() {
+fun DetectTransformGestures() {
     var angle by remember { mutableStateOf(0f) }
     var zoom by remember { mutableStateOf(1f) }
     var offsetX by remember { mutableStateOf(0f) }
@@ -59,7 +59,7 @@
             )
             .background(Color.Blue)
             .pointerInput {
-                detectMultitouchGestures(
+                detectTransformGestures(
                     onGesture = { _, pan, gestureZoom, gestureRotate ->
                         angle += gestureRotate
                         zoom *= gestureZoom
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BackgroundTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BackgroundTest.kt
index 8ef91ea..484f51f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BackgroundTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BackgroundTest.kt
@@ -22,13 +22,18 @@
 import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Providers
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.platform.AmbientDensity
+import androidx.compose.ui.platform.AmbientLayoutDirection
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.ValueElement
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
@@ -37,6 +42,7 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -57,6 +63,18 @@
 
     private val contentTag = "Content"
 
+    private val rtlAwareShape = object : Shape {
+        override fun createOutline(
+            size: Size,
+            layoutDirection: LayoutDirection,
+            density: Density
+        ) = if (layoutDirection == LayoutDirection.Ltr) {
+            RectangleShape.createOutline(size, layoutDirection, density)
+        } else {
+            CircleShape.createOutline(size, layoutDirection, density)
+        }
+    }
+
     @Before
     fun before() {
         isDebugInspectorInfoEnabled = true
@@ -162,6 +180,62 @@
     }
 
     @Test
+    fun background_rtl_initially() {
+        rule.setContent {
+            SemanticParent {
+                Providers(AmbientLayoutDirection provides LayoutDirection.Rtl) {
+                    Box(
+                        Modifier.preferredSize(40f.toDp())
+                            .background(Color.Magenta)
+                            .background(
+                                brush = SolidColor(Color.White),
+                                shape = rtlAwareShape
+                            )
+                    )
+                }
+            }
+        }
+        val bitmap = rule.onNodeWithTag(contentTag).captureToImage()
+        bitmap.assertShape(
+            density = rule.density,
+            backgroundColor = Color.Magenta,
+            shape = CircleShape,
+            shapeColor = Color.White,
+            shapeOverlapPixelCount = 2.0f
+        )
+    }
+
+    @Test
+    fun background_rtl_after_switch() {
+        val direction = mutableStateOf(LayoutDirection.Ltr)
+        rule.setContent {
+            SemanticParent {
+                Providers(AmbientLayoutDirection provides direction.value) {
+                    Box(
+                        Modifier.preferredSize(40f.toDp())
+                            .background(Color.Magenta)
+                            .background(
+                                brush = SolidColor(Color.White),
+                                shape = rtlAwareShape
+                            )
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            direction.value = LayoutDirection.Rtl
+        }
+        rule.onNodeWithTag(contentTag).captureToImage().assertShape(
+            density = rule.density,
+            backgroundColor = Color.Magenta,
+            shape = CircleShape,
+            shapeColor = Color.White,
+            shapeOverlapPixelCount = 2.0f
+        )
+    }
+
+    @Test
     fun testInspectableParameter1() {
         val modifier = Modifier.background(Color.Magenta) as InspectableValue
         assertThat(modifier.nameFallback).isEqualTo("background")
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BorderTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BorderTest.kt
index 16e0a76..37bde61 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BorderTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BorderTest.kt
@@ -24,19 +24,26 @@
 import androidx.compose.foundation.shape.GenericShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Providers
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.graphics.toPixelMap
+import androidx.compose.ui.platform.AmbientLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -65,6 +72,18 @@
 
     val testTag = "BorderParent"
 
+    private val rtlAwareShape = object : Shape {
+        override fun createOutline(
+            size: Size,
+            layoutDirection: LayoutDirection,
+            density: Density
+        ) = if (layoutDirection == LayoutDirection.Ltr) {
+            Outline.Rectangle(Rect(0f, 1f, 0f, 1f))
+        } else {
+            shape.createOutline(size, layoutDirection, density)
+        }
+    }
+
     @Test
     fun border_color() {
         rule.setContent {
@@ -188,7 +207,7 @@
     @Test
     fun border_triangle_shape() {
         val testTag = "testTag"
-        val triangle = GenericShape() { size ->
+        val triangle = GenericShape { size, _ ->
             lineTo(size.width, 0f)
             lineTo(size.width, size.height)
             close()
@@ -217,6 +236,59 @@
         }
     }
 
+    @Test
+    fun border_rtl_initially() {
+        rule.setContent {
+            SemanticParent {
+                Box(
+                    Modifier.preferredSize(40.0f.toDp(), 40.0f.toDp())
+                        .background(color = Color.Blue)
+                        .border(BorderStroke(10.0f.toDp(), Color.Red), rtlAwareShape)
+                ) {}
+            }
+        }
+        rule.onNodeWithTag(testTag).captureToImage().assertShape(
+            density = rule.density,
+            backgroundColor = Color.Red,
+            shape = shape,
+            backgroundShape = shape,
+            shapeSizeX = 20.0f,
+            shapeSizeY = 20.0f,
+            shapeColor = Color.Blue,
+            shapeOverlapPixelCount = 3.0f
+        )
+    }
+
+    @Test
+    fun border_rtl_after_switch() {
+        val direction = mutableStateOf(LayoutDirection.Ltr)
+        rule.setContent {
+            SemanticParent {
+                Providers(AmbientLayoutDirection provides direction.value) {
+                    Box(
+                        Modifier.preferredSize(40.0f.toDp(), 40.0f.toDp())
+                            .background(color = Color.Blue)
+                            .border(BorderStroke(10.0f.toDp(), Color.Red), rtlAwareShape)
+                    ) {}
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            direction.value = LayoutDirection.Rtl
+        }
+        rule.onNodeWithTag(testTag).captureToImage().assertShape(
+            density = rule.density,
+            backgroundColor = Color.Red,
+            shape = shape,
+            backgroundShape = shape,
+            shapeSizeX = 20.0f,
+            shapeSizeY = 20.0f,
+            shapeColor = Color.Blue,
+            shapeOverlapPixelCount = 3.0f
+        )
+    }
+
     @Composable
     fun SemanticParent(content: @Composable Density.() -> Unit) {
         Box {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
index 1e6f611..11219b1 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
@@ -56,7 +56,10 @@
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.res.imageResource
 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.performClick
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -573,7 +576,7 @@
     }
 
     @Test
-    fun testImageContentDescription() {
+    fun defaultSemanticsWhenContentDescriptionProvided() {
         val testTag = "TestTag"
         rule.setContent {
             Image(
@@ -582,9 +585,8 @@
                 contentDescription = "asdf"
             )
         }
-        rule.onNodeWithTag(testTag).fetchSemanticsNode().let {
-            Assert.assertTrue(it.config.contains(SemanticsProperties.ContentDescription))
-            Assert.assertEquals(it.config[SemanticsProperties.ContentDescription], "asdf")
-        }
+        rule.onNodeWithTag(testTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Image))
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.ContentDescription, "asdf"))
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerBasedShapeTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerBasedShapeTest.kt
index 44215c9..6a26aa1 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerBasedShapeTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerBasedShapeTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.ui.geometry.toRect
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -56,7 +57,7 @@
             onOutlineRequested = assertSizes
         )
 
-        assertThat(impl.createOutline(passedSize, density))
+        assertThat(impl.createOutline(passedSize, LayoutDirection.Ltr, density))
             .isEqualTo(Outline.Rectangle(passedSize.toRect()))
 
         assertThat(assertionExecuted).isTrue()
@@ -89,8 +90,8 @@
             onOutlineRequested = assertSizes
         )
 
-        impl.createOutline(sizeWithLargerWidth, density)
-        impl.createOutline(sizeWithLargerHeight, density)
+        impl.createOutline(sizeWithLargerWidth, LayoutDirection.Ltr, density)
+        impl.createOutline(sizeWithLargerHeight, LayoutDirection.Ltr, density)
 
         assertThat(sizesList).isEqualTo(mutableListOf(sizeWithLargerWidth, sizeWithLargerHeight))
     }
@@ -122,8 +123,8 @@
             onOutlineRequested = assertSizes
         )
 
-        impl.createOutline(sizeWithLargerWidth, density)
-        impl.createOutline(sizeWithLargerHeight, density)
+        impl.createOutline(sizeWithLargerWidth, LayoutDirection.Ltr, density)
+        impl.createOutline(sizeWithLargerHeight, LayoutDirection.Ltr, density)
 
         assertThat(sizesList).isEqualTo(mutableListOf(sizeWithLargerWidth, sizeWithLargerHeight))
     }
@@ -155,8 +156,8 @@
             onOutlineRequested = assertSizes
         )
 
-        impl.createOutline(sizeWithLargerWidth, density)
-        impl.createOutline(sizeWithLargerHeight, density)
+        impl.createOutline(sizeWithLargerWidth, LayoutDirection.Ltr, density)
+        impl.createOutline(sizeWithLargerHeight, LayoutDirection.Ltr, density)
 
         assertThat(sizesList).isEqualTo(mutableListOf(sizeWithLargerWidth, sizeWithLargerHeight))
     }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CutCornerShapeTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CutCornerShapeTest.kt
index 834d3e4..1f07917 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CutCornerShapeTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CutCornerShapeTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.graphics.PathOperation
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -177,7 +178,7 @@
         ).isFalse()
     }
 
-    private fun Shape.toOutline() = createOutline(size, density)
+    private fun Shape.toOutline() = createOutline(size, LayoutDirection.Ltr, density)
 }
 
 fun assertPathsEquals(path1: Path, path2: Path) {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/RoundedCornerShapeTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/RoundedCornerShapeTest.kt
index 13b823e..c8e7d30 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/RoundedCornerShapeTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/RoundedCornerShapeTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -191,5 +192,5 @@
         ).isFalse()
     }
 
-    private fun Shape.toOutline() = createOutline(size, density)
+    private fun Shape.toOutline() = createOutline(size, LayoutDirection.Ltr, density)
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
index 6278c8d2..a59db97 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * Draws [shape] with a solid [color] behind the content.
@@ -93,6 +94,7 @@
 
     // naive cache outline calculation if size is the same
     private var lastSize: Size? = null
+    private var lastLayoutDirection: LayoutDirection? = null
     private var lastOutline: Outline? = null
 
     override fun ContentDrawScope.draw() {
@@ -112,10 +114,10 @@
 
     private fun ContentDrawScope.drawOutline() {
         val outline =
-            if (size == lastSize) {
+            if (size == lastSize && layoutDirection == lastLayoutDirection) {
                 lastOutline!!
             } else {
-                shape.createOutline(size, this)
+                shape.createOutline(size, layoutDirection, this)
             }
         color?.let { drawOutline(outline, color = color) }
         brush?.let { drawOutline(outline, brush = brush, alpha = alpha) }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BaseTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BaseTextField.kt
index bb860d5..ae33409 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BaseTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BaseTextField.kt
@@ -110,7 +110,7 @@
     onValueChange: (TextFieldValue) -> Unit,
     modifier: Modifier = Modifier,
     textColor: Color = Color.Unspecified,
-    textStyle: TextStyle = AmbientTextStyle.current,
+    textStyle: TextStyle = TextStyle(),
     keyboardType: KeyboardType = KeyboardType.Text,
     imeAction: ImeAction = ImeAction.Default,
     onImeActionPerformed: (ImeAction) -> Unit = {},
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
index bdfc205..20cc811 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
@@ -74,7 +74,7 @@
     factory = {
         this.then(
             Modifier.drawWithCache {
-                val outline: Outline = shape.createOutline(size, this)
+                val outline: Outline = shape.createOutline(size, layoutDirection, this)
                 val borderSize = if (width == Dp.Hairline) 1f else width.toPx()
 
                 var insetOutline: Outline? = null // outline used for roundrect/generic shapes
@@ -104,7 +104,7 @@
                             size.width - inset * 2,
                             size.height - inset * 2
                         )
-                        insetOutline = shape.createOutline(insetSize, this)
+                        insetOutline = shape.createOutline(insetSize, layoutDirection, this)
                         stroke = Stroke(strokeWidth)
                         pathClip = if (outline is Outline.Rounded) {
                             Path().apply { addRoundRect(outline.roundRect) }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
index 72f9598..2f8d50d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
@@ -33,7 +33,9 @@
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.rememberVectorPainter
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 
 /**
@@ -171,7 +173,10 @@
     colorFilter: ColorFilter? = null
 ) {
     val semantics = if (contentDescription != null) {
-        Modifier.semantics { this.contentDescription = contentDescription }
+        Modifier.semantics {
+            this.contentDescription = contentDescription
+            this.role = Role.Image
+        }
     } else {
         Modifier
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Text.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Text.kt
deleted file mode 100644
index 0f13853..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Text.kt
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("DEPRECATION")
-
-package androidx.compose.foundation
-
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.foundation.text.InlineTextContent
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ComposableContract
-import androidx.compose.runtime.ExperimentalComposeApi
-import androidx.compose.runtime.Providers
-import androidx.compose.runtime.ambientOf
-import androidx.compose.runtime.structuralEqualityPolicy
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.takeOrElse
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.Paragraph
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.TextUnit
-
-/**
- * High level element that displays text and provides semantics / accessibility information.
- *
- * The default [style] uses the [AmbientTextStyle] defined by a theme. If you are setting your
- * own style, you may want to consider first retrieving [AmbientTextStyle], and using
- * [TextStyle.copy] to keep any theme defined attributes, only modifying the specific attributes
- * you want to override.
- *
- * For ease of use, commonly used parameters from [TextStyle] are also present here. The order of
- * precedence is as follows:
- * - If a parameter is explicitly set here (i.e, it is _not_ `null` or [TextUnit.Unspecified]),
- * then this parameter will always be used.
- * - If a parameter is _not_ set, (`null` or [TextUnit.Unspecified]), then the corresponding value
- * from [style] will be used instead.
- *
- * Additionally, for [color], if [color] is not set, and [style] does not have a color, then
- * [AmbientContentColor] will be used - this allows this [Text] or element containing this [Text] to
- * adapt to different background colors and still maintain contrast and accessibility.
- *
- * @param text The text to be displayed.
- * @param modifier [Modifier] to apply to this layout node.
- * @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set,
- * this will be [AmbientContentColor].
- * @param fontSize The size of glyphs to use when painting the text. See [TextStyle.fontSize].
- * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
- * See [TextStyle.fontStyle].
- * @param fontWeight The typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
- * @param fontFamily The font family to be used when rendering the text. See [TextStyle.fontFamily].
- * @param letterSpacing The amount of space to add between each letter.
- * See [TextStyle.letterSpacing].
- * @param textDecoration The decorations to paint on the text (e.g., an underline).
- * See [TextStyle.textDecoration].
- * @param textAlign The alignment of the text within the lines of the paragraph.
- * See [TextStyle.textAlign].
- * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
- * See [TextStyle.lineHeight].
- * @param overflow How visual overflow should be handled.
- * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
- * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
- * [overflow] and TextAlign may have unexpected effects.
- * @param maxLines An optional maximum number of lines for the text to span, wrapping if
- * necessary. If the text exceeds the given number of lines, it will be truncated according to
- * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
- * @param onTextLayout Callback that is executed when a new text layout is calculated.
- * @param style Style configuration for the text such as color, font, line height etc.
- */
-@Deprecated(
-    message = "Use androidx.compose.material.Text for a high level Text component that " +
-        "consumes theming information, or androidx.compose.foundation.text.BasicText for a basic " +
-        "unopinionated component that does not have default theming",
-    replaceWith = ReplaceWith(
-        "Text(text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, " +
-            "letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, maxLines, " +
-            "onTextLayout, style)",
-        "androidx.compose.material.Text"
-    )
-)
-@Composable
-fun Text(
-    text: String,
-    modifier: Modifier = Modifier,
-    color: Color = Color.Unspecified,
-    fontSize: TextUnit = TextUnit.Unspecified,
-    fontStyle: FontStyle? = null,
-    fontWeight: FontWeight? = null,
-    fontFamily: FontFamily? = null,
-    letterSpacing: TextUnit = TextUnit.Unspecified,
-    textDecoration: TextDecoration? = null,
-    textAlign: TextAlign? = null,
-    lineHeight: TextUnit = TextUnit.Unspecified,
-    overflow: TextOverflow = TextOverflow.Clip,
-    softWrap: Boolean = true,
-    maxLines: Int = Int.MAX_VALUE,
-    onTextLayout: (TextLayoutResult) -> Unit = {},
-    style: TextStyle = AmbientTextStyle.current
-) {
-    Text(
-        AnnotatedString(text),
-        modifier,
-        color,
-        fontSize,
-        fontStyle,
-        fontWeight,
-        fontFamily,
-        letterSpacing,
-        textDecoration,
-        textAlign,
-        lineHeight,
-        overflow,
-        softWrap,
-        maxLines,
-        emptyMap(),
-        onTextLayout,
-        style
-    )
-}
-
-/**
- * High level element that displays text and provides semantics / accessibility information.
- *
- * The default [style] uses the [AmbientTextStyle] defined by a theme. If you are setting your
- * own style, you may want to consider first retrieving [AmbientTextStyle], and using
- * [TextStyle.copy] to keep any theme defined attributes, only modifying the specific attributes
- * you want to override.
- *
- * For ease of use, commonly used parameters from [TextStyle] are also present here. The order of
- * precedence is as follows:
- * - If a parameter is explicitly set here (i.e, it is _not_ `null` or [TextUnit.Unspecified]),
- * then this parameter will always be used.
- * - If a parameter is _not_ set, (`null` or [TextUnit.Unspecified]), then the corresponding value
- * from [style] will be used instead.
- *
- * Additionally, for [color], if [color] is not set, and [style] does not have a color, then
- * [AmbientContentColor] will be used - this allows this [Text] or element containing this [Text] to
- * adapt to different background colors and still maintain contrast and accessibility.
- *
- * @param text The text to be displayed.
- * @param modifier [Modifier] to apply to this layout node.
- * @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set,
- * this will be [AmbientContentColor].
- * @param fontSize The size of glyphs to use when painting the text. See [TextStyle.fontSize].
- * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
- * See [TextStyle.fontStyle].
- * @param fontWeight The typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
- * @param fontFamily The font family to be used when rendering the text. See [TextStyle.fontFamily].
- * @param letterSpacing The amount of space to add between each letter.
- * See [TextStyle.letterSpacing].
- * @param textDecoration The decorations to paint on the text (e.g., an underline).
- * See [TextStyle.textDecoration].
- * @param textAlign The alignment of the text within the lines of the paragraph.
- * See [TextStyle.textAlign].
- * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
- * See [TextStyle.lineHeight].
- * @param overflow How visual overflow should be handled.
- * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
- * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
- * [overflow] and TextAlign may have unexpected effects.
- * @param maxLines An optional maximum number of lines for the text to span, wrapping if
- * necessary. If the text exceeds the given number of lines, it will be truncated according to
- * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
- * @param inlineContent A map store composables that replaces certain ranges of the text. It's
- * used to insert composables into text layout. Check [InlineTextContent] for more information.
- * @param onTextLayout Callback that is executed when a new text layout is calculated.
- * @param style Style configuration for the text such as color, font, line height etc.
- */
-@Deprecated(
-    message = "Use androidx.compose.material.Text for a high level Text component that " +
-        "consumes theming information, or androidx.compose.foundation.text.BasicText for a basic " +
-        "unopinionated component that does not have default theming",
-    replaceWith = ReplaceWith(
-        "Text(text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, " +
-            "letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, maxLines, " +
-            "inlineContent, onTextLayout, style)",
-        "androidx.compose.material.Text"
-    )
-)
-@Composable
-fun Text(
-    text: AnnotatedString,
-    modifier: Modifier = Modifier,
-    color: Color = Color.Unspecified,
-    fontSize: TextUnit = TextUnit.Unspecified,
-    fontStyle: FontStyle? = null,
-    fontWeight: FontWeight? = null,
-    fontFamily: FontFamily? = null,
-    letterSpacing: TextUnit = TextUnit.Unspecified,
-    textDecoration: TextDecoration? = null,
-    textAlign: TextAlign? = null,
-    lineHeight: TextUnit = TextUnit.Unspecified,
-    overflow: TextOverflow = TextOverflow.Clip,
-    softWrap: Boolean = true,
-    maxLines: Int = Int.MAX_VALUE,
-    inlineContent: Map<String, InlineTextContent> = mapOf(),
-    onTextLayout: (TextLayoutResult) -> Unit = {},
-    style: TextStyle = AmbientTextStyle.current
-) {
-    val textColor = color.takeOrElse { style.color.takeOrElse { AmbientContentColor.current } }
-    val mergedStyle = style.merge(
-        TextStyle(
-            color = textColor,
-            fontSize = fontSize,
-            fontWeight = fontWeight,
-            textAlign = textAlign,
-            lineHeight = lineHeight,
-            fontFamily = fontFamily,
-            textDecoration = textDecoration,
-            fontStyle = fontStyle,
-            letterSpacing = letterSpacing
-        )
-    )
-    BasicText(
-        text,
-        modifier,
-        mergedStyle,
-        onTextLayout,
-        overflow,
-        softWrap,
-        maxLines,
-        inlineContent
-    )
-}
-
-/**
- * Ambient containing the preferred [TextStyle] that will be used by [Text] components by default.
- * To set the value for this ambient, see [ProvideTextStyle] which will merge any missing
- * [TextStyle] properties with the existing [TextStyle] set in this ambient.
- *
- * @see ProvideTextStyle
- */
-@Deprecated(
-    message = "AmbientTextStyle has moved to the Material library. For non-Material applications," +
-        " create your own design system specific theming ambients.",
-    replaceWith = ReplaceWith(
-        "AmbientTextStyle", "androidx.compose.material.AmbientTextStyle"
-    )
-)
-val AmbientTextStyle = ambientOf(
-    @OptIn(ExperimentalComposeApi::class) structuralEqualityPolicy()
-) { TextStyle() }
-
-// TODO: b/156598010 remove this and replace with fold definition on the backing Ambient
-/**
- * This function is used to set the current value of [AmbientTextStyle], merging the given style
- * with the current style values for any missing attributes. Any [Text] components included in
- * this component's [children] will be styled with this style unless styled explicitly.
- *
- * @see AmbientTextStyle
- */
-@Suppress("ComposableLambdaParameterNaming")
-@Deprecated(
-    message = "ProvideTextStyle has moved to the Material library. For non-Material applications," +
-        " create your own design system specific theming ambients.",
-    replaceWith = ReplaceWith(
-        "ProvideTextStyle(value, children)",
-        "androidx.compose.material.ProvideTextStyle"
-    )
-)
-@Composable
-fun ProvideTextStyle(value: TextStyle, children: @Composable () -> Unit) {
-    val mergedStyle = AmbientTextStyle.current.merge(value)
-    Providers(AmbientTextStyle provides mergedStyle, content = children)
-}
-
-/**
- * This effect is used to read the current value of the Text style ambient. Any [Text]
- * components included in this component's children will be styled with this style unless
- * styled explicitly.
- */
-@Deprecated(
-    message = "Use AmbientTextStyle.current explicitly",
-    replaceWith = ReplaceWith(
-        "AmbientTextStyle.current",
-        "androidx.compose.foundation.AmbientTextStyle"
-    )
-)
-@Composable
-@ComposableContract(readonly = true)
-fun currentTextStyle(): TextStyle = AmbientTextStyle.current
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformGestureDetector.kt
similarity index 98%
rename from compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetector.kt
rename to compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformGestureDetector.kt
index 81b7b51..e66482d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformGestureDetector.kt
@@ -45,9 +45,9 @@
  * gestures are detected.
  *
  * Example Usage:
- * @sample androidx.compose.foundation.samples.DetectMultitouchGestures
+ * @sample androidx.compose.foundation.samples.DetectTransformGestures
  */
-suspend fun PointerInputScope.detectMultitouchGestures(
+suspend fun PointerInputScope.detectTransformGestures(
     panZoomLock: Boolean = false,
     onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit
 ) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
index 60e6843..1ccebac 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
@@ -20,6 +20,7 @@
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import kotlin.math.min
 
 /**
@@ -39,7 +40,11 @@
     val bottomLeft: CornerSize
 ) : Shape {
 
-    final override fun createOutline(size: Size, density: Density): Outline {
+    final override fun createOutline(
+        size: Size,
+        layoutDirection: LayoutDirection,
+        density: Density
+    ): Outline {
         val minDimension = size.minDimension
         val topLeft = min(topLeft.toPx(size, density), minDimension)
         val topRight = min(topRight.toPx(size, density), minDimension)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt
index 8f64bd2..e3a65a3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/GenericShape.kt
@@ -21,20 +21,35 @@
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * Creates [Shape] defined by applying the provided [builder] on a [Path].
  *
  * @param builder the builder lambda to apply on a [Path]
  */
-data class GenericShape(
-    private val builder: Path.(size: Size) -> Unit
+class GenericShape(
+    private val builder: Path.(size: Size, layoutDirection: LayoutDirection) -> Unit
 ) : Shape {
-    override fun createOutline(size: Size, density: Density): Outline {
+
+    override fun createOutline(
+        size: Size,
+        layoutDirection: LayoutDirection,
+        density: Density
+    ): Outline {
         val path = Path().apply {
-            builder(size)
+            builder(size, layoutDirection)
             close()
         }
         return Outline.Generic(path)
     }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        return (other as? GenericShape)?.builder == builder
+    }
+
+    override fun hashCode(): Int {
+        return builder.hashCode()
+    }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index b99968f..0c7a67c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -20,7 +20,7 @@
 import androidx.compose.foundation.text.selection.MultiWidgetSelectionDelegate
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.DisposableEffectDisposable
+import androidx.compose.runtime.DisposableEffectResult
 import androidx.compose.runtime.DisposableEffectScope
 import androidx.compose.runtime.emptyContent
 import androidx.compose.runtime.getValue
@@ -328,7 +328,7 @@
         }
     }
 
-    val commit: DisposableEffectScope.() -> DisposableEffectDisposable = {
+    val commit: DisposableEffectScope.() -> DisposableEffectResult = {
         // if no SelectionContainer is added as parent selectionRegistrar will be null
         state.selectable = selectionRegistrar?.let { selectionRegistrar ->
             selectionRegistrar.subscribe(
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TransformGestureDetectorTest.kt
similarity index 98%
rename from compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetectorTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TransformGestureDetectorTest.kt
index aa3caaa..0a0de0f 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/MultitouchGestureDetectorTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TransformGestureDetectorTest.kt
@@ -28,7 +28,7 @@
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
-class MultitouchGestureDetectorTest(val panZoomLock: Boolean) {
+class TransformGestureDetectorTest(val panZoomLock: Boolean) {
     companion object {
         @JvmStatic
         @Parameterized.Parameters
@@ -44,7 +44,7 @@
     private var zoomAmount = 1f
 
     private val util = SuspendingGestureTestUtil {
-        detectMultitouchGestures(panZoomLock = panZoomLock) { c, pan, gestureZoom, gestureAngle ->
+        detectTransformGestures(panZoomLock = panZoomLock) { c, pan, gestureZoom, gestureAngle ->
             centroid = c
             if (gestureAngle != 0f) {
                 rotated = true
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lifecycle/Lifecycle.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lifecycle/Lifecycle.kt
new file mode 100644
index 0000000..32aee34
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/lifecycle/Lifecycle.kt
@@ -0,0 +1,387 @@
+/*
+ * 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("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE")
+
+package androidx.compose.integration.docs.lifecycle
+
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.OnBackPressedDispatcher
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Button
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Scaffold
+import androidx.compose.material.ScaffoldState
+import androidx.compose.material.Text
+import androidx.compose.material.rememberScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.key
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlin.random.Random
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/xxxxxxxxxxxxxxx
+ *
+ * No action required if it's modified.
+ */
+
+private object LifecycleSnippet1 {
+    @Composable
+    fun MyComposable() {
+        Column {
+            Text("Hello")
+            Text("World")
+        }
+    }
+}
+private object LifecycleSnippet2 {
+    @Composable
+    fun LoginScreen(showError: Boolean) {
+        if (showError) {
+            LoginError()
+        }
+        LoginInput() // This call site is used to generate the key
+    }
+
+    @Composable
+    fun LoginInput() { /* ... */ }
+}
+
+private object LifecycleSnippet3 {
+    @Composable
+    fun MoviesScreen(movies: List<Movie>) {
+        LazyColumn {
+            items(movies) { movie ->
+                // All MovieOverview composables in Composition will have the same key!
+                // Thus, all calls will always recompose and restart all side effects.
+                MovieOverview(movie)
+            }
+        }
+    }
+}
+
+private object LifecycleSnippet4 {
+    @Composable
+    fun MoviesScreen(movies: List<Movie>) {
+        LazyColumn {
+            items(movies) { movie ->
+                key(movie.id) { // Use movie.id as key
+                    MovieOverview(movie)
+                }
+            }
+        }
+    }
+}
+
+private object LifecycleSnippet5 {
+    // Marking the type as stable to favor skipping and smart recompositions.
+    @Stable
+    interface UiState<T : Result<T>> {
+        val value: T?
+        val exception: Throwable?
+
+        val hasError: Boolean
+            get() = exception != null
+    }
+}
+
+private object LifecycleSnippet6 {
+    @Composable
+    fun MyComposable() {
+        DisposableEffect(Unit) {
+            // Initial composition. The composable is added to the UI tree.
+
+            onDispose {
+                // The composable is removed from the UI tree.
+            }
+        }
+        /* ... */
+    }
+}
+
+private object LifecycleSnippet7 {
+    @Composable
+    fun MyComposable() {
+
+        SideEffect {
+            // The composable went through a recomposition
+        }
+        /* ... */
+    }
+}
+
+@ExperimentalMaterialApi
+private object LifecycleSnippet8 {
+    @Composable
+    fun MyScreen(
+        state: UiState<List<Movie>>,
+        scaffoldState: ScaffoldState = rememberScaffoldState()
+    ) {
+
+        // If the UI state contains an error, show snackbar
+        if (state.hasError) {
+
+            // `LaunchedEffect` will cancel and re-launch if `scaffoldState` changes
+            LaunchedEffect(scaffoldState) {
+                // Show snackbar using a coroutine, when the coroutine is cancelled the
+                // snackbar will automatically dismiss. This coroutine will cancel whenever
+                // `state.hasError` is false, and only start when `state.hasError`
+                // is true (due to the above if-check), or if `scaffoldState` changes.
+                scaffoldState.snackbarHostState.showSnackbar(
+                    message = "Error message",
+                    actionLabel = "Retry message"
+                )
+            }
+        }
+
+        Scaffold(scaffoldState = scaffoldState) {
+            /* ... */
+        }
+    }
+}
+
+@ExperimentalMaterialApi
+private object LifecycleSnippet9 {
+    @Composable
+    fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {
+
+        // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
+        val scope = rememberCoroutineScope()
+
+        Scaffold(scaffoldState = scaffoldState) {
+            Column {
+                /* ... */
+                Button(
+                    onClick = {
+                        // Create a new coroutine in the event handler to show a snackbar
+                        scope.launch {
+                            scaffoldState.snackbarHostState.showSnackbar("Something happened!")
+                        }
+                    }
+                ) {
+                    Text("Press me")
+                }
+            }
+        }
+    }
+}
+
+private object LifecycleSnippet10 {
+    @Composable
+    fun LandingScreen(onTimeout: () -> Unit) {
+
+        // This will always refer to the latest onTimeout function that
+        // LandingScreen was recomposed with
+        val currentOnTimeout by rememberUpdatedState(onTimeout)
+
+        // Create an effect that matches the lifecycle of LandingScreen.
+        // If LandingScreen recomposes, the delay shouldn't start again.
+        LaunchedEffect(Unit) {
+            delay(SplashWaitTimeMillis)
+            currentOnTimeout()
+        }
+
+        /* Landing screen content */
+    }
+}
+
+private object LifecycleSnippet11 {
+    @Composable
+    fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {
+
+        // Safely update the current `onBack` lambda when a new one is provided
+        val currentOnBack by rememberUpdatedState(onBack)
+
+        // Remember in Composition a back callback that calls the `onBack` lambda
+        val backCallback = remember {
+            // Always intercept back events. See the SideEffect for a more complete version
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    currentOnBack()
+                }
+            }
+        }
+
+        // If `backDispatcher` changes, dispose and reset the effect
+        DisposableEffect(backDispatcher) {
+            // Add callback to the backDispatcher
+            backDispatcher.addCallback(backCallback)
+
+            // When the effect leaves the Composition, remove the callback
+            onDispose {
+                backCallback.remove()
+            }
+        }
+    }
+}
+
+private object LifecycleSnippet12 {
+    @Composable
+    fun BackHandler(
+        backDispatcher: OnBackPressedDispatcher,
+        enabled: Boolean = true, // Whether back events should be intercepted or not
+        onBack: () -> Unit
+    ) {
+        // START - DO NOT COPY IN CODE SNIPPET
+        val currentOnBack by rememberUpdatedState(onBack)
+
+        val backCallback = remember {
+            // Always intercept back events. See the SideEffect for a more complete version
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    currentOnBack()
+                }
+            }
+        }
+        // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
+
+        // On every successful composition, update the callback with the `enabled` value
+        // to tell `backCallback` whether back events should be intercepted or not
+        SideEffect {
+            backCallback.isEnabled = enabled
+        }
+
+        /* Rest of the code */
+    }
+}
+
+private object LifecycleSnippet13 {
+    @Composable
+    fun loadNetworkImage(
+        url: String,
+        imageRepository: ImageRepository
+    ): State<Result<Image>> {
+
+        // Creates a State<T> with Result.Loading as initial value
+        // If either `url` or `imageRepository` changes, the running producer
+        // will cancel and will be re-launched with the new inputs.
+        return produceState(initialValue = Result.Loading, url, imageRepository) {
+
+            // In a coroutine, can make suspend calls
+            val image = imageRepository.load(url)
+
+            // Update State with either an Error or Success result.
+            // This will trigger a recomposition where this State is read
+            value = if (image == null) {
+                Result.Error
+            } else {
+                Result.Success(image)
+            }
+        }
+    }
+}
+
+private object LifecycleSnippet14 {
+    @Composable
+    fun GreetingScreen(
+        user: State<User>,
+        weather: State<Weather>
+    ) {
+
+        val greeting = derivedStateOf { prepareGreeting(user.value, weather.value) }
+
+        Column {
+            // Changing either `user` or `weather` will cause `prepareGreeting` to
+            // execute again and, subsequently, `GreetingMessage` will recompose
+            // but it won't trigger this composable, `GreetingScreen`, to recompose
+            GreetingMessage(greeting)
+            /* ... */
+        }
+    }
+
+    @Composable
+    fun GreetingMessage(greeting: State<Greeting>) {
+        Text("Hi ${greeting.value.name}")
+    }
+}
+
+private object LifecycleSnippet15 {
+    @Composable
+    fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {
+        // START - DO NOT COPY IN CODE SNIPPET
+        val currentOnBack by rememberUpdatedState(onBack)
+
+        val backCallback = remember {
+            // Always intercept back events. See the SideEffect for a more complete version
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    currentOnBack()
+                }
+            }
+        }
+        // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
+
+        DisposableEffect(backDispatcher) {
+            backDispatcher.addCallback(backCallback)
+            onDispose {
+                backCallback.remove()
+            }
+        }
+    }
+}
+
+/*
+Fakes needed for snippets to build:
+ */
+private const val SplashWaitTimeMillis = 1000L
+
+@Composable
+private fun LoginError() { }
+
+@Composable
+private fun MovieOverview(movie: Movie) { }
+private data class Movie(val id: Long)
+
+private data class UiState<T>(
+    val loading: Boolean = false,
+    val exception: Exception? = null,
+    val data: T? = null
+) {
+    val hasError: Boolean
+        get() = exception != null
+}
+
+private class Image
+private class ImageRepository {
+    fun load(url: String): Image? = if (Random.nextInt() == 0) Image() else null // Avoid warnings
+}
+
+private sealed class Result<out R> {
+    data class Success<out T>(val data: T) : Result<T>()
+    object Loading : Result<Nothing>()
+    object Error : Result<Nothing>()
+}
+
+private class User
+private class Weather
+private class Greeting(val name: String)
+private fun prepareGreeting(user: User, weather: Weather) = Greeting("haha")
diff --git a/compose/integration-tests/src/main/java/androidx/ui/integration/test/core/ComponentWithTwoLayoutNodesTestCase.kt b/compose/integration-tests/src/main/java/androidx/ui/integration/test/core/ComponentWithTwoLayoutNodesTestCase.kt
index 9143260..dbf855e 100644
--- a/compose/integration-tests/src/main/java/androidx/ui/integration/test/core/ComponentWithTwoLayoutNodesTestCase.kt
+++ b/compose/integration-tests/src/main/java/androidx/ui/integration/test/core/ComponentWithTwoLayoutNodesTestCase.kt
@@ -43,7 +43,7 @@
             val innerSize = getInnerSize().value
             Canvas(Modifier.preferredSize(innerSize)) {
                 drawOutline(
-                    CircleShape.createOutline(size, this),
+                    CircleShape.createOutline(size, layoutDirection, this),
                     Color.Cyan
                 )
             }
diff --git a/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt b/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt
index 6ff4cb0..42ff69c 100644
--- a/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt
+++ b/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt
@@ -52,6 +52,11 @@
  * and provides layout size matching the icon.
  *
  * @sample androidx.compose.material.icons.samples.DrawIcon
+ *
+ * Note that only the most commonly used icons are provided by default. You can add a dependency on
+ * androidx.compose.material:material-icons-extended to access every icon, but note that due to
+ * the very large size of this dependency you should make sure to use R8 / ProGuard to remove
+ * unused icons from your application.
  */
 object Icons {
     /**
diff --git a/compose/material/material-icons-extended/src/androidAndroidTest/kotlin/androidx/compose/material/icons/IconComparisonTest.kt b/compose/material/material-icons-extended/src/androidAndroidTest/kotlin/androidx/compose/material/icons/IconComparisonTest.kt
index 611a8b0..7ad8cb3 100644
--- a/compose/material/material-icons-extended/src/androidAndroidTest/kotlin/androidx/compose/material/icons/IconComparisonTest.kt
+++ b/compose/material/material-icons-extended/src/androidAndroidTest/kotlin/androidx/compose/material/icons/IconComparisonTest.kt
@@ -18,12 +18,12 @@
 
 import android.graphics.Bitmap
 import android.os.Build
+import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.preferredSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Composition
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.paint
@@ -104,10 +104,9 @@
         iconSublist.forEach { (property, drawableName) ->
             var xmlVector: ImageVector? = null
             val programmaticVector = property.get()
-            var composition: Composition? = null
 
             rule.activityRule.scenario.onActivity {
-                composition = it.setContent {
+                it.setContent {
                     xmlVector = drawableName.toImageVector()
                     DrawVectors(programmaticVector, xmlVector!!)
                 }
@@ -131,8 +130,8 @@
             )
 
             // Dispose between composing each pair of icons to ensure correctness
-            rule.runOnUiThread {
-                composition?.dispose()
+            rule.activityRule.scenario.onActivity {
+                it.setContentView(View(it))
             }
         }
     }
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
index e07166c..8e16dc1 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
@@ -175,7 +175,7 @@
     }
 }
 
-private val TriangleShape = GenericShape { size ->
+private val TriangleShape = GenericShape { size, _ ->
     moveTo(size.width / 2f, 0f)
     lineTo(size.width, size.height)
     lineTo(0f, size.height)
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
index c226ac0..9d74add 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
@@ -274,7 +274,7 @@
 /**
  * A [GenericShape] that draws a box with a triangle at the bottom center to indicate a popup.
  */
-private val MagnifierPopupShape = GenericShape { size ->
+private val MagnifierPopupShape = GenericShape { size, _ ->
     val width = size.width
     val height = size.height
 
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
index dea21b2..ab97ef7 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
@@ -30,7 +30,10 @@
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.platform.AmbientDensity
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertHeightIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.captureToImage
@@ -42,7 +45,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -193,7 +195,7 @@
     }
 
     @Test
-    fun contentDescriptionAppliedToIcon() {
+    fun defaultSemanticsWhenContentDescriptionProvided() {
         val testTag = "TestTag"
         rule.setContent {
             Icon(
@@ -203,11 +205,9 @@
             )
         }
 
-        rule.onNodeWithTag(testTag).fetchSemanticsNode().let {
-            assertThat(it.config.contains(SemanticsProperties.ContentDescription)).isTrue()
-            assertThat(it.config[SemanticsProperties.ContentDescription])
-                .isEqualTo("qwerty")
-        }
+        rule.onNodeWithTag(testTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.ContentDescription, "qwerty"))
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Image))
     }
 
     private fun createBitmapWithColor(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AppBar.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AppBar.kt
index e1c3d19..70ad0fc 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AppBar.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/AppBar.kt
@@ -42,6 +42,7 @@
 import androidx.compose.ui.graphics.addOutline
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import kotlin.math.sqrt
 
@@ -203,12 +204,16 @@
     val fabPlacement: FabPlacement
 ) : Shape {
 
-    override fun createOutline(size: Size, density: Density): Outline {
+    override fun createOutline(
+        size: Size,
+        layoutDirection: LayoutDirection,
+        density: Density
+    ): Outline {
         val boundingRectangle = Path().apply {
             addRect(Rect(0f, 0f, size.width, size.height))
         }
         val path = Path().apply {
-            addCutoutShape(density)
+            addCutoutShape(layoutDirection, density)
             // Subtract this path from the bounding rectangle
             op(boundingRectangle, this, PathOperation.difference)
         }
@@ -219,7 +224,7 @@
      * Adds the filled [cutoutShape] to the [Path]. The path can the be subtracted from the main
      * rectangle path used for the app bar, to create the resulting cutout shape.
      */
-    private fun Path.addCutoutShape(density: Density) {
+    private fun Path.addCutoutShape(layoutDirection: LayoutDirection, density: Density) {
         // The gap on all sides between the FAB and the cutout
         val cutoutOffset = with(density) { BottomAppBarCutoutOffset.toPx() }
 
@@ -236,7 +241,7 @@
         // cut into the app bar
         val cutoutStartY = -cutoutRadius
 
-        addOutline(cutoutShape.createOutline(cutoutSize, density))
+        addOutline(cutoutShape.createOutline(cutoutSize, layoutDirection, density))
         translate(Offset(cutoutStartX, cutoutStartY))
 
         // TODO: consider exposing the custom cutout shape instead of just replacing circle shapes?
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
index aeb0954..e050dc5 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
@@ -31,7 +31,9 @@
 import androidx.compose.ui.graphics.toolingGraphicsLayer
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 
@@ -117,7 +119,10 @@
     // TODO: b/149735981 semantics for content description
     val colorFilter = if (tint == Color.Unspecified) null else ColorFilter.tint(tint)
     val semantics = if (contentDescription != null) {
-        Modifier.semantics { this.contentDescription = contentDescription }
+        Modifier.semantics {
+            this.contentDescription = contentDescription
+            this.role = Role.Image
+        }
     } else {
         Modifier
     }
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 10b7de7..0193213 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -324,20 +324,24 @@
 
   public final class Recomposer extends androidx.compose.runtime.CompositionReference {
     ctor public Recomposer(kotlin.coroutines.CoroutineContext effectCoroutineContext);
+    method public androidx.compose.runtime.RecomposerInfo asRecomposerInfo();
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public int getChangeCount();
+    method public long getChangeCount();
+    method public boolean getHasPendingWork();
     method public kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> getState();
-    method public boolean hasInvalidations();
+    method @Deprecated public boolean hasInvalidations();
     method public suspend Object? join(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public suspend Object? runRecomposeAndApplyChanges(kotlin.coroutines.Continuation<?> p);
     method public void shutDown();
-    property public final int changeCount;
+    property public final long changeCount;
+    property public final boolean hasPendingWork;
     property public final kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> state;
     field public static final androidx.compose.runtime.Recomposer.Companion Companion;
   }
 
   public static final class Recomposer.Companion {
-    method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.Recomposer current();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.compose.runtime.RecomposerInfo>> getRunningRecomposers();
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.compose.runtime.RecomposerInfo>> runningRecomposers;
   }
 
   public enum Recomposer.State {
@@ -349,6 +353,15 @@
     enum_constant public static final androidx.compose.runtime.Recomposer.State ShuttingDown;
   }
 
+  public interface RecomposerInfo {
+    method public long getChangeCount();
+    method public boolean getHasPendingWork();
+    method public kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> getState();
+    property public abstract long changeCount;
+    property public abstract boolean hasPendingWork;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> state;
+  }
+
   public final class RecomposerKt {
     method public static suspend <R> Object? withRunningRecomposer(kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.runtime.Recomposer,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
   }
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 10b7de7..0193213 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -324,20 +324,24 @@
 
   public final class Recomposer extends androidx.compose.runtime.CompositionReference {
     ctor public Recomposer(kotlin.coroutines.CoroutineContext effectCoroutineContext);
+    method public androidx.compose.runtime.RecomposerInfo asRecomposerInfo();
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public int getChangeCount();
+    method public long getChangeCount();
+    method public boolean getHasPendingWork();
     method public kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> getState();
-    method public boolean hasInvalidations();
+    method @Deprecated public boolean hasInvalidations();
     method public suspend Object? join(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public suspend Object? runRecomposeAndApplyChanges(kotlin.coroutines.Continuation<?> p);
     method public void shutDown();
-    property public final int changeCount;
+    property public final long changeCount;
+    property public final boolean hasPendingWork;
     property public final kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> state;
     field public static final androidx.compose.runtime.Recomposer.Companion Companion;
   }
 
   public static final class Recomposer.Companion {
-    method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.Recomposer current();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.compose.runtime.RecomposerInfo>> getRunningRecomposers();
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.compose.runtime.RecomposerInfo>> runningRecomposers;
   }
 
   public enum Recomposer.State {
@@ -349,6 +353,15 @@
     enum_constant public static final androidx.compose.runtime.Recomposer.State ShuttingDown;
   }
 
+  public interface RecomposerInfo {
+    method public long getChangeCount();
+    method public boolean getHasPendingWork();
+    method public kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> getState();
+    property public abstract long changeCount;
+    property public abstract boolean hasPendingWork;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> state;
+  }
+
   public final class RecomposerKt {
     method public static suspend <R> Object? withRunningRecomposer(kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.runtime.Recomposer,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
   }
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index cf7e727..2e9fcdb 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -366,20 +366,24 @@
 
   public final class Recomposer extends androidx.compose.runtime.CompositionReference {
     ctor public Recomposer(kotlin.coroutines.CoroutineContext effectCoroutineContext);
+    method public androidx.compose.runtime.RecomposerInfo asRecomposerInfo();
     method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
-    method public int getChangeCount();
+    method public long getChangeCount();
+    method public boolean getHasPendingWork();
     method public kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> getState();
-    method public boolean hasInvalidations();
+    method @Deprecated public boolean hasInvalidations();
     method public suspend Object? join(kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public suspend Object? runRecomposeAndApplyChanges(kotlin.coroutines.Continuation<?> p);
     method public void shutDown();
-    property public final int changeCount;
+    property public final long changeCount;
+    property public final boolean hasPendingWork;
     property public final kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> state;
     field public static final androidx.compose.runtime.Recomposer.Companion Companion;
   }
 
   public static final class Recomposer.Companion {
-    method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.Recomposer current();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.compose.runtime.RecomposerInfo>> getRunningRecomposers();
+    property public final kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.compose.runtime.RecomposerInfo>> runningRecomposers;
   }
 
   public enum Recomposer.State {
@@ -391,6 +395,15 @@
     enum_constant public static final androidx.compose.runtime.Recomposer.State ShuttingDown;
   }
 
+  public interface RecomposerInfo {
+    method public long getChangeCount();
+    method public boolean getHasPendingWork();
+    method public kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> getState();
+    property public abstract long changeCount;
+    property public abstract boolean hasPendingWork;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.compose.runtime.Recomposer.State> state;
+  }
+
   public final class RecomposerKt {
     method public static suspend <R> Object? withRunningRecomposer(kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.runtime.Recomposer,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
   }
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt
index 1bee4c7..be03962 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt
+++ b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/ComposeBenchmarkBase.kt
@@ -16,11 +16,11 @@
 
 package androidx.compose.runtime.benchmark
 
+import android.view.View
 import androidx.benchmark.junit4.BenchmarkRule
 import androidx.benchmark.junit4.measureRepeated
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composer
-import androidx.compose.runtime.Composition
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.Recomposer
@@ -31,8 +31,6 @@
 import androidx.compose.runtime.snapshots.takeMutableSnapshot
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.test.TestMonotonicFrameClock
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
@@ -40,9 +38,11 @@
 import kotlinx.coroutines.test.TestCoroutineScope
 import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.withContext
-import org.junit.Assert.assertTrue
 import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Rule
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
 
 @OptIn(ExperimentalComposeApi::class, InternalComposeApi::class)
 abstract class ComposeBenchmarkBase {
@@ -55,26 +55,24 @@
 
     @ExperimentalCoroutinesApi
     suspend fun DelayController.measureCompose(block: @Composable () -> Unit) = coroutineScope {
-        var composition: Composition? = null
         val activity = activityRule.activity
         val recomposer = Recomposer(coroutineContext)
+        val emptyView = View(activity)
 
         try {
             benchmarkRule.measureRepeatedSuspendable {
-                composition = activity.setContent(recomposer) {
+                activity.setContent(recomposer) {
                     block()
                 }
 
                 runWithTimingDisabled {
-                    composition?.dispose()
+                    activity.setContentView(emptyView)
                     advanceUntilIdle()
-                    Runtime.getRuntime().let {
-                        it.gc()
-                    }
+                    Runtime.getRuntime().gc()
                 }
             }
         } finally {
-            composition?.dispose()
+            activity.setContentView(emptyView)
             advanceUntilIdle()
             recomposer.shutDown()
         }
@@ -86,8 +84,9 @@
         var activeComposer: Composer<*>? = null
 
         val activity = activityRule.activity
+        val emptyView = View(activity)
 
-        val composition = activity.setContent {
+        activity.setContent {
             activeComposer = currentComposer
             receiver.composeCb()
         }
@@ -115,7 +114,7 @@
             }
         } finally {
             unregisterApplyObserver()
-            composition.dispose()
+            activity.setContentView(emptyView)
         }
     }
 
@@ -127,11 +126,12 @@
         receiver.block()
 
         val activity = activityRule.activity
+        val emptyView = View(activity)
 
         val recomposer = Recomposer(coroutineContext)
         launch { recomposer.runRecomposeAndApplyChanges() }
 
-        val composition = activity.setContent(recomposer) {
+        activity.setContent(recomposer) {
             receiver.composeCb()
         }
 
@@ -143,12 +143,12 @@
             }
             assertTrue(
                 "recomposer does not have invalidations for frame",
-                recomposer.hasInvalidations()
+                recomposer.hasPendingWork
             )
             advanceUntilIdle()
             assertFalse(
                 "recomposer has invalidations for frame",
-                recomposer.hasInvalidations()
+                recomposer.hasPendingWork
             )
             runWithTimingDisabled {
                 receiver.resetCb()
@@ -158,7 +158,7 @@
             iterations++
         }
 
-        composition.dispose()
+        activity.setContentView(emptyView)
         recomposer.shutDown()
     }
 }
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt
index 6648cbc..01df048 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt
@@ -482,8 +482,8 @@
     @After
     fun ensureNoSubcomposePending() {
         activityRule.activity.uiThread {
-            val hasInvalidations = Recomposer.current().hasInvalidations()
-            assertTrue(!hasInvalidations, "Pending changes detected after test completed")
+            val hasInvalidations = Recomposer.runningRecomposers.value.any { it.hasPendingWork }
+            assertFalse(hasInvalidations, "Pending changes detected after test completed")
         }
     }
 
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt
index 150ef55..6c84925 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/BaseComposeTest.kt
@@ -18,36 +18,25 @@
 package androidx.compose.runtime
 
 import android.app.Activity
-import android.os.Bundle
 import android.os.Looper
 import android.view.Choreographer
 import android.view.View
 import android.view.ViewGroup
-import android.widget.LinearLayout
+import androidx.activity.ComponentActivity
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.node.UiApplier
 import androidx.compose.ui.platform.AmbientContext
-import androidx.compose.ui.platform.setViewContent
+import androidx.compose.ui.platform.setContent
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlin.test.assertTrue
 
-class TestActivity : Activity() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(
-            LinearLayout(this).apply {
-                id = ROOT_ID
-            }
-        )
-    }
-}
+class TestActivity : ComponentActivity()
+
 @Suppress("DEPRECATION")
 fun makeTestActivityRule() = androidx.test.rule.ActivityTestRule(TestActivity::class.java)
 
-private val ROOT_ID = 18284847
-
-internal val Activity.root get() = findViewById(ROOT_ID) as ViewGroup
+internal val Activity.root get() = findViewById<ViewGroup>(android.R.id.content)
 
 internal fun Activity.uiThread(block: () -> Unit) {
     val latch = CountDownLatch(1)
@@ -78,15 +67,12 @@
     }
 }
 
-internal fun Activity.show(block: @Composable () -> Unit): Composition {
-    var composition: Composition? = null
+internal fun ComponentActivity.show(block: @Composable () -> Unit) {
     uiThread {
         @OptIn(ExperimentalComposeApi::class)
         Snapshot.sendApplyNotifications()
-        @Suppress("DEPRECATION")
-        composition = setViewContent(block)
+        setContent(content = block)
     }
-    return composition!!
 }
 
 internal fun Activity.waitForAFrame() {
@@ -132,8 +118,8 @@
     }
 }
 
-class ComposeTester(val activity: Activity, val composable: @Composable () -> Unit) {
-    inner class ActiveTest(val activity: Activity, val composition: Composition) {
+class ComposeTester(val activity: ComponentActivity, val composable: @Composable () -> Unit) {
+    inner class ActiveTest(val activity: Activity) {
         fun then(block: ActiveTest.(activity: Activity) -> Unit): ActiveTest {
             activity.waitForAFrame()
             activity.uiThread {
@@ -143,12 +129,15 @@
         }
 
         fun done() {
+            activity.uiThread {
+                activity.setContentView(View(activity))
+            }
             activity.waitForAFrame()
         }
     }
 
-    private fun initialComposition(composable: @Composable () -> Unit): Composition {
-        return activity.show {
+    private fun initialComposition(composable: @Composable () -> Unit) {
+        activity.show {
             Providers(
                 AmbientContext provides activity
             ) {
@@ -158,11 +147,20 @@
     }
 
     fun then(block: ComposeTester.(activity: Activity) -> Unit): ActiveTest {
-        val composition = initialComposition(composable)
+        initialComposition(composable)
         activity.waitForAFrame()
         activity.uiThread {
             block(activity)
         }
-        return ActiveTest(activity, composition)
+        return ActiveTest(activity)
+    }
+}
+
+fun View.traversal(): Sequence<View> = sequence {
+    yield(this@traversal)
+    if (this@traversal is ViewGroup) {
+        for (i in 0 until childCount) {
+            yieldAll(getChildAt(i).traversal())
+        }
     }
 }
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/ComposeIntoTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/ComposeIntoTests.kt
index 4ce2841..b3cd3ee 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/ComposeIntoTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/ComposeIntoTests.kt
@@ -17,6 +17,7 @@
 package androidx.compose.runtime
 
 import android.os.HandlerThread
+import android.view.View
 import androidx.core.os.HandlerCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -49,21 +50,18 @@
         val activity = activityRule.activity
 
         var initializationCount = 0
-        var commitCount = 0
         @OptIn(ExperimentalComposeApi::class)
         val composable = @Composable @ComposableContract(tracked = false) {
             DisposableEffect(Unit) {
                 initializationCount++
                 onDispose { }
             }
-            SideEffect { commitCount++ }
         }
 
         activity.show(composable)
         activity.waitForAFrame()
 
         assertEquals(1, initializationCount)
-        assertEquals(1, commitCount)
 
         activity.show(composable)
         activity.waitForAFrame()
@@ -71,7 +69,6 @@
         // if we call setContent multiple times, we want to ensure that it doesn't tear
         // down the whole hierarchy, so onActive should only get called once.
         assertEquals(1, initializationCount)
-        assertEquals(2, commitCount)
     }
 
     @Test // b/153355487
@@ -81,13 +78,13 @@
         var composed = 0
         var compositionLatch = CountDownLatch(1)
         val threadLatch = CountDownLatch(1)
-        val composition = activity.show {
+        activity.show {
             composed = model.value
             compositionLatch.countDown()
         }
+        val thread = HandlerThread("")
         try {
             compositionLatch.wait()
-            val thread = HandlerThread("")
             thread.start()
             HandlerCompat.createAsync(thread.looper).post {
                 model.value = 1
@@ -98,7 +95,10 @@
             compositionLatch.wait()
             assertEquals(1, composed)
         } finally {
-            activity.runOnUiThread { composition.dispose() }
+            activity.runOnUiThread {
+                activity.setContentView(View(activity))
+            }
+            thread.quitSafely()
         }
     }
 
@@ -107,12 +107,11 @@
     fun testCompositionCanBeCollectedWithPendingInvalidate() {
         val referenceQueue = ReferenceQueue<Composer<*>>()
         var scope: RecomposeScope? = null
-        var composition: Composition? = null
         var composer: Composer<*>? = null
         var phantomReference: PhantomReference<Composer<*>>? = null
         fun doShow() {
             val threadLatch = CountDownLatch(1)
-            composition = activity.show {
+            activity.show {
                 composer = currentComposer
                 scope = currentRecomposeScope
                 threadLatch.countDown()
@@ -126,13 +125,11 @@
 
         val threadLatch = CountDownLatch(1)
         activity.runOnUiThread {
-            composition?.dispose()
-            composition = null
+            activity.setContentView(View(activity))
             composer = null
             threadLatch.countDown()
         }
         threadLatch.wait()
-        assertNull(composition)
         assertNull(composer)
         assertNotNull(phantomReference)
 
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/DisposeTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/DisposeTests.kt
index c602c08..b3552d8 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/DisposeTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/DisposeTests.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.runtime
 
+import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import junit.framework.TestCase
@@ -45,8 +46,10 @@
     fun testDisposeComposition() {
         val log = mutableListOf<String>()
 
+        lateinit var recomposeScope: RecomposeScope
         @OptIn(ExperimentalComposeApi::class)
         val composable = @Composable @ComposableContract(tracked = false) {
+            recomposeScope = currentRecomposeScope
             DisposableEffect(NeverEqualObject) {
                 log.add("onCommit")
                 onDispose {
@@ -67,21 +70,19 @@
             TestCase.assertEquals(expected, log.joinToString())
         }
 
-        var composition: Composition? = null
-
         assertLog("onCommit, onActive") {
-            composition = activity.show(composable)
+            activity.show(composable)
             activity.waitForAFrame()
         }
 
         assertLog("onCommitDispose, onCommit") {
-            activity.show(composable)
+            recomposeScope.invalidate()
             activity.waitForAFrame()
         }
 
         assertLog("onActiveDispose, onCommitDispose") {
             activity.uiThread {
-                composition?.dispose()
+                activity.setContentView(View(activity))
             }
             activity.waitForAFrame()
         }
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/NewCodeGenTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/NewCodeGenTests.kt
index d823570..9dc93a6 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/NewCodeGenTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/NewCodeGenTests.kt
@@ -248,22 +248,24 @@
     fun testMoveComponents() {
         var data by mutableStateOf(listOf(1, 2, 3, 4, 5))
         compose {
-            for (item in data) {
-                key(item) {
-                    TextView(text = "$item View")
+            LinearLayout {
+                for (item in data) {
+                    key(item) {
+                        TextView(text = "$item View")
+                    }
                 }
             }
         }.then {
             data = data.toMutableList().also { it.add(it.removeAt(0)) }
         }.then { activity ->
-            val root = activity.root
-            for (index in 0 until data.size) {
-                val textView = root.getChildAt(index) as TextView
-                TestCase.assertEquals(
-                    "${data[index]} View",
-                    textView.text
-                )
-            }
+            activity.root.traversal()
+                .filterIsInstance<TextView>()
+                .forEachIndexed { index, textView ->
+                    TestCase.assertEquals(
+                        "${data[index]} View",
+                        textView.text
+                    )
+                }
         }
     }
 }
\ No newline at end of file
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
index 000cc48..b8e7ead 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
@@ -27,6 +27,11 @@
 import junit.framework.TestCase.assertFalse
 import junit.framework.TestCase.assertNotSame
 import junit.framework.TestCase.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.test.runBlockingTest
 import org.junit.After
 import org.junit.Rule
 import org.junit.Test
@@ -54,7 +59,7 @@
             val tv = activity.findViewById(456) as TextView
             assertEquals("some text", tv.text)
 
-            assertEquals(tv, activity.root.getChildAt(0))
+            assertEquals(tv, activity.root.traversal().first { it is TextView })
         }
     }
 
@@ -297,7 +302,7 @@
             }
             LinearLayout { }
         }.then { activity ->
-            assertChildHierarchy(activity.root) {
+            assertChildHierarchy(activity.root.viewBlockHolders()) {
                 """
                     <LinearLayout>
                         <LinearLayout />
@@ -327,7 +332,7 @@
             }
         }.then { activity ->
 
-            assertChildHierarchy(activity.root) {
+            assertChildHierarchy(activity.root.firstViewBlockHolder()) {
                 """
                 <LinearLayout>
                     <LinearLayout>
@@ -361,7 +366,7 @@
             }
         }.then {
 
-            assertChildHierarchy(activity.root) {
+            assertChildHierarchy(activity.root.firstViewBlockHolder()) {
                 """
                 <LinearLayout>
                     <LinearLayout>
@@ -472,6 +477,23 @@
             assertNotSame(snapshotId, Snapshot.current.id)
         }
     }
+
+    @Test
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun runningRecomposerFlow() = runBlockingTest {
+        lateinit var recomposer: RecomposerInfo
+        val recomposerJob = launch {
+            withRunningRecomposer {
+                recomposer = it.asRecomposerInfo()
+                suspendCancellableCoroutine<Unit> { }
+            }
+        }
+        val afterLaunch = Recomposer.runningRecomposers.value
+        assertTrue("recomposer in running list", recomposer in afterLaunch)
+        recomposerJob.cancelAndJoin()
+        val afterCancel = Recomposer.runningRecomposers.value
+        assertFalse("recomposer no longer in running list", recomposer in afterCancel)
+    }
 }
 
 @Composable
@@ -479,6 +501,16 @@
     content()
 }
 
+private fun View.firstViewBlockHolder(): ViewGroup = traversal()
+    .filterIsInstance<ViewGroup>()
+    // NOTE: Implementation dependence on Compose UI implementation detail
+    .first { it.javaClass.simpleName == "ViewBlockHolder" }
+
+private fun View.viewBlockHolders(): Sequence<ViewGroup> = traversal()
+    .filterIsInstance<ViewGroup>()
+    // NOTE: Implementation dependence on Compose UI implementation detail
+    .filter { it.javaClass.simpleName == "ViewBlockHolder" }
+
 fun assertChildHierarchy(root: ViewGroup, getHierarchy: () -> String) {
     val realHierarchy = printChildHierarchy(root)
 
@@ -488,6 +520,15 @@
     )
 }
 
+fun assertChildHierarchy(roots: Sequence<ViewGroup>, getHierarchy: () -> String) {
+    val realHierarchy = roots.map { printChildHierarchy(it).trim() }.joinToString("\n")
+
+    assertEquals(
+        normalizeString(getHierarchy()),
+        realHierarchy.trim()
+    )
+}
+
 fun normalizeString(str: String): String {
     val lines = str.split('\n').dropWhile { it.isBlank() }.dropLastWhile {
         it.isBlank()
diff --git a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt
index f2e2346..625a5b4 100644
--- a/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt
+++ b/compose/runtime/runtime/integration-tests/src/androidAndroidTest/kotlin/androidx/compose/runtime/SuspendingEffectsTests.kt
@@ -129,16 +129,12 @@
     @Test
     fun testRememberCoroutineScopeActiveWithComposition() {
         lateinit var coroutineScope: CoroutineScope
-        val tester = compose {
+        compose {
             coroutineScope = rememberCoroutineScope()
         }.then {
             assertTrue(coroutineScope.isActive, "coroutine scope was active before dispose")
-        }
-        val composition = tester.composition
-        tester.then {
-            composition.dispose()
-            assertFalse(coroutineScope.isActive, "coroutine scope was inactive after dispose")
-        }
+        }.done()
+        assertFalse(coroutineScope.isActive, "coroutine scope was inactive after dispose")
     }
 
     @Test
diff --git a/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt
index a8d1ea9..1471aab 100644
--- a/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt
+++ b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt
@@ -20,8 +20,8 @@
 import androidx.compose.runtime.AbstractApplier
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
+import androidx.compose.runtime.CompositionReference
 import androidx.compose.runtime.ExperimentalComposeApi
-import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.compositionFor
 import androidx.compose.runtime.ComposeNode
 import androidx.compose.runtime.getValue
@@ -63,8 +63,11 @@
     }
 
     // A function like the following could be created to create a composition provided a root Node.
-    fun Node.setContent(content: @Composable () -> Unit): Composition {
-        return compositionFor(this, NodeApplier(this), Recomposer.current()).also {
+    fun Node.setContent(
+        parent: CompositionReference,
+        content: @Composable () -> Unit
+    ): Composition {
+        return compositionFor(this, NodeApplier(this), parent).apply {
             setContent(content)
         }
     }
@@ -89,8 +92,8 @@
     }
 
     // and then a sample tree could be composed:
-    fun runApp(root: GroupNode) {
-        root.setContent {
+    fun runApp(root: GroupNode, parent: CompositionReference) {
+        root.setContent(parent) {
             var count by remember { mutableStateOf(0) }
             Group {
                 Text("Count: $count")
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
index 562c53c..ebeae67 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
@@ -28,5 +28,5 @@
      * IMPORTANT: Whenever updating this value, please make sure to also update `versionTable` and
      * `minimumRuntimeVersionInt` in `VersionChecker.kt` of the compiler.
      */
-    const val version: Int = 2200
+    const val version: Int = 2300
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index 88ddd8d..0eaa105 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -26,16 +26,15 @@
 import androidx.compose.runtime.snapshots.SnapshotWriteObserver
 import androidx.compose.runtime.snapshots.fastForEach
 import androidx.compose.runtime.snapshots.takeMutableSnapshot
+import kotlinx.collections.immutable.persistentSetOf
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.NonCancellable
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.takeWhile
 import kotlinx.coroutines.job
@@ -64,6 +63,30 @@
 }
 
 /**
+ * Read-only information about a [Recomposer]. Used when code should only monitor the activity of
+ * a [Recomposer], and not attempt to alter its state or create new compositions from it.
+ */
+interface RecomposerInfo {
+    /**
+     * The current [State] of the [Recomposer]. See each [State] value for its meaning.
+     */
+    val state: Flow<Recomposer.State>
+
+    /**
+     * `true` if the [Recomposer] has been assigned work to do and it is currently performing
+     * that work or awaiting an opportunity to do so.
+     */
+    val hasPendingWork: Boolean
+
+    /**
+     * The running count of the number of times the [Recomposer] awoke and applied changes to
+     * one or more [Composer]s. This count is unaffected if the composer awakes and recomposed but
+     * composition did not produce changes to apply.
+     */
+    val changeCount: Long
+}
+
+/**
  * The scheduler for performing recomposition and applying updates to one or more [Composition]s.
  */
 // RedundantVisibilityModifier suppressed because metalava picks up internal function overrides
@@ -81,7 +104,7 @@
      * one or more composers. This count is unaffected if the composer awakes and recomposed but
      * composition did not produce changes to apply.
      */
-    var changeCount = 0
+    var changeCount = 0L
         private set
 
     private val broadcastFrameClock = BroadcastFrameClock {
@@ -233,6 +256,24 @@
     public val state: Flow<State>
         get() = _state
 
+    // A separate private object to avoid the temptation of casting a RecomposerInfo
+    // to a Recomposer if Recomposer itself were to implement RecomposerInfo.
+    private inner class RecomposerInfoImpl : RecomposerInfo {
+        override val state: Flow<State>
+            get() = this@Recomposer.state
+        override val hasPendingWork: Boolean
+            get() = this@Recomposer.hasPendingWork
+        override val changeCount: Long
+            get() = this@Recomposer.changeCount
+    }
+
+    private val recomposerInfo = RecomposerInfoImpl()
+
+    /**
+     * Obtain a read-only [RecomposerInfo] for this [Recomposer].
+     */
+    fun asRecomposerInfo(): RecomposerInfo = recomposerInfo
+
     private fun recordComposerModificationsLocked() {
         if (snapshotInvalidations.isNotEmpty()) {
             snapshotInvalidations.fastForEach { changes ->
@@ -286,6 +327,8 @@
                 }?.resume(Unit)
             }
 
+            addRunning(recomposerInfo)
+
             try {
                 // Invalidate all registered composers when we start since we weren't observing
                 // snapshot changes on their behalf. Assume anything could have changed.
@@ -371,6 +414,7 @@
                     }
                     deriveStateLocked()
                 }
+                removeRunning(recomposerInfo)
             }
         }
     }
@@ -465,9 +509,17 @@
     /**
      * Returns true if any pending invalidations have been scheduled.
      */
-    fun hasInvalidations(): Boolean = synchronized(stateLock) {
-        snapshotInvalidations.isNotEmpty() || hasFrameWorkLocked
-    }
+    @Deprecated("Replaced by hasPendingWork", ReplaceWith("hasPendingWork"))
+    fun hasInvalidations(): Boolean = hasPendingWork
+
+    /**
+     * `true` if this [Recomposer] has any pending work scheduled, regardless of whether or not
+     * it is currently [running][runRecomposeAndApplyChanges].
+     */
+    val hasPendingWork: Boolean
+        get() = synchronized(stateLock) {
+            snapshotInvalidations.isNotEmpty() || hasFrameWorkLocked
+        }
 
     private val hasFrameWorkLocked: Boolean
         get() = composerInvalidations.isNotEmpty() || broadcastFrameClock.hasAwaiters
@@ -521,28 +573,31 @@
     }
 
     companion object {
-        @OptIn(ExperimentalCoroutinesApi::class)
-        private val mainRecomposer: Recomposer by lazy {
-            val embeddingContext = EmbeddingContext()
-            val mainScope = CoroutineScope(
-                NonCancellable + embeddingContext.mainThreadCompositionContext()
-            )
 
-            Recomposer(mainScope.coroutineContext).also {
-                // NOTE: Launching undispatched so that compositions created with the
-                // Recomposer.current() singleton instance can assume the recomposer is running
-                // when they perform initial composition. The relevant Recomposer code is
-                // appropriately thread-safe for this.
-                mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
-                    it.runRecomposeAndApplyChanges()
-                }
+        private val _runningRecomposers = MutableStateFlow(persistentSetOf<RecomposerInfo>())
+
+        /**
+         * An observable [Set] of [RecomposerInfo]s for currently
+         * [running][runRecomposeAndApplyChanges] [Recomposer]s.
+         * Emitted sets are immutable.
+         */
+        val runningRecomposers: StateFlow<Set<RecomposerInfo>>
+            get() = _runningRecomposers
+
+        private fun addRunning(info: RecomposerInfo) {
+            while (true) {
+                val old = _runningRecomposers.value
+                val new = old.add(info)
+                if (old === new || _runningRecomposers.compareAndSet(old, new)) break
             }
         }
 
-        /**
-         * Retrieves [Recomposer] for the current thread. Needs to be the main thread.
-         */
-        @TestOnly
-        fun current(): Recomposer = mainRecomposer
+        private fun removeRunning(info: RecomposerInfo) {
+            while (true) {
+                val old = _runningRecomposers.value
+                val new = old.remove(info)
+                if (old === new || _runningRecomposers.compareAndSet(old, new)) break
+            }
+        }
     }
 }
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
index deaedac..1b983cb 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
@@ -57,9 +57,9 @@
             override fun advance(): Boolean {
                 val changeCount = recomposer.changeCount
                 Snapshot.sendApplyNotifications()
-                if (recomposer.hasInvalidations()) {
+                if (recomposer.hasPendingWork) {
                     advanceTimeBy(5_000)
-                    check(!recomposer.hasInvalidations()) {
+                    check(!recomposer.hasPendingWork) {
                         "Potentially infinite recomposition, still recomposing after advancing"
                     }
                 }
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
index 87326b1..61e30d5 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
@@ -29,7 +29,6 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import androidx.activity.ComponentActivity
-import androidx.compose.runtime.Composition
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.dispatch.MonotonicFrameClock
@@ -69,8 +68,6 @@
     internal var view: View? = null
         private set
 
-    private var composition: Composition? = null
-
     override var didLastRecomposeHaveChanges = false
         private set
 
@@ -133,7 +130,7 @@
             "Need to call onPreEmitContent before emitContent!"
         }
 
-        composition = activity.setContent(recomposer) { testCase!!.Content() }
+        activity.setContent(recomposer) { testCase!!.Content() }
         view = findViewRootForTest(activity)!!.view
         @OptIn(ExperimentalComposeApi::class)
         Snapshot.notifyObjectsInitialized()
@@ -142,12 +139,12 @@
 
     // TODO: This method may advance the global snapshot and should be just a getter
     override fun hasPendingChanges(): Boolean {
-        if (recomposer.hasInvalidations() || hasPendingChangesInFrame()) {
+        if (recomposer.hasPendingWork || hasPendingChangesInFrame()) {
             @OptIn(ExperimentalComposeApi::class)
             Snapshot.sendApplyNotifications()
         }
 
-        return recomposer.hasInvalidations()
+        return recomposer.hasPendingWork
     }
 
     /**
@@ -249,14 +246,14 @@
             return
         }
 
-        composition?.dispose()
+        // Clear the view; this will also dispose the underlying composition
+        // by the default disposal policy. This happens **before** advanceUntilIdle.
+        val rootView = activity.findViewById(android.R.id.content) as ViewGroup
+        rootView.removeAllViews()
 
         // Dispatcher will clean up the cancelled coroutines when it advances to them
         testCoroutineDispatcher.advanceUntilIdle()
 
-        // Clear the view
-        val rootView = activity.findViewById(android.R.id.content) as ViewGroup
-        rootView.removeAllViews()
         // Important so we can set the content again.
         view = null
         testCase = null
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt
index 2a560e0..c942042 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import org.junit.Assert
 import kotlin.math.roundToInt
 
@@ -180,7 +181,7 @@
     Assert.assertTrue(centerX - sizeX / 2 >= 0.0f)
     Assert.assertTrue(centerY + sizeY / 2 <= height)
     Assert.assertTrue(centerY - sizeY / 2 >= 0.0f)
-    val outline = shape.createOutline(Size(shapeSizeX, shapeSizeY), density)
+    val outline = shape.createOutline(Size(shapeSizeX, shapeSizeY), LayoutDirection.Ltr, density)
     val path = Path()
     path.addOutline(outline)
     val shapeOffset = Offset(
@@ -188,7 +189,9 @@
         (centerY - shapeSizeY / 2f)
     )
     val backgroundPath = Path()
-    backgroundPath.addOutline(backgroundShape.createOutline(Size(sizeX, sizeY), density))
+    backgroundPath.addOutline(
+        backgroundShape.createOutline(Size(sizeX, sizeY), LayoutDirection.Ltr, density)
+    )
     for (x in centerX - sizeX / 2 until centerX + sizeX / 2) {
         for (y in centerY - sizeY / 2 until centerY + sizeY / 2) {
             val point = Offset(x.toFloat(), y.toFloat())
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index dabbd79..f00e391 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -666,7 +666,7 @@
   }
 
   @androidx.compose.runtime.Immutable public interface Shape {
-    method public androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    method public androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
   @androidx.compose.runtime.Immutable public final class SolidColor extends androidx.compose.ui.graphics.Brush {
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index dabbd79..f00e391 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -666,7 +666,7 @@
   }
 
   @androidx.compose.runtime.Immutable public interface Shape {
-    method public androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    method public androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
   @androidx.compose.runtime.Immutable public final class SolidColor extends androidx.compose.ui.graphics.Brush {
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 60b3ab4..6e0b80c 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -698,7 +698,7 @@
   }
 
   @androidx.compose.runtime.Immutable public interface Shape {
-    method public androidx.compose.ui.graphics.Outline createOutline-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    method public androidx.compose.ui.graphics.Outline createOutline-9w1PWio(long size, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.unit.Density density);
   }
 
   @androidx.compose.runtime.Immutable public final class SolidColor extends androidx.compose.ui.graphics.Brush {
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/RectangleShapeTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/RectangleShapeTest.kt
index c844d78..bc7919f 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/RectangleShapeTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/RectangleShapeTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.toRect
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.test.filters.SmallTest
 import org.junit.Assert.assertEquals
 import org.junit.Test
@@ -40,5 +41,5 @@
         assertEquals(outline.rect, size.toRect())
     }
 
-    private fun Shape.toOutline() = createOutline(size, density)
+    private fun Shape.toOutline() = createOutline(size, LayoutDirection.Ltr, density)
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/RectangleShape.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/RectangleShape.kt
index e89a9ec..54ed9ed 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/RectangleShape.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/RectangleShape.kt
@@ -20,12 +20,13 @@
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.toRect
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * A shape describing the rectangle.
  */
 @Stable
 val RectangleShape: Shape = object : Shape {
-    override fun createOutline(size: Size, density: Density) =
+    override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density) =
         Outline.Rectangle(size.toRect())
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
index f4ad2d1..e174944 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shape.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * Defines a generic shape.
@@ -29,9 +30,10 @@
      * Creates [Outline] of this shape for the given [size].
      *
      * @param size the size of the shape boundary.
+     * @param layoutDirection the current layout direction.
      * @param density the current density of the screen.
      *
      * @return [Outline] of this shape for the given [size].
      */
-    fun createOutline(size: Size, density: Density): Outline
+    fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline
 }
diff --git a/compose/ui/ui-test-junit4/api/current.txt b/compose/ui/ui-test-junit4/api/current.txt
index b40623c..34187d7 100644
--- a/compose/ui/ui-test-junit4/api/current.txt
+++ b/compose/ui/ui-test-junit4/api/current.txt
@@ -108,6 +108,9 @@
     method @Deprecated @androidx.compose.ui.test.ExperimentalTestApi public static void unregisterTestClock(androidx.compose.ui.test.TestAnimationClock clock);
   }
 
+  public final class ComposeIdlingResourceNewKt {
+  }
+
   public final class ComposeNotIdleException extends java.lang.Throwable {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
diff --git a/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt b/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt
index b40623c..34187d7 100644
--- a/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt
@@ -108,6 +108,9 @@
     method @Deprecated @androidx.compose.ui.test.ExperimentalTestApi public static void unregisterTestClock(androidx.compose.ui.test.TestAnimationClock clock);
   }
 
+  public final class ComposeIdlingResourceNewKt {
+  }
+
   public final class ComposeNotIdleException extends java.lang.Throwable {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.txt b/compose/ui/ui-test-junit4/api/restricted_current.txt
index b40623c..34187d7 100644
--- a/compose/ui/ui-test-junit4/api/restricted_current.txt
+++ b/compose/ui/ui-test-junit4/api/restricted_current.txt
@@ -108,6 +108,9 @@
     method @Deprecated @androidx.compose.ui.test.ExperimentalTestApi public static void unregisterTestClock(androidx.compose.ui.test.TestAnimationClock clock);
   }
 
+  public final class ComposeIdlingResourceNewKt {
+  }
+
   public final class ComposeNotIdleException extends java.lang.Throwable {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
index 771a966..b54bb4e 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
@@ -14,18 +14,15 @@
  * limitations under the License.
  */
 
-@file:Suppress("DEPRECATION")
-
 package androidx.compose.ui.test.junit4
 
 import android.os.Looper
 import androidx.activity.ComponentActivity
-import androidx.compose.animation.core.FloatPropKey
 import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.animateFloat
 import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.transitionDefinition
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.transition
+import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
@@ -34,6 +31,7 @@
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.State
 import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.snapshots.Snapshot
@@ -188,40 +186,35 @@
     @Composable
     private fun Ui(animationState: State<AnimationStates>) {
         Box(modifier = Modifier.background(color = Color.Yellow).fillMaxSize()) {
-            val state = transition(
-                definition = animationDefinition,
-                toState = animationState.value,
-                onStateChangeFinished = { animationRunning = false }
-            )
+            val transition = updateTransition(animationState.value)
+            animationRunning = transition.currentState != transition.targetState
+            val x by transition.animateFloat(
+                transitionSpec = {
+                    if (AnimationStates.From isTransitioningTo AnimationStates.To) {
+                        tween(
+                            easing = LinearEasing,
+                            durationMillis = nonIdleDuration.toInt()
+                        )
+                    } else {
+                        snap()
+                    }
+                }
+            ) {
+                if (it == AnimationStates.From) {
+                    animateFromX
+                } else {
+                    animateToX
+                }
+            }
             Canvas(modifier = Modifier.fillMaxSize()) {
-                recordedAnimatedValues.add(state[x])
-                drawRect(Color.Cyan, Offset(state[x], 0f), rectSize)
+                recordedAnimatedValues.add(x)
+                drawRect(Color.Cyan, Offset(x, 0f), rectSize)
             }
         }
     }
-
-    private val x = FloatPropKey()
-
-    private enum class AnimationStates {
-        From,
-        To
-    }
-
-    private val animationDefinition = transitionDefinition<AnimationStates> {
-        state(AnimationStates.From) {
-            this[x] = animateFromX
-        }
-        state(AnimationStates.To) {
-            this[x] = animateToX
-        }
-        transition(AnimationStates.From to AnimationStates.To) {
-            x using tween(
-                easing = LinearEasing,
-                durationMillis = nonIdleDuration.toInt()
-            )
-        }
-        transition(AnimationStates.To to AnimationStates.From) {
-            x using snap()
-        }
-    }
 }
+
+private enum class AnimationStates {
+    From,
+    To
+}
\ No newline at end of file
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
index a862388..6d5220a 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
@@ -52,14 +52,14 @@
 
     @Before
     fun setUp() {
-        assertThat(composeRootRegistry.getUnfilteredComposeRoots()).isEmpty()
+        assertThat(composeRootRegistry.getCreatedComposeRoots()).isEmpty()
         composeRootRegistry.addOnRegistrationChangedListener(onRegistrationChangedListener)
     }
 
     @Test
     fun registryIsSetUpAndEmpty() {
         assertThat(composeRootRegistry.isSetUp).isTrue()
-        assertThat(composeRootRegistry.getUnfilteredComposeRoots()).isEmpty()
+        assertThat(composeRootRegistry.getCreatedComposeRoots()).isEmpty()
     }
 
     @Test
@@ -70,9 +70,10 @@
             val composeRoot = activity.findRootForTest()
 
             // Then it is registered
-            assertThat(composeRootRegistry.getUnfilteredComposeRoots())
+            assertThat(composeRootRegistry.getCreatedComposeRoots())
                 .isEqualTo(setOf(composeRoot))
-            assertThat(composeRootRegistry.getComposeRoots()).isEqualTo(setOf(composeRoot))
+            assertThat(composeRootRegistry.getRegisteredComposeRoots())
+                .isEqualTo(setOf(composeRoot))
             // And our listener was notified
             assertThat(onRegistrationChangedListener.recordedChanges).isEqualTo(
                 listOf(Pair(composeRoot, true))
@@ -91,8 +92,7 @@
             activity.setContentView(View(activity))
 
             // Then it is not registered now
-            assertThat(composeRootRegistry.getUnfilteredComposeRoots()).isEmpty()
-            assertThat(composeRootRegistry.getComposeRoots()).isEmpty()
+            assertThat(composeRootRegistry.getRegisteredComposeRoots()).isEmpty()
             // But our listener was notified of addition and removal
             assertThat(onRegistrationChangedListener.recordedChanges).isEqualTo(
                 listOf(
@@ -114,8 +114,8 @@
             composeRootRegistry.tearDownRegistry()
 
             // Then the registry is empty
-            assertThat(composeRootRegistry.getUnfilteredComposeRoots()).isEmpty()
-            assertThat(composeRootRegistry.getComposeRoots()).isEmpty()
+            assertThat(composeRootRegistry.getCreatedComposeRoots()).isEmpty()
+            assertThat(composeRootRegistry.getRegisteredComposeRoots()).isEmpty()
             // And our listener was notified of addition and removal
             assertThat(onRegistrationChangedListener.recordedChanges).isEqualTo(
                 listOf(
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MonotonicFrameClockTestRuleTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MonotonicFrameClockTestRuleTest.kt
index fd8c621..295153f 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MonotonicFrameClockTestRuleTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MonotonicFrameClockTestRuleTest.kt
@@ -14,22 +14,20 @@
  * limitations under the License.
  */
 
-@file:Suppress("DEPRECATION")
-
 package androidx.compose.ui.test.junit4
 
-import androidx.compose.animation.core.FloatPropKey
 import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.animateFloat
 import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.transitionDefinition
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.transition
+import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -89,40 +87,35 @@
         hasRecomposed = true
         Box(modifier = Modifier.background(color = Color.Yellow).fillMaxSize()) {
             hasRecomposed = true
-            val state = transition(
-                definition = animationDefinition,
-                toState = animationState.value,
-                onStateChangeFinished = { animationRunning = false }
-            )
+            val transition = updateTransition(animationState.value)
+            animationRunning = transition.currentState != transition.targetState
+            val x by transition.animateFloat(
+                transitionSpec = {
+                    if (AnimationStates.From isTransitioningTo AnimationStates.To) {
+                        tween(
+                            easing = LinearEasing,
+                            durationMillis = duration.toInt()
+                        )
+                    } else {
+                        snap()
+                    }
+                }
+            ) {
+                if (it == AnimationStates.From) {
+                    startValue
+                } else {
+                    endValue
+                }
+            }
             hasRecomposed = true
             Canvas(modifier = Modifier.fillMaxSize()) {
-                drawRect(Color.Cyan, Offset(state[x], 0.0f), size)
+                drawRect(Color.Cyan, Offset(x, 0.0f), size)
             }
         }
     }
 
-    private val x = FloatPropKey()
-
     private enum class AnimationStates {
         From,
         To
     }
-
-    private val animationDefinition = transitionDefinition<AnimationStates> {
-        state(AnimationStates.From) {
-            this[x] = startValue
-        }
-        state(AnimationStates.To) {
-            this[x] = endValue
-        }
-        transition(AnimationStates.From to AnimationStates.To) {
-            x using tween(
-                easing = LinearEasing,
-                durationMillis = duration.toInt()
-            )
-        }
-        transition(AnimationStates.To to AnimationStates.From) {
-            x using snap()
-        }
-    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
index b52e442e..01061d3 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.test.click
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performGesture
+import androidx.test.filters.FlakyTest
 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
 import androidx.test.runner.lifecycle.Stage
 import com.google.common.truth.Truth.assertThat
@@ -42,6 +43,7 @@
 
     @Ignore("b/178044284")
     @Test
+    @FlakyTest(bugId = 178003554)
     fun test() {
         lateinit var activity1: Activity1
         rule.activityRule.scenario.onActivity { activity1 = it }
@@ -49,8 +51,10 @@
         rule.onNodeWithTag("activity2").performGesture { click() }
         val activity2 = getCurrentActivity() as Activity2
 
-        assertThat(activity1.clickCounter).isEqualTo(0)
-        assertThat(activity2.clickCounter).isEqualTo(1)
+        rule.runOnIdle {
+            assertThat(activity1.clickCounter).isEqualTo(0)
+            assertThat(activity2.clickCounter).isEqualTo(1)
+        }
     }
 
     // In general this method to retrieve the current activity may fail, because the presence of
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/WaitingForPopupTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/WaitingForPopupTest.kt
new file mode 100644
index 0000000..b387837
--- /dev/null
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/WaitingForPopupTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.test.junit4
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.preferredSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.isPopup
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class WaitingForPopupTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Composable
+    private fun ShowPopup() {
+        Popup {
+            Box(Modifier.preferredSize(10.dp, 10.dp))
+        }
+    }
+
+    @Test
+    fun popupInFirstComposition() {
+        rule.setContent {
+            ShowPopup()
+        }
+        rule.onNode(isPopup()).assertExists()
+    }
+
+    @Test
+    fun popupInLaterComposition() {
+        val showPopup = mutableStateOf(false)
+        rule.setContent {
+            if (showPopup.value) {
+                ShowPopup()
+            }
+        }
+        rule.onNode(isPopup()).assertDoesNotExist()
+        showPopup.value = true
+        rule.onNode(isPopup()).assertExists()
+    }
+
+    @Test
+    fun popupTogglingRepeatedly() {
+        val showPopup = mutableStateOf(false)
+        rule.setContent {
+            if (showPopup.value) {
+                ShowPopup()
+            }
+        }
+        rule.onNode(isPopup()).assertDoesNotExist()
+
+        // (no particular reason for 4x, could've been 10x just as well)
+        repeat(4) {
+            showPopup.value = !showPopup.value
+            if (showPopup.value) {
+                rule.onNode(isPopup()).assertExists()
+            } else {
+                rule.onNode(isPopup()).assertDoesNotExist()
+            }
+        }
+    }
+}
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.kt
index bad3ac4..f3d5cb0 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.text.blinkingCursorEnabled
@@ -26,7 +27,6 @@
 import androidx.compose.ui.InternalComposeUiApi
 import androidx.compose.ui.node.RootForTest
 import androidx.compose.ui.platform.ViewRootForTest
-import androidx.compose.ui.platform.WindowRecomposerFactory
 import androidx.compose.ui.platform.WindowRecomposerPolicy
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.semantics.SemanticsNode
@@ -377,12 +377,11 @@
         val currentActivity = activity
 
         runOnUiThread {
-            val composition = currentActivity.setContent(
-                recomposer ?: Recomposer.current(),
-                composable
-            )
+            currentActivity.setContent(recomposer, composable)
             disposeContentHook = {
-                composition.dispose()
+                // Removing a default ComposeView from the view hierarchy will
+                // dispose its composition.
+                activity.setContentView(View(activity))
             }
         }
 
@@ -473,8 +472,19 @@
     inner class AndroidComposeStatement(
         private val base: Statement
     ) : Statement() {
-        @OptIn(InternalTextApi::class)
+
+        @OptIn(InternalComposeUiApi::class)
         override fun evaluate() {
+            val recomposer = recomposer
+            if (recomposer == null) {
+                evaluateInner()
+            } else WindowRecomposerPolicy.withFactory({ recomposer }) {
+                evaluateInner()
+            }
+        }
+
+        @OptIn(InternalTextApi::class)
+        private fun evaluateInner() {
             @Suppress("DEPRECATION_ERROR")
             val oldTextInputFactory = textInputServiceFactory
             try {
@@ -484,12 +494,6 @@
                 textInputServiceFactory = {
                     TextInputServiceForTests(it)
                 }
-                if (recomposer != null) {
-                    @OptIn(InternalComposeUiApi::class)
-                    WindowRecomposerPolicy.setWindowRecomposerFactory {
-                        recomposer
-                    }
-                }
                 base.evaluate()
             } finally {
                 if (driveClockByMonotonicFrameClock) {
@@ -501,13 +505,6 @@
                     @OptIn(ExperimentalCoroutinesApi::class)
                     testCoroutineDispatcher?.cleanupTestCoroutines()
                 }
-                if (recomposer != null) {
-                    @Suppress("DEPRECATION")
-                    @OptIn(InternalComposeUiApi::class)
-                    WindowRecomposerPolicy.setWindowRecomposerFactory(
-                        WindowRecomposerFactory.Global
-                    )
-                }
                 @Suppress("DEPRECATION_ERROR")
                 blinkingCursorEnabled = true
                 @Suppress("DEPRECATION_ERROR")
@@ -586,7 +583,7 @@
             //  structure. In case of multiple AndroidOwners, add a fake root
             waitForIdle()
 
-            return composeRootRegistry.getComposeRoots().also {
+            return composeRootRegistry.getRegisteredComposeRoots().also {
                 // TODO(b/153632210): This check should be done by callers of getOwners()
                 check(it.isNotEmpty()) {
                     "No compose views found in the app. Is your Activity resumed?"
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResource.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResource.kt
index 0948cab..76627f8 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResource.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResource.kt
@@ -106,6 +106,7 @@
     private var hadAnimationClocksIdle = true
     private var hadNoSnapshotChanges = true
     private var hadNoRecomposerChanges = true
+    private var hadNoPendingSetContent = true
     private var hadNoPendingMeasureLayout = true
     // TODO(b/174244530): Include hadNoPendingDraw when it is reliable
 //    private var hadNoPendingDraw = true
@@ -117,9 +118,15 @@
         @OptIn(ExperimentalComposeApi::class)
         get() {
             hadNoSnapshotChanges = !Snapshot.current.hasPendingChanges()
-            hadNoRecomposerChanges = !Recomposer.current().hasInvalidations()
+            hadNoRecomposerChanges = Recomposer.runningRecomposers.value.none { it.hasPendingWork }
             hadAnimationClocksIdle = areAllClocksIdle()
-            val composeRoots = composeRootRegistry.getUnfilteredComposeRoots()
+
+            // pending set content needs all created compose roots,
+            // because by definition they will not be in resumed state
+            hadNoPendingSetContent =
+                !composeRootRegistry.getCreatedComposeRoots().any { it.isBusyAttaching }
+
+            val composeRoots = composeRootRegistry.getRegisteredComposeRoots()
             hadNoPendingMeasureLayout = !composeRoots.any { it.hasPendingMeasureOrLayout }
             // TODO(b/174244530): Include hadNoPendingDraw when it is reliable
 //            hadNoPendingDraw = !composeRoots.any {
@@ -130,6 +137,7 @@
             return hadNoSnapshotChanges &&
                 hadNoRecomposerChanges &&
                 hadAnimationClocksIdle &&
+                hadNoPendingSetContent &&
                 // TODO(b/174244530): Include hadNoPendingDraw when it is reliable
                 hadNoPendingMeasureLayout /*&&
                 hadNoPendingDraw*/
@@ -160,13 +168,14 @@
         val hadSnapshotChanges = !hadNoSnapshotChanges
         val hadRecomposerChanges = !hadNoRecomposerChanges
         val hadRunningAnimations = !hadAnimationClocksIdle
+        val hadPendingSetContent = !hadNoPendingSetContent
         val hadPendingMeasureLayout = !hadNoPendingMeasureLayout
         // TODO(b/174244530): Include hadNoPendingDraw when it is reliable
 //        val hadPendingDraw = !hadNoPendingDraw
 
         val wasIdle = !hadSnapshotChanges && !hadRecomposerChanges && !hadRunningAnimations &&
             // TODO(b/174244530): Include hadNoPendingDraw when it is reliable
-            !hadPendingMeasureLayout /*&& !hadPendingDraw*/
+            !hadPendingSetContent && !hadPendingMeasureLayout /*&& !hadPendingDraw*/
 
         if (wasIdle) {
             return null
@@ -180,6 +189,9 @@
         if (busyRecomposing) {
             busyReasons.add("pending recompositions")
         }
+        if (hadPendingSetContent) {
+            busyReasons.add("pending setContent")
+        }
         if (hadPendingMeasureLayout) {
             busyReasons.add("pending measure/layout")
         }
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResourceNew.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResourceNew.kt
index de4b15c..68dcd01 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResourceNew.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeIdlingResourceNew.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.test.IdlingResource
 import androidx.compose.ui.test.junit4.MainTestClockImpl
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
@@ -36,10 +37,11 @@
     private val mainRecomposer: Recomposer
 ) : IdlingResource {
 
-    private var hadAwaitersOnMainClock = true
-    private var hadSnapshotChanges = true
-    private var hadRecomposerChanges = true
-    private var hadPendingMeasureLayout = true
+    private var hadAwaitersOnMainClock = false
+    private var hadSnapshotChanges = false
+    private var hadRecomposerChanges = false
+    private var hadPendingSetContent = false
+    private var hadPendingMeasureLayout = false
 
     override val isIdleNow: Boolean
         @OptIn(ExperimentalComposeApi::class)
@@ -47,7 +49,7 @@
             fun shouldPumpTime(): Boolean {
                 hadAwaitersOnMainClock = clock.hasAwaiters
                 hadSnapshotChanges = Snapshot.current.hasPendingChanges()
-                hadRecomposerChanges = mainRecomposer.hasInvalidations()
+                hadRecomposerChanges = mainRecomposer.hasPendingWork
 
                 val needsRecompose = hadAwaitersOnMainClock || hadSnapshotChanges ||
                     hadRecomposerChanges
@@ -60,25 +62,35 @@
                 ++i
             }
 
-            val composeRoots = composeRootRegistry.getComposeRoots()
+            // pending set content needs all created compose roots,
+            // because by definition they will not be in resumed state
+            hadPendingSetContent =
+                composeRootRegistry.getCreatedComposeRoots().any { it.isBusyAttaching }
+
+            val composeRoots = composeRootRegistry.getRegisteredComposeRoots()
             hadPendingMeasureLayout = composeRoots.any { it.hasPendingMeasureOrLayout }
 
-            return !shouldPumpTime() && !hadPendingMeasureLayout
+            return !shouldPumpTime() &&
+                !hadPendingSetContent &&
+                !hadPendingMeasureLayout
         }
 
     override fun getDiagnosticMessageIfBusy(): String? {
         val wasBusy = hadSnapshotChanges || hadRecomposerChanges || hadAwaitersOnMainClock ||
-            hadPendingMeasureLayout
+            hadPendingSetContent || hadPendingMeasureLayout
 
         if (wasBusy) {
             return null
         }
 
         val busyReasons = mutableListOf<String>()
-        val busyRecomposing = hadSnapshotChanges || hadRecomposerChanges
+        val busyRecomposing = hadSnapshotChanges || hadRecomposerChanges || hadAwaitersOnMainClock
         if (busyRecomposing) {
             busyReasons.add("pending recompositions")
         }
+        if (hadPendingSetContent) {
+            busyReasons.add("pending setContent")
+        }
         if (hadPendingMeasureLayout) {
             busyReasons.add("pending measure/layout")
         }
@@ -94,3 +106,11 @@
         return message
     }
 }
+
+internal val ViewRootForTest.isBusyAttaching: Boolean
+    get() {
+        // If the rootView has a parent, it is the ViewRootImpl, which is set in
+        // windowManager.addView(). If the rootView doesn't have a parent, the view hasn't been
+        // attached to a window yet, or is removed again.
+        return view.rootView.parent != null && !view.isAttachedToWindow
+    }
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeRootRegistry.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeRootRegistry.kt
index 4bb7766..11b66c0 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeRootRegistry.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeRootRegistry.kt
@@ -16,10 +16,16 @@
 
 package androidx.compose.ui.test.junit4.android
 
+import android.os.Handler
+import android.os.Looper
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewTreeLifecycleOwner
 import kotlinx.coroutines.suspendCancellableCoroutine
 import org.junit.runners.model.Statement
 import java.util.Collections
@@ -36,7 +42,10 @@
  * state.
  */
 internal class ComposeRootRegistry {
-    private val roots = Collections.newSetFromMap(WeakHashMap<ViewRootForTest, Boolean>())
+
+    private val lock = Any()
+    private val allRoots = Collections.newSetFromMap(WeakHashMap<ViewRootForTest, Boolean>())
+    private val resumedRoots = mutableSetOf<ViewRootForTest>()
     private val registryListeners = mutableSetOf<OnRegistrationChangedListener>()
 
     /**
@@ -57,66 +66,64 @@
      */
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     internal fun tearDownRegistry() {
-        ViewRootForTest.onViewCreatedCallback = null
-        synchronized(roots) {
-            getUnfilteredComposeRoots().forEach {
+        synchronized(lock) {
+            // Stop accepting new roots
+            ViewRootForTest.onViewCreatedCallback = null
+            // Unregister the world
+            getCreatedComposeRoots().forEach {
                 unregisterComposeRoot(it)
             }
-            // Remove all listeners as well, now we've unregistered all roots
-            synchronized(registryListeners) { registryListeners.clear() }
+            // Clear all references
+            allRoots.clear()
+            resumedRoots.clear()
+            registryListeners.clear()
         }
     }
 
     private fun onViewRootCreated(root: ViewRootForTest) {
-        root.view.addOnAttachStateChangeListener(ViewAttachedListener(root))
-    }
-
-    /**
-     * Returns a copy of the set of all registered [ViewRootForTest]s, including ones that are
-     * normally not relevant (like those whose lifecycle state is not RESUMED).
-     */
-    fun getUnfilteredComposeRoots(): Set<ViewRootForTest> {
-        return synchronized(roots) { roots.toSet() }
-    }
-
-    /**
-     * Returns a copy of the set of all registered [ViewRootForTest]s that can be interacted with.
-     * This method is almost always preferred over [getUnfilteredComposeRoots].
-     */
-    fun getComposeRoots(): Set<ViewRootForTest> {
-        return synchronized(roots) {
-            roots.filterTo(mutableSetOf()) {
-                it.isLifecycleInResumedState
+        // Need to register immediately to accommodate ViewRoots that have delayed
+        // setContent until they are attached to the window (e.g. popups and dialogs).
+        synchronized(lock) {
+            if (isSetUp) {
+                allRoots.add(root)
+                root.view.addOnAttachStateChangeListener(StateChangeHandler(root))
             }
         }
     }
 
     /**
-     * Adds the given [listener], to be notified when an [ViewRootForTest] registers or unregisters.
+     * Returns a copy of the set of all created [ViewRootForTest]s, including ones that are
+     * normally not relevant (like those whose lifecycle state is not RESUMED). You probably need
+     * [getRegisteredComposeRoots] though. Do not store any of these results, always call this
+     * method again if you need access to anything in this set.
      */
-    fun addOnRegistrationChangedListener(listener: OnRegistrationChangedListener) {
-        synchronized(registryListeners) { registryListeners.add(listener) }
+    fun getCreatedComposeRoots(): Set<ViewRootForTest> {
+        synchronized(lock) {
+            while (true) {
+                try {
+                    return allRoots.toSet()
+                } catch (_: ConcurrentModificationException) {
+                    // A weakly referenced key may have been cleared while copying the set
+                    // Keep trying until we succeed
+                }
+            }
+        }
     }
 
     /**
-     * Removes the given [listener].
+     * Returns a copy of the set of all registered [ViewRootForTest]s that can be interacted with.
+     * This method is almost always preferred over [getCreatedComposeRoots].
      */
-    fun removeOnRegistrationChangedListener(listener: OnRegistrationChangedListener) {
-        synchronized(registryListeners) { registryListeners.remove(listener) }
-    }
-
-    private fun dispatchOnRegistrationChanged(composeRoot: ViewRootForTest, isRegistered: Boolean) {
-        synchronized(registryListeners) { registryListeners.toList() }.forEach {
-            it.onRegistrationChanged(composeRoot, isRegistered)
-        }
+    fun getRegisteredComposeRoots(): Set<ViewRootForTest> {
+        return synchronized(lock) { resumedRoots.toSet() }
     }
 
     /**
      * Registers the [composeRoot] in this registry. Must be called from [View.onAttachedToWindow].
      */
     internal fun registerComposeRoot(composeRoot: ViewRootForTest) {
-        synchronized(roots) {
-            if (roots.add(composeRoot)) {
+        synchronized(lock) {
+            if (isSetUp && resumedRoots.add(composeRoot)) {
                 dispatchOnRegistrationChanged(composeRoot, true)
             }
         }
@@ -127,8 +134,8 @@
      * [View.onDetachedFromWindow].
      */
     internal fun unregisterComposeRoot(composeRoot: ViewRootForTest) {
-        synchronized(roots) {
-            if (roots.remove(composeRoot)) {
+        synchronized(lock) {
+            if (resumedRoots.remove(composeRoot)) {
                 dispatchOnRegistrationChanged(composeRoot, false)
             }
         }
@@ -155,20 +162,96 @@
         fun onRegistrationChanged(composeRoot: ViewRootForTest, registered: Boolean)
     }
 
-    private inner class ViewAttachedListener(
+    /**
+     * Adds the given [listener], to be notified when an [ViewRootForTest] registers or unregisters.
+     */
+    fun addOnRegistrationChangedListener(listener: OnRegistrationChangedListener) {
+        synchronized(lock) { registryListeners.add(listener) }
+    }
+
+    /**
+     * Removes the given [listener].
+     */
+    fun removeOnRegistrationChangedListener(listener: OnRegistrationChangedListener) {
+        synchronized(lock) { registryListeners.remove(listener) }
+    }
+
+    private fun dispatchOnRegistrationChanged(composeRoot: ViewRootForTest, isRegistered: Boolean) {
+        synchronized(lock) { registryListeners.toList() }.forEach {
+            it.onRegistrationChanged(composeRoot, isRegistered)
+        }
+    }
+
+    private inner class StateChangeHandler(
         private val composeRoot: ViewRootForTest
-    ) : View.OnAttachStateChangeListener {
+    ) : View.OnAttachStateChangeListener, LifecycleEventObserver, OnRegistrationChangedListener {
+        private var removeObserver: (() -> Unit)? = null
+
         override fun onViewAttachedToWindow(view: View) {
-            registerComposeRoot(composeRoot)
+            // Only add lifecycle observer. If the root is resumed,
+            // the lifecycle observer will get notified.
+            val lifecycle = checkNotNull(ViewTreeLifecycleOwner.get(view)?.lifecycle)
+            lifecycle.addObserver(this)
+            // Setup a lambda to remove the observer when we're detached from the window. When
+            // that happens, we won't have access to the lifecycle anymore.
+            removeObserver = {
+                lifecycle.removeObserver(this)
+            }
         }
 
         override fun onViewDetachedFromWindow(view: View) {
+            removeLifecycleObserver()
+            // Also unregister the root, as we won't receive lifecycle events anymore
+            unregisterComposeRoot()
+        }
+
+        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+            if (event == Lifecycle.Event.ON_RESUME) {
+                registerComposeRoot(composeRoot)
+                // Listen to when it is unregistered: it happens
+                // when the registry is torn down prematurely
+                addOnRegistrationChangedListener(this)
+            } else {
+                unregisterComposeRoot()
+            }
+        }
+
+        override fun onRegistrationChanged(composeRoot: ViewRootForTest, registered: Boolean) {
+            if (composeRoot == this.composeRoot && !registered) {
+                // If we are unregistered, stop observing the lifecycle
+                removeLifecycleObserver()
+            }
+        }
+
+        private fun unregisterComposeRoot() {
+            removeOnRegistrationChangedListener(this)
             unregisterComposeRoot(composeRoot)
         }
+
+        private fun removeLifecycleObserver() {
+            // Lifecycle observers can only be added/removed on the main thread, but
+            // this method can be called from any thread when the registry is torn down.
+            if (Looper.myLooper() != Looper.getMainLooper()) {
+                // Post at front of queue to make sure we remove
+                // the observer before it can receive new events
+                Handler(Looper.getMainLooper()).postAtFrontOfQueue {
+                    removeLifecycleObserverMainThread()
+                }
+            } else {
+                removeLifecycleObserverMainThread()
+            }
+        }
+
+        private fun removeLifecycleObserverMainThread() {
+            removeObserver?.invoke()?.also {
+                removeObserver = null
+            }
+        }
     }
 }
 
-private val ComposeRootRegistry.hasComposeRoots: Boolean get() = getComposeRoots().isNotEmpty()
+private val ComposeRootRegistry.hasComposeRoots: Boolean
+    get() = getRegisteredComposeRoots().isNotEmpty()
 
 private fun ComposeRootRegistry.ensureComposeRootRegistryIsSetUp() {
     check(isSetUp) {
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt
index 912e3f8..98c61aa 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt
+++ b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.kt
@@ -104,7 +104,7 @@
     @OptIn(ExperimentalComposeApi::class)
     private fun isIdle() =
         !Snapshot.current.hasPendingChanges() &&
-            !Recomposer.current().hasInvalidations()
+            !Recomposer.runningRecomposers.value.any { it.hasPendingWork }
 
     override fun waitForIdle() {
         while (!isIdle()) {
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 7374580..ee1b7d4 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -261,14 +261,25 @@
     method @Deprecated @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier drawOpacity(androidx.compose.ui.Modifier, float opacity);
   }
 
+  public interface BuildDrawCacheParams {
+    method public androidx.compose.ui.unit.Density getDensity();
+    method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
+    method public long getSize-NH-jbRc();
+    property public abstract androidx.compose.ui.unit.Density density;
+    property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
+    property public abstract long size;
+  }
+
   public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
     method public float getDensity();
     method public float getFontScale();
+    method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
     method public long getSize-NH-jbRc();
     method public androidx.compose.ui.draw.DrawResult onDrawBehind(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> block);
     method public androidx.compose.ui.draw.DrawResult onDrawWithContent(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> block);
     property public float density;
     property public float fontScale;
+    property public final androidx.compose.ui.unit.LayoutDirection layoutDirection;
     property public final long size;
   }
 
@@ -278,7 +289,7 @@
   }
 
   public interface DrawCacheModifier extends androidx.compose.ui.draw.DrawModifier {
-    method public void onBuildCache-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    method public void onBuildCache(androidx.compose.ui.draw.BuildDrawCacheParams params);
   }
 
   public interface DrawModifier extends androidx.compose.ui.Modifier.Element {
@@ -2100,7 +2111,7 @@
     method public void move--gyyYBs(long position);
     method public void resize-ozmzZPI(long size);
     method public void updateDisplayList();
-    method public void updateLayerProperties-LsE73-w(float scaleX, float scaleY, float alpha, float translationX, float translationY, float shadowElevation, float rotationX, float rotationY, float rotationZ, float cameraDistance, long transformOrigin, androidx.compose.ui.graphics.Shape shape, boolean clip);
+    method public void updateLayerProperties-y8e2Zg0(float scaleX, float scaleY, float alpha, float translationX, float translationY, float shadowElevation, float rotationX, float rotationY, float rotationZ, float cameraDistance, long transformOrigin, androidx.compose.ui.graphics.Shape shape, boolean clip, androidx.compose.ui.unit.LayoutDirection layoutDirection);
     property public abstract long layerId;
   }
 
@@ -2155,12 +2166,15 @@
     method public final void disposeComposition();
     method public final boolean getHasComposition();
     method protected boolean getShouldCreateCompositionOnAttachedToWindow();
+    method public final boolean getShowLayoutBounds();
     method protected final void onLayout(boolean changed, int left, int top, int right, int bottom);
     method protected final void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
     method public final void setParentCompositionReference(androidx.compose.runtime.CompositionReference? parent);
+    method public final void setShowLayoutBounds(boolean value);
     method public final void setViewCompositionStrategy(androidx.compose.ui.platform.ViewCompositionStrategy strategy);
     property public final boolean hasComposition;
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
+    property public final boolean showLayoutBounds;
   }
 
   public final class AmbientsKt {
@@ -2408,9 +2422,7 @@
   }
 
   public static final class WindowRecomposerFactory.Companion {
-    method @Deprecated public androidx.compose.ui.platform.WindowRecomposerFactory getGlobal();
     method public androidx.compose.ui.platform.WindowRecomposerFactory getLifecycleAware();
-    property @Deprecated public final androidx.compose.ui.platform.WindowRecomposerFactory Global;
     property public final androidx.compose.ui.platform.WindowRecomposerFactory LifecycleAware;
   }
 
@@ -2421,15 +2433,14 @@
   }
 
   @androidx.compose.ui.InternalComposeUiApi public final class WindowRecomposerPolicy {
-    method public void setWindowRecomposerFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method public void setFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method public inline <R> R! withFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory, kotlin.jvm.functions.Function0<? extends R> block);
     field public static final androidx.compose.ui.platform.WindowRecomposerPolicy INSTANCE;
   }
 
   public final class WrapperKt {
-    method public static androidx.compose.runtime.Composition setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated public static androidx.compose.runtime.Composition setContent(android.view.ViewGroup, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated public static androidx.compose.runtime.Composition setViewContent(android.view.ViewGroup, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-    method @Deprecated public static androidx.compose.runtime.Composition setViewContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+    method public static void setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionReference? parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated public static androidx.compose.runtime.Composition setContent(android.view.ViewGroup, androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
 }
@@ -2633,6 +2644,7 @@
   public enum Role {
     enum_constant public static final androidx.compose.ui.semantics.Role Button;
     enum_constant public static final androidx.compose.ui.semantics.Role Checkbox;
+    enum_constant public static final androidx.compose.ui.semantics.Role Image;
     enum_constant public static final androidx.compose.ui.semantics.Role RadioButton;
     enum_constant public static final androidx.compose.ui.semantics.Role Switch;
     enum_constant public static final androidx.compose.ui.semantics.Role Tab;
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 7374580..ee1b7d4 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -261,14 +261,25 @@
     method @Deprecated @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier drawOpacity(androidx.compose.ui.Modifier, float opacity);
   }
 
+  public interface BuildDrawCacheParams {
+    method public androidx.compose.ui.unit.Density getDensity();
+    method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
+    method public long getSize-NH-jbRc();
+    property public abstract androidx.compose.ui.unit.Density density;
+    property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
+    property public abstract long size;
+  }
+
   public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
     method public float getDensity();
     method public float getFontScale();
+    method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
     method public long getSize-NH-jbRc();
     method public androidx.compose.ui.draw.DrawResult onDrawBehind(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> block);
     method public androidx.compose.ui.draw.DrawResult onDrawWithContent(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> block);
     property public float density;
     property public float fontScale;
+    property public final androidx.compose.ui.unit.LayoutDirection layoutDirection;
     property public final long size;
   }
 
@@ -278,7 +289,7 @@
   }
 
   public interface DrawCacheModifier extends androidx.compose.ui.draw.DrawModifier {
-    method public void onBuildCache-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    method public void onBuildCache(androidx.compose.ui.draw.BuildDrawCacheParams params);
   }
 
   public interface DrawModifier extends androidx.compose.ui.Modifier.Element {
@@ -2100,7 +2111,7 @@
     method public void move--gyyYBs(long position);
     method public void resize-ozmzZPI(long size);
     method public void updateDisplayList();
-    method public void updateLayerProperties-LsE73-w(float scaleX, float scaleY, float alpha, float translationX, float translationY, float shadowElevation, float rotationX, float rotationY, float rotationZ, float cameraDistance, long transformOrigin, androidx.compose.ui.graphics.Shape shape, boolean clip);
+    method public void updateLayerProperties-y8e2Zg0(float scaleX, float scaleY, float alpha, float translationX, float translationY, float shadowElevation, float rotationX, float rotationY, float rotationZ, float cameraDistance, long transformOrigin, androidx.compose.ui.graphics.Shape shape, boolean clip, androidx.compose.ui.unit.LayoutDirection layoutDirection);
     property public abstract long layerId;
   }
 
@@ -2155,12 +2166,15 @@
     method public final void disposeComposition();
     method public final boolean getHasComposition();
     method protected boolean getShouldCreateCompositionOnAttachedToWindow();
+    method public final boolean getShowLayoutBounds();
     method protected final void onLayout(boolean changed, int left, int top, int right, int bottom);
     method protected final void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
     method public final void setParentCompositionReference(androidx.compose.runtime.CompositionReference? parent);
+    method public final void setShowLayoutBounds(boolean value);
     method public final void setViewCompositionStrategy(androidx.compose.ui.platform.ViewCompositionStrategy strategy);
     property public final boolean hasComposition;
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
+    property public final boolean showLayoutBounds;
   }
 
   public final class AmbientsKt {
@@ -2408,9 +2422,7 @@
   }
 
   public static final class WindowRecomposerFactory.Companion {
-    method @Deprecated public androidx.compose.ui.platform.WindowRecomposerFactory getGlobal();
     method public androidx.compose.ui.platform.WindowRecomposerFactory getLifecycleAware();
-    property @Deprecated public final androidx.compose.ui.platform.WindowRecomposerFactory Global;
     property public final androidx.compose.ui.platform.WindowRecomposerFactory LifecycleAware;
   }
 
@@ -2421,15 +2433,14 @@
   }
 
   @androidx.compose.ui.InternalComposeUiApi public final class WindowRecomposerPolicy {
-    method public void setWindowRecomposerFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method public void setFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method public inline <R> R! withFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory, kotlin.jvm.functions.Function0<? extends R> block);
     field public static final androidx.compose.ui.platform.WindowRecomposerPolicy INSTANCE;
   }
 
   public final class WrapperKt {
-    method public static androidx.compose.runtime.Composition setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated public static androidx.compose.runtime.Composition setContent(android.view.ViewGroup, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated public static androidx.compose.runtime.Composition setViewContent(android.view.ViewGroup, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-    method @Deprecated public static androidx.compose.runtime.Composition setViewContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+    method public static void setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionReference? parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated public static androidx.compose.runtime.Composition setContent(android.view.ViewGroup, androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
 }
@@ -2633,6 +2644,7 @@
   public enum Role {
     enum_constant public static final androidx.compose.ui.semantics.Role Button;
     enum_constant public static final androidx.compose.ui.semantics.Role Checkbox;
+    enum_constant public static final androidx.compose.ui.semantics.Role Image;
     enum_constant public static final androidx.compose.ui.semantics.Role RadioButton;
     enum_constant public static final androidx.compose.ui.semantics.Role Switch;
     enum_constant public static final androidx.compose.ui.semantics.Role Tab;
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 18788d3..f6bc111 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -261,14 +261,25 @@
     method @Deprecated @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier drawOpacity(androidx.compose.ui.Modifier, float opacity);
   }
 
+  public interface BuildDrawCacheParams {
+    method public androidx.compose.ui.unit.Density getDensity();
+    method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
+    method public long getSize-NH-jbRc();
+    property public abstract androidx.compose.ui.unit.Density density;
+    property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
+    property public abstract long size;
+  }
+
   public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
     method public float getDensity();
     method public float getFontScale();
+    method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
     method public long getSize-NH-jbRc();
     method public androidx.compose.ui.draw.DrawResult onDrawBehind(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> block);
     method public androidx.compose.ui.draw.DrawResult onDrawWithContent(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> block);
     property public float density;
     property public float fontScale;
+    property public final androidx.compose.ui.unit.LayoutDirection layoutDirection;
     property public final long size;
   }
 
@@ -278,7 +289,7 @@
   }
 
   public interface DrawCacheModifier extends androidx.compose.ui.draw.DrawModifier {
-    method public void onBuildCache-lwCvPpU(long size, androidx.compose.ui.unit.Density density);
+    method public void onBuildCache(androidx.compose.ui.draw.BuildDrawCacheParams params);
   }
 
   public interface DrawModifier extends androidx.compose.ui.Modifier.Element {
@@ -2162,7 +2173,7 @@
     method public void move--gyyYBs(long position);
     method public void resize-ozmzZPI(long size);
     method public void updateDisplayList();
-    method public void updateLayerProperties-LsE73-w(float scaleX, float scaleY, float alpha, float translationX, float translationY, float shadowElevation, float rotationX, float rotationY, float rotationZ, float cameraDistance, long transformOrigin, androidx.compose.ui.graphics.Shape shape, boolean clip);
+    method public void updateLayerProperties-y8e2Zg0(float scaleX, float scaleY, float alpha, float translationX, float translationY, float shadowElevation, float rotationX, float rotationY, float rotationZ, float cameraDistance, long transformOrigin, androidx.compose.ui.graphics.Shape shape, boolean clip, androidx.compose.ui.unit.LayoutDirection layoutDirection);
     property public abstract long layerId;
   }
 
@@ -2217,12 +2228,15 @@
     method public final void disposeComposition();
     method public final boolean getHasComposition();
     method protected boolean getShouldCreateCompositionOnAttachedToWindow();
+    method public final boolean getShowLayoutBounds();
     method protected final void onLayout(boolean changed, int left, int top, int right, int bottom);
     method protected final void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
     method public final void setParentCompositionReference(androidx.compose.runtime.CompositionReference? parent);
+    method public final void setShowLayoutBounds(boolean value);
     method public final void setViewCompositionStrategy(androidx.compose.ui.platform.ViewCompositionStrategy strategy);
     property public final boolean hasComposition;
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
+    property public final boolean showLayoutBounds;
   }
 
   public final class AmbientsKt {
@@ -2470,9 +2484,7 @@
   }
 
   public static final class WindowRecomposerFactory.Companion {
-    method @Deprecated public androidx.compose.ui.platform.WindowRecomposerFactory getGlobal();
     method public androidx.compose.ui.platform.WindowRecomposerFactory getLifecycleAware();
-    property @Deprecated public final androidx.compose.ui.platform.WindowRecomposerFactory Global;
     property public final androidx.compose.ui.platform.WindowRecomposerFactory LifecycleAware;
   }
 
@@ -2483,15 +2495,16 @@
   }
 
   @androidx.compose.ui.InternalComposeUiApi public final class WindowRecomposerPolicy {
-    method public void setWindowRecomposerFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method @kotlin.PublishedApi internal boolean compareAndSetFactory(androidx.compose.ui.platform.WindowRecomposerFactory expected, androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method @kotlin.PublishedApi internal androidx.compose.ui.platform.WindowRecomposerFactory getAndSetFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method public void setFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory);
+    method public inline <R> R! withFactory(androidx.compose.ui.platform.WindowRecomposerFactory factory, kotlin.jvm.functions.Function0<? extends R> block);
     field public static final androidx.compose.ui.platform.WindowRecomposerPolicy INSTANCE;
   }
 
   public final class WrapperKt {
-    method public static androidx.compose.runtime.Composition setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated public static androidx.compose.runtime.Composition setContent(android.view.ViewGroup, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated public static androidx.compose.runtime.Composition setViewContent(android.view.ViewGroup, optional androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-    method @Deprecated public static androidx.compose.runtime.Composition setViewContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+    method public static void setContent(androidx.activity.ComponentActivity, optional androidx.compose.runtime.CompositionReference? parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated public static androidx.compose.runtime.Composition setContent(android.view.ViewGroup, androidx.compose.runtime.CompositionReference parent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
 }
@@ -2695,6 +2708,7 @@
   public enum Role {
     enum_constant public static final androidx.compose.ui.semantics.Role Button;
     enum_constant public static final androidx.compose.ui.semantics.Role Checkbox;
+    enum_constant public static final androidx.compose.ui.semantics.Role Image;
     enum_constant public static final androidx.compose.ui.semantics.Role RadioButton;
     enum_constant public static final androidx.compose.ui.semantics.Role Switch;
     enum_constant public static final androidx.compose.ui.semantics.Role Tab;
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index ac04486..d503b0f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -254,40 +254,44 @@
     }
 
     @Test
-    fun testPopulateAccessibilityNodeInfoProperties_role() {
-        var semanticsNode = createSemanticsNodeWithProperties(1, true) {
+    fun testPopulateAccessibilityNodeInfoProperties_buttonRole() {
+        val semanticsNode = createSemanticsNodeWithProperties(1, true) {
             role = Role.Button
         }
         accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
         assertEquals("android.widget.Button", info.className)
-        info.recycle()
+    }
 
-        info = AccessibilityNodeInfoCompat.obtain()
-        semanticsNode = createSemanticsNodeWithProperties(1, true) {
+    @Test
+    fun testPopulateAccessibilityNodeInfoProperties_switchRole() {
+        val semanticsNode = createSemanticsNodeWithProperties(1, true) {
             role = Role.Switch
         }
         accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
         assertEquals("android.widget.Switch", info.className)
-        info.recycle()
+    }
 
-        info = AccessibilityNodeInfoCompat.obtain()
-        semanticsNode = createSemanticsNodeWithProperties(1, true) {
+    @Test
+    fun testPopulateAccessibilityNodeInfoProperties_checkBoxRole() {
+        val semanticsNode = createSemanticsNodeWithProperties(1, true) {
             role = Role.Checkbox
         }
         accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
         assertEquals("android.widget.CheckBox", info.className)
-        info.recycle()
+    }
 
-        info = AccessibilityNodeInfoCompat.obtain()
-        semanticsNode = createSemanticsNodeWithProperties(1, true) {
+    @Test
+    fun testPopulateAccessibilityNodeInfoProperties_radioButtonRole() {
+        val semanticsNode = createSemanticsNodeWithProperties(1, true) {
             role = Role.RadioButton
         }
         accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
         assertEquals("android.widget.RadioButton", info.className)
-        info.recycle()
+    }
 
-        info = AccessibilityNodeInfoCompat.obtain()
-        semanticsNode = createSemanticsNodeWithProperties(1, true) {
+    @Test
+    fun testPopulateAccessibilityNodeInfoProperties_tabRole() {
+        val semanticsNode = createSemanticsNodeWithProperties(1, true) {
             role = Role.Tab
         }
         accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
@@ -295,6 +299,15 @@
     }
 
     @Test
+    fun testPopulateAccessibilityNodeInfoProperties_imageRole() {
+        val semanticsNode = createSemanticsNodeWithProperties(1, true) {
+            role = Role.Image
+        }
+        accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
+        assertEquals("android.widget.ImageView", info.className)
+    }
+
+    @Test
     fun testPopulateAccessibilityNodeInfoProperties_SeekBar() {
         val setProgressActionLabel = "setProgress"
         val semanticsNode = createSemanticsNodeWithProperties(1, true) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index 00f8ee8..4c0368a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -82,7 +82,6 @@
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInRoot
-import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.node.Ref
 import androidx.compose.ui.platform.AmbientDensity
@@ -139,6 +138,7 @@
     val activityTestRule = androidx.test.rule.ActivityTestRule<TestActivity>(
         TestActivity::class.java
     )
+
     @get:Rule
     val excessiveAssertions = AndroidOwnerExtraAssertionsRule()
     private lateinit var activity: TestActivity
@@ -252,7 +252,8 @@
                 cameraDistance = cameraDistance,
                 transformOrigin = TransformOrigin.Center,
                 shape = RectangleShape,
-                clip = true
+                clip = true,
+                layoutDirection = LayoutDirection.Ltr
             )
         }
         // Verify that the camera distance is applied properly even after accounting for
@@ -365,12 +366,9 @@
         activityTestRule.runOnUiThreadIR {
             // there isn't going to be a normal draw because we are just moving the repaint
             // boundary, but we should have a draw cycle
-            activityTestRule.findAndroidComposeView().viewTreeObserver.addOnDrawListener(object :
-                    ViewTreeObserver.OnDrawListener {
-                    override fun onDraw() {
-                        drawLatch.countDown()
-                    }
-                })
+            activityTestRule.findAndroidComposeView().viewTreeObserver.addOnDrawListener {
+                drawLatch.countDown()
+            }
             offset.value = 20
         }
 
@@ -2329,7 +2327,7 @@
     // Tests that show layout bounds draws outlines around content and modifiers
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
-    @OptIn(InternalCoreApi::class)
+    @OptIn(InternalComposeUiApi::class)
     fun showLayoutBounds_content() {
         activityTestRule.runOnUiThreadIR {
             activity.setContent {
@@ -2343,8 +2341,8 @@
                 }
             }
             val content = activity.findViewById<ViewGroup>(android.R.id.content)
-            val owner = content.getChildAt(0) as Owner
-            owner.showLayoutBounds = true
+            val composeView = content.getChildAt(0) as ComposeView
+            composeView.showLayoutBounds = true
         }
         activityTestRule.waitAndScreenShot().apply {
             assertRect(Color.White, size = 8)
@@ -2465,15 +2463,18 @@
     @Test
     fun layerModifier_noClip() {
         val triangleShape = object : Shape {
-            override fun createOutline(size: Size, density: Density): Outline =
-                Outline.Generic(
-                    Path().apply {
-                        moveTo(size.width / 2f, 0f)
-                        lineTo(size.width, size.height)
-                        lineTo(0f, size.height)
-                        close()
-                    }
-                )
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ) = Outline.Generic(
+                Path().apply {
+                    moveTo(size.width / 2f, 0f)
+                    lineTo(size.width, size.height)
+                    lineTo(0f, size.height)
+                    close()
+                }
+            )
         }
         activityTestRule.runOnUiThread {
             activity.setContent {
@@ -2581,12 +2582,12 @@
     @Test
     fun detachChildWithLayer() {
         activityTestRule.runOnUiThread {
-            val composition = activity.setContent {
+            activity.setContent {
                 FixedSize(10, Modifier.graphicsLayer()) {
                     FixedSize(8)
                 }
             }
-            composition.dispose()
+            activity.setContentView(View(activity)) // Replace content view with empty
         }
     }
 
@@ -3339,6 +3340,7 @@
     /**
      * invalidateDescendants should invalidate all layout layers.
      */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun invalidateDescendants() {
         var color = Color.White
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt
index f596611..c4198df 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/MemoryLeakTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui
 
 import android.util.Log
+import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -97,8 +98,9 @@
     fun disposeContent_assertNoLeak() = runBlocking(AndroidUiDispatcher.Main) {
         // We have to ignore the first run because `dispose` leaves the OwnerView in the
         // View hierarchy to reuse it next time. That is probably not the final desired behavior
-        loopAndVerifyMemory(iterations = 400, gcFrequency = 40, ignoreFirstRun = true) {
-            val composition = activityTestRule.activity.setContent {
+        val emptyView = View(activityTestRule.activity)
+        loopAndVerifyMemory(iterations = 400, gcFrequency = 40) {
+            activityTestRule.activity.setContent {
                 Column {
                     repeat(3) {
                         Box {
@@ -108,8 +110,8 @@
                 }
             }
 
-            // This typically recycles the old view
-            composition.dispose()
+            // This replaces the Compose view, disposing its composition.
+            activityTestRule.activity.setContentView(emptyView)
         }
     }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
index c6800cd..b27892f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap
 import android.os.Build
+import androidx.compose.runtime.Providers
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.AtLeastSize
 import androidx.compose.ui.Modifier
@@ -39,10 +40,12 @@
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.padding
+import androidx.compose.ui.platform.AmbientLayoutDirection
 import androidx.compose.ui.platform.setContent
 import androidx.compose.ui.runOnUiThreadIR
 import androidx.compose.ui.test.TestActivity
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.waitAndScreenShot
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -66,11 +69,11 @@
     private lateinit var drawLatch: CountDownLatch
 
     private val rectShape = object : Shape {
-        override fun createOutline(size: Size, density: Density): Outline =
+        override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density) =
             Outline.Rectangle(size.toRect())
     }
     private val triangleShape = object : Shape {
-        override fun createOutline(size: Size, density: Density): Outline =
+        override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density) =
             Outline.Generic(
                 Path().apply {
                     moveTo(size.width / 2f, 0f)
@@ -81,7 +84,7 @@
             )
     }
     private val invertedTriangleShape = object : Shape {
-        override fun createOutline(size: Size, density: Density): Outline =
+        override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density) =
             Outline.Generic(
                 Path().apply {
                     lineTo(size.width, 0f)
@@ -166,8 +169,11 @@
     @Test
     fun roundedUniformRectClip() {
         val shape = object : Shape {
-            override fun createOutline(size: Size, density: Density): Outline =
-                Outline.Rounded(RoundRect(size.toRect(), CornerRadius(12f)))
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ) = Outline.Rounded(RoundRect(size.toRect(), CornerRadius(12f)))
         }
         rule.runOnUiThreadIR {
             activity.setContent {
@@ -200,16 +206,19 @@
     @Test
     fun roundedRectWithDiffCornersClip() {
         val shape = object : Shape {
-            override fun createOutline(size: Size, density: Density): Outline =
-                Outline.Rounded(
-                    RoundRect(
-                        size.toRect(),
-                        CornerRadius.Zero,
-                        CornerRadius(12f),
-                        CornerRadius(12f),
-                        CornerRadius(12f)
-                    )
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ) = Outline.Rounded(
+                RoundRect(
+                    size.toRect(),
+                    CornerRadius.Zero,
+                    CornerRadius(12f),
+                    CornerRadius(12f),
+                    CornerRadius(12f)
                 )
+            )
         }
         rule.runOnUiThreadIR {
             activity.setContent {
@@ -257,16 +266,19 @@
     fun concaveClip() {
         // 30 pixels rect with a rect hole of 10 pixels in the middle
         val concaveShape = object : Shape {
-            override fun createOutline(size: Size, density: Density): Outline =
-                Outline.Generic(
-                    Path().apply {
-                        op(
-                            Path().apply { addRect(Rect(0f, 0f, 30f, 30f)) },
-                            Path().apply { addRect(Rect(10f, 10f, 20f, 20f)) },
-                            PathOperation.difference
-                        )
-                    }
-                )
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ) = Outline.Generic(
+                Path().apply {
+                    op(
+                        Path().apply { addRect(Rect(0f, 0f, 30f, 30f)) },
+                        Path().apply { addRect(Rect(10f, 10f, 20f, 20f)) },
+                        PathOperation.difference
+                    )
+                }
+            )
         }
         rule.runOnUiThreadIR {
             activity.setContent {
@@ -308,8 +320,11 @@
         drawLatch = CountDownLatch(1)
         rule.runOnUiThreadIR {
             model.value = object : Shape {
-                override fun createOutline(size: Size, density: Density): Outline =
-                    Outline.Rounded(RoundRect(size.toRect(), CornerRadius(12f)))
+                override fun createOutline(
+                    size: Size,
+                    layoutDirection: LayoutDirection,
+                    density: Density
+                ) = Outline.Rounded(RoundRect(size.toRect(), CornerRadius(12f)))
             }
         }
 
@@ -452,6 +467,47 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun switchLayoutDirection() {
+        val direction = mutableStateOf(LayoutDirection.Ltr)
+        val shape = object : Shape {
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ) = if (layoutDirection == LayoutDirection.Ltr) {
+                rectShape.createOutline(size, layoutDirection, density)
+            } else {
+                triangleShape.createOutline(size, layoutDirection, density)
+            }
+        }
+
+        rule.runOnUiThreadIR {
+            activity.setContent {
+                Providers(AmbientLayoutDirection provides direction.value) {
+                    AtLeastSize(
+                        size = 30,
+                        modifier = Modifier.fillColor(Color.Green)
+                            .clip(shape)
+                            .fillColor(Color.Cyan)
+                    ) {}
+                }
+            }
+        }
+
+        takeScreenShot(30).apply {
+            assertRect(Color.Cyan, size = 30)
+        }
+
+        drawLatch = CountDownLatch(1)
+        rule.runOnUiThread { direction.value = LayoutDirection.Rtl }
+
+        takeScreenShot(30).apply {
+            assertTriangle(Color.Cyan, Color.Green)
+        }
+    }
+
     private fun Modifier.fillColor(color: Color): Modifier {
         return drawBehind {
             drawRect(
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
index c7a8018..ea680c0 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
@@ -19,6 +19,7 @@
 import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.runtime.Providers
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -32,6 +33,7 @@
 import androidx.compose.ui.graphics.asAndroidBitmap
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.AmbientLayoutDirection
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
@@ -40,6 +42,7 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
@@ -266,6 +269,36 @@
         }
     }
 
+    @Test
+    fun testCacheInvalidatedAfterLayoutDirectionChange() {
+        var cacheBuildCount = 0
+        var layoutDirection by mutableStateOf(LayoutDirection.Ltr)
+        var realLayoutDirection: LayoutDirection? = null
+        rule.setContent {
+            Providers(AmbientLayoutDirection provides layoutDirection) {
+                AtLeastSize(
+                    size = 10,
+                    modifier = Modifier.drawWithCache {
+                        realLayoutDirection = layoutDirection
+                        cacheBuildCount++
+                        onDrawBehind {}
+                    }
+                ) { }
+            }
+        }
+
+        rule.runOnIdle {
+            assertEquals(1, cacheBuildCount)
+            assertEquals(LayoutDirection.Ltr, realLayoutDirection)
+            layoutDirection = LayoutDirection.Rtl
+        }
+
+        rule.runOnIdle {
+            assertEquals(2, cacheBuildCount)
+            assertEquals(LayoutDirection.Rtl, realLayoutDirection)
+        }
+    }
+
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun testCacheInvalidatedWithHelperModifier() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
index 7d9c808..e330766 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
@@ -54,6 +54,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -382,8 +383,11 @@
     @Test
     fun testEmptyClip() {
         val EmptyRectangle = object : Shape {
-            override fun createOutline(size: Size, density: Density): Outline =
-                Outline.Rectangle(Rect.Zero)
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ) = Outline.Rectangle(Rect.Zero)
         }
         val tag = "testTag"
         rule.setContent {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt
index f8fe5a9..65d53ce 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt
@@ -40,6 +40,7 @@
 import androidx.compose.ui.test.TestActivity
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.waitAndScreenShot
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -69,8 +70,11 @@
     private lateinit var drawLatch: CountDownLatch
 
     private val rectShape = object : Shape {
-        override fun createOutline(size: Size, density: Density): Outline =
-            Outline.Rectangle(size.toRect())
+        override fun createOutline(
+            size: Size,
+            layoutDirection: LayoutDirection,
+            density: Density
+        ) = Outline.Rectangle(size.toRect())
     }
 
     @Before
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
index e11ddbf..fd51969 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
@@ -57,28 +57,26 @@
         }
         lateinit var weakActivityRef: WeakReference<Activity>
         try {
-            ActivityScenario.launch(ComponentActivity::class.java).use { scenario ->
-                scenario.onActivity { activity ->
-                    weakActivityRef = WeakReference(activity)
-                    WindowRecomposerPolicy.setWindowRecomposerFactory { localRecomposer }
-                    activity.setContentView(
-                        ComposeView(activity).apply {
-                            setContent {
-                                Box(Modifier.background(Color.Blue).fillMaxSize())
+            WindowRecomposerPolicy.withFactory({ localRecomposer }) {
+                ActivityScenario.launch(ComponentActivity::class.java).use { scenario ->
+                    scenario.onActivity { activity ->
+                        weakActivityRef = WeakReference(activity)
+                        activity.setContentView(
+                            ComposeView(activity).apply {
+                                setContent {
+                                    Box(Modifier.background(Color.Blue).fillMaxSize())
+                                }
                             }
-                        }
-                    )
+                        )
+                    }
+                    assertNotNull(weakActivityRef.get())
                 }
-                assertNotNull(weakActivityRef.get())
+                repeat(10) {
+                    Runtime.getRuntime().gc()
+                }
+                assertNull("expected Activity to have been collected", weakActivityRef.get())
             }
-            repeat(10) {
-                Runtime.getRuntime().gc()
-            }
-            assertNull("expected Activity to have been collected", weakActivityRef.get())
         } finally {
-            // TODO: Change this to the `with` API from a later CL
-            @Suppress("DEPRECATION")
-            WindowRecomposerPolicy.setWindowRecomposerFactory(WindowRecomposerFactory.Global)
             localRecomposer.shutDown()
             runBlocking {
                 recomposerJob.join()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
index 5c8a11e..bfbf0b8 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
@@ -55,7 +55,8 @@
     @Before
     fun setup() {
         activityScenario = ActivityScenario.launch(TestActivity::class.java)
-        activityScenario.moveToState(Lifecycle.State.CREATED)
+        // Default Recomposer will not recompose if the lifecycle state is not at least STARTED
+        activityScenario.moveToState(Lifecycle.State.STARTED)
     }
 
     @Test
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt
index 0e4a2b4..164a138 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt
@@ -255,6 +255,7 @@
                 Role.Switch -> info.className = "android.widget.Switch"
                 Role.RadioButton -> info.className = "android.widget.RadioButton"
                 Role.Tab -> info.roleDescription = AccessibilityRoleDescriptions.Tab
+                Role.Image -> info.className = "android.widget.ImageView"
             }
         }
         info.packageName = view.context.packageName
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.kt
index cac514d..5727f47 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.kt
@@ -24,6 +24,9 @@
 import androidx.compose.runtime.Composition
 import androidx.compose.runtime.CompositionReference
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.InternalComposeUiApi
+import androidx.compose.ui.node.InternalCoreApi
+import androidx.compose.ui.node.Owner
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.ViewTreeLifecycleOwner
 
@@ -112,6 +115,20 @@
         get() = true
 
     /**
+     * Enables the display of visual layout bounds for the Compose UI content of this view.
+     * This is typically managed
+     */
+    @OptIn(InternalCoreApi::class)
+    @InternalComposeUiApi
+    var showLayoutBounds: Boolean = false
+        set(value) {
+            field = value
+            getChildAt(0)?.let {
+                (it as Owner).showLayoutBounds = value
+            }
+        }
+
+    /**
      * The Jetpack Compose UI content for this view.
      * Subclasses must implement this method to provide content. Initial composition will
      * occur when the view becomes attached to a window or when [createComposition] is called,
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.kt
index 41840e4..a0255f3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/OutlineResolver.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.asAndroidPath
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import kotlin.math.roundToInt
 import android.graphics.Outline as AndroidOutline
 
@@ -108,10 +109,18 @@
      */
     private var outlineNeeded = false
 
+    private var layoutDirection = LayoutDirection.Ltr
+
     /**
      * Updates the values of the outline. Returns `true` when the shape has changed.
      */
-    fun update(shape: Shape, alpha: Float, clipToOutline: Boolean, elevation: Float): Boolean {
+    fun update(
+        shape: Shape,
+        alpha: Float,
+        clipToOutline: Boolean,
+        elevation: Float,
+        layoutDirection: LayoutDirection
+    ): Boolean {
         cachedOutline.alpha = alpha
         val shapeChanged = this.shape != shape
         if (shapeChanged) {
@@ -123,6 +132,10 @@
             this.outlineNeeded = outlineNeeded
             cacheIsDirty = true
         }
+        if (this.layoutDirection != layoutDirection) {
+            this.layoutDirection = layoutDirection
+            cacheIsDirty = true
+        }
         return shapeChanged
     }
 
@@ -145,7 +158,7 @@
                 // The methods to configure the outline will determine/update the flag
                 // if it not supported on the API level
                 isSupportedOutline = true
-                when (val outline = shape.createOutline(size, density)) {
+                when (val outline = shape.createOutline(size, layoutDirection, density)) {
                     is Outline.Rectangle -> updateCacheWithRect(outline.rect)
                     is Outline.Rounded -> updateCacheWithRoundRect(outline.roundRect)
                     is Outline.Generic -> updateCacheWithPath(outline.path)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt
index a89a65e..fb55794 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.node.OwnedLayer
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * RenderNode implementation of OwnedLayer.
@@ -80,7 +81,8 @@
         cameraDistance: Float,
         transformOrigin: TransformOrigin,
         shape: Shape,
-        clip: Boolean
+        clip: Boolean,
+        layoutDirection: LayoutDirection
     ) {
         this.transformOrigin = transformOrigin
         val wasClippingManually = renderNode.clipToOutline && outlineResolver.clipPath != null
@@ -102,7 +104,8 @@
             shape,
             renderNode.alpha,
             renderNode.clipToOutline,
-            renderNode.elevation
+            renderNode.elevation,
+            layoutDirection
         )
         renderNode.setOutline(outlineResolver.outline)
         val isClippingManually = renderNode.clipToOutline && outlineResolver.clipPath != null
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt
index b51c042..1844344 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.setFrom
 import androidx.compose.ui.node.OwnedLayer
+import androidx.compose.ui.unit.LayoutDirection
 import java.lang.reflect.Field
 import java.lang.reflect.Method
 
@@ -98,7 +99,8 @@
         cameraDistance: Float,
         transformOrigin: TransformOrigin,
         shape: Shape,
-        clip: Boolean
+        clip: Boolean,
+        layoutDirection: LayoutDirection
     ) {
         this.mTransformOrigin = transformOrigin
         this.scaleX = scaleX
@@ -121,7 +123,8 @@
             shape,
             this.alpha,
             this.clipToOutline,
-            this.elevation
+            this.elevation,
+            layoutDirection
         )
         updateOutlineResolver()
         val isClippingManually = manualClipPath != null
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt
index a2cbe83..b865bd9 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.kt
@@ -34,6 +34,7 @@
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.android.asCoroutineDispatcher
 import kotlinx.coroutines.launch
+import java.util.concurrent.atomic.AtomicReference
 import kotlin.coroutines.EmptyCoroutineContext
 
 /**
@@ -97,35 +98,58 @@
         val LifecycleAware: WindowRecomposerFactory = WindowRecomposerFactory { rootView ->
             rootView.createLifecycleAwareViewTreeRecomposer()
         }
-
-        /**
-         * A [WindowRecomposerFactory] that always returns references to the global,
-         * soon to be deprecated, application main thread-bound [Recomposer.current].
-         * The existence of this API/constant is only temporary while other Compose UI
-         * infrastructure migrates to [LifecycleAware] recomposers.
-         */
-        @Deprecated(
-            "Global Recomposer.current will be removed without replacement; prefer LifecycleScoped"
-        )
-        val Global: WindowRecomposerFactory = WindowRecomposerFactory {
-            Recomposer.current()
-        }
     }
 }
 
 @InternalComposeUiApi
 object WindowRecomposerPolicy {
-    // TODO: Change default to LifecycleScoped when code expecting Recomposer.current() migrates
-    @Suppress("DEPRECATION")
-    @Volatile
-    private var factory: WindowRecomposerFactory = WindowRecomposerFactory.Global
 
-    fun setWindowRecomposerFactory(factory: WindowRecomposerFactory) {
-        this.factory = factory
+    private val factory = AtomicReference<WindowRecomposerFactory>(
+        WindowRecomposerFactory.LifecycleAware
+    )
+
+    // Don't expose the actual AtomicReference as @PublishedApi; we might convert to atomicfu later
+    @PublishedApi
+    internal fun getAndSetFactory(
+        factory: WindowRecomposerFactory
+    ): WindowRecomposerFactory = this.factory.getAndSet(factory)
+
+    @PublishedApi
+    internal fun compareAndSetFactory(
+        expected: WindowRecomposerFactory,
+        factory: WindowRecomposerFactory
+    ): Boolean = this.factory.compareAndSet(expected, factory)
+
+    fun setFactory(factory: WindowRecomposerFactory) {
+        this.factory.set(factory)
+    }
+
+    inline fun <R> withFactory(
+        factory: WindowRecomposerFactory,
+        block: () -> R
+    ): R {
+        var cause: Throwable? = null
+        val oldFactory = getAndSetFactory(factory)
+        return try {
+            block()
+        } catch (t: Throwable) {
+            cause = t
+            throw t
+        } finally {
+            if (!compareAndSetFactory(factory, oldFactory)) {
+                val err = IllegalStateException(
+                    "WindowRecomposerFactory was set to unexpected value; cannot safely restore " +
+                        "old state"
+                )
+                if (cause == null) throw err
+                cause.addSuppressed(err)
+                throw cause
+            }
+        }
     }
 
     internal fun createAndInstallWindowRecomposer(rootView: View): Recomposer {
-        val newRecomposer = factory.createRecomposer(rootView)
+        val newRecomposer = factory.get().createRecomposer(rootView)
         rootView.setTag(R.id.androidx_compose_ui_view_composition_reference, newRecomposer)
 
         // If the Recomposer shuts down, unregister it so that a future request for a window
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
index 471e53b..5f4dcaf 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
@@ -15,12 +15,10 @@
  */
 package androidx.compose.ui.platform
 
-import android.app.Activity
 import android.os.Build
 import android.util.Log
 import android.view.View
 import android.view.ViewGroup
-import android.widget.FrameLayout
 import androidx.activity.ComponentActivity
 import androidx.annotation.MainThread
 import androidx.compose.runtime.Composable
@@ -47,69 +45,6 @@
 
 private val TAG = "Wrapper"
 
-/**
- * Composes the children of the view with the passed in [composable].
- *
- * @see setViewContent
- * @see Composition.dispose
- */
-// TODO: Remove this API when View/LayoutNode mixed trees work
-@OptIn(ExperimentalComposeApi::class)
-@Deprecated(
-    "setViewContent was deprecated - use setContent instead",
-    ReplaceWith(
-        "setContent(parent, composable)",
-        "androidx.compose.ui.platform.setContent"
-    )
-)
-fun ViewGroup.setViewContent(
-    parent: CompositionReference = Recomposer.current(),
-    composable: @Composable () -> Unit
-): Composition = compositionFor(
-    this,
-    UiApplier(this),
-    parent,
-    onCreated = {
-        removeAllViews()
-    }
-).apply {
-    setContent {
-        Providers(AmbientContext provides this@setViewContent.context) {
-            composable()
-        }
-    }
-}
-
-/**
- * Sets the contentView of an activity to a FrameLayout, and composes the contents of the layout
- * with the passed in [composable].
- *
- * @see setContent
- * @see Activity.setContentView
- */
-// TODO: Remove this API when View/LayoutNode mixed trees work
-@Deprecated(
-    "setViewContent was deprecated - use setContent instead",
-    ReplaceWith(
-        "setContent(composable)",
-        "androidx.compose.ui.platform.setContent"
-    )
-)
-fun Activity.setViewContent(composable: @Composable () -> Unit): Composition {
-    // TODO(lmr): add ambients here, or remove API entirely if we can
-    // If there is already a FrameLayout in the root, we assume we want to compose
-    // into it instead of create a new one. This allows for `setContent` to be
-    // called multiple times.
-    GlobalSnapshotManager.ensureStarted()
-    val root = window
-        .decorView
-        .findViewById<ViewGroup>(android.R.id.content)
-        .getChildAt(0) as? ViewGroup
-        ?: FrameLayout(this).also { setContentView(it) }
-    @Suppress("DEPRECATION")
-    return root.setViewContent(composable = composable)
-}
-
 // TODO(chuckj): This is a temporary work-around until subframes exist so that
 // nextFrame() inside recompose() doesn't really start a new frame, but a new subframe
 // instead.
@@ -137,19 +72,23 @@
  * @param content A `@Composable` function declaring the UI contents
  */
 fun ComponentActivity.setContent(
-    // Note: Recomposer.current() is the default here since all Activity view trees are hosted
-    // on the main thread.
-    parent: CompositionReference = Recomposer.current(),
+    parent: CompositionReference? = null,
     content: @Composable () -> Unit
-): Composition {
-    GlobalSnapshotManager.ensureStarted()
-    val composeView: AndroidComposeView = window.decorView
+) {
+    val existingComposeView = window.decorView
         .findViewById<ViewGroup>(android.R.id.content)
-        .getChildAt(0) as? AndroidComposeView
-        ?: AndroidComposeView(this).also {
-            setContentView(it.view, DefaultLayoutParams)
-        }
-    return doSetContent(composeView, parent, content)
+        .getChildAt(0) as? ComposeView
+
+    if (existingComposeView != null) with(existingComposeView) {
+        setParentCompositionReference(parent)
+        setContent(content)
+    } else ComposeView(this).apply {
+        // Set content and parent **before** setContentView
+        // to have ComposeView create the composition on attach
+        setParentCompositionReference(parent)
+        setContent(content)
+        setContentView(this, DefaultLayoutParams)
+    }
 }
 
 /**
@@ -165,9 +104,10 @@
  * @param parent The [Recomposer] or parent composition reference.
  * @param content Composable that will be the content of the view.
  */
+@Suppress("DEPRECATION")
 @Deprecated("Use ComposeView or AbstractComposeView instead.")
 fun ViewGroup.setContent(
-    parent: CompositionReference = Recomposer.current(),
+    parent: CompositionReference,
     content: @Composable () -> Unit
 ): Composition {
     GlobalSnapshotManager.ensureStarted()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
index 43ba66b..e383dc0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * A [Modifier.Element] that draws into the space of the layout.
@@ -49,11 +50,31 @@
      * to objects change. This method is guaranteed to be called before
      * [DrawModifier.draw].
      *
-     * @param size The current size of the drawing environment
-     * @param density The current screen density to provide the ability to convert between
-     * density independent and raw pixel values
+     * @param params The params to be used to build the cache.
      */
-    fun onBuildCache(size: Size, density: Density)
+    fun onBuildCache(params: BuildDrawCacheParams)
+}
+
+/**
+ * The set of parameters which could be used to build the drawing cache.
+ *
+ * @see DrawCacheModifier.onBuildCache
+ */
+interface BuildDrawCacheParams {
+    /**
+     * The current size of the drawing environment
+     */
+    val size: Size
+
+    /**
+     * The current layout direction.
+     */
+    val layoutDirection: LayoutDirection
+
+    /**
+     * The current screen density to provide the ability to convert between
+     */
+    val density: Density
 }
 
 /**
@@ -116,16 +137,19 @@
  * [onDrawBehind] will draw behind the layout's drawing contents however, [onDrawWithContent] will
  * provide the ability to draw before or after the layout's contents
  */
-class CacheDrawScope internal constructor(
-    internal var cachedDrawDensity: Density? = null
-) : Density {
+class CacheDrawScope internal constructor() : Density {
+    internal var cacheParams: BuildDrawCacheParams = EmptyBuildDrawCacheParams
     internal var drawResult: DrawResult? = null
 
     /**
      * Provides the dimensions of the current drawing environment
      */
-    var size: Size = Size.Unspecified
-        internal set
+    val size: Size get() = cacheParams.size
+
+    /**
+     * Provides the [LayoutDirection].
+     */
+    val layoutDirection: LayoutDirection get() = cacheParams.layoutDirection
 
     /**
      * Issue drawing commands to be executed before the layout content is drawn
@@ -143,10 +167,16 @@
     }
 
     override val density: Float
-        get() = cachedDrawDensity!!.density
+        get() = cacheParams.density.density
 
     override val fontScale: Float
-        get() = cachedDrawDensity!!.density
+        get() = cacheParams.density.fontScale
+}
+
+private object EmptyBuildDrawCacheParams : BuildDrawCacheParams {
+    override val size: Size = Size.Unspecified
+    override val layoutDirection: LayoutDirection = LayoutDirection.Ltr
+    override val density: Density = Density(1f, 1f)
 }
 
 /**
@@ -158,10 +188,9 @@
     val onBuildDrawCache: CacheDrawScope.() -> DrawResult
 ) : DrawCacheModifier {
 
-    override fun onBuildCache(size: Size, density: Density) {
+    override fun onBuildCache(params: BuildDrawCacheParams) {
         cacheDrawScope.apply {
-            cachedDrawDensity = density
-            this.size = size
+            cacheParams = params
             drawResult = null
             onBuildDrawCache()
             checkNotNull(drawResult) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index 2182f5c..0f10d1c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -263,7 +263,8 @@
                 cameraDistance = graphicsLayerScope.cameraDistance,
                 transformOrigin = graphicsLayerScope.transformOrigin,
                 shape = graphicsLayerScope.shape,
-                clip = graphicsLayerScope.clip
+                clip = graphicsLayerScope.clip,
+                layoutDirection = layoutNode.layoutDirection
             )
             isClipping = graphicsLayerScope.clip
         } else {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
index 94ac6c4..3f58143 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
@@ -16,11 +16,13 @@
 
 package androidx.compose.ui.node
 
+import androidx.compose.ui.draw.BuildDrawCacheParams
 import androidx.compose.ui.draw.DrawCacheModifier
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.toSize
 
 internal class ModifiedDrawNode(
@@ -30,19 +32,24 @@
 
     private var cacheDrawModifier: DrawCacheModifier? = updateCacheDrawModifier()
 
-    // b/173669932 we should not cache this here, however, on subsequent modifier updates
-    // the density provided via layoutNode.density becomes 1
-    private val density = layoutNode.density
+    private val buildCacheParams: BuildDrawCacheParams = object : BuildDrawCacheParams {
+        // b/173669932 we should not cache this here, however, on subsequent modifier updates
+        // the density provided via layoutNode.density becomes 1
+        override val density = layoutNode.density
+
+        override val layoutDirection: LayoutDirection get() = layoutNode.layoutDirection
+
+        override val size: Size get() = measuredSize.toSize()
+    }
 
     // Flag to determine if the cache should be re-built
     private var invalidateCache = true
 
     // Callback used to build the drawing cache
     private val updateCache = {
-        val size: Size = measuredSize.toSize()
         // b/173669932 figure out why layoutNode.mDrawScope density is 1 after observation updates
         // and use that here instead of the cached density we get in the constructor
-        cacheDrawModifier?.onBuildCache(size, density)
+        cacheDrawModifier?.onBuildCache(buildCacheParams)
         invalidateCache = false
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
index 0de27b2..dcc372f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * A layer returned by [Owner.createLayer] to separate drawn content.
@@ -49,7 +50,8 @@
         cameraDistance: Float,
         transformOrigin: TransformOrigin,
         shape: Shape,
-        clip: Boolean
+        clip: Boolean,
+        layoutDirection: LayoutDirection
     )
 
     /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index 7c88a80..9406733 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -405,7 +405,12 @@
      * for accessibility: [SemanticsProperties.Disabled], [SemanticsProperties.StateDescription],
      * [SemanticsActions.OnClick]
      */
-    Tab
+    Tab,
+    /**
+     * This element is an image. Associated semantics properties for accessibility:
+     * [SemanticsProperties.ContentDescription]
+     */
+    Image
 }
 
 /**
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
index 4812d12..55eacec 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
@@ -101,7 +101,7 @@
         yield()
 
         // we can't stuck in infinite loop (because of double dispatching in FrameManager.schedule)
-        while (Recomposer.current().hasInvalidations()) {
+        while (Recomposer.runningRecomposers.value.any { it.hasPendingWork }) {
             yield()
         }
     }
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/OutlineCache.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/OutlineCache.kt
index a724377..2b742dc 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/OutlineCache.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/OutlineCache.kt
@@ -19,6 +19,7 @@
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.toSize
 
@@ -30,7 +31,8 @@
 internal class OutlineCache(
     density: Density,
     size: IntSize,
-    shape: Shape
+    shape: Shape,
+    layoutDirection: LayoutDirection
 ) {
     var density = density
         set(value) {
@@ -56,13 +58,21 @@
             }
         }
 
+    var layoutDirection = layoutDirection
+        set(value) {
+            if (value != field) {
+                field = value
+                update()
+            }
+        }
+
     var outline: Outline? = null
         private set
 
     private fun update() {
         outline = if (size != IntSize.Zero) {
             val floatSize = size.toSize()
-            shape.createOutline(floatSize, density)
+            shape.createOutline(floatSize, layoutDirection, density)
         } else {
             null
         }
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
index d6e7a67..d817830 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.graphics.toSkijaRect
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.node.OwnedLayer
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -49,7 +50,8 @@
 ) : OwnedLayer {
     private var size = IntSize.Zero
     private var position = IntOffset.Zero
-    private var outlineCache = OutlineCache(owner.density, size, RectangleShape)
+    private var outlineCache =
+        OutlineCache(owner.density, size, RectangleShape, LayoutDirection.Ltr)
     private val matrix = Matrix()
     private val pictureRecorder = PictureRecorder()
     private var picture: Picture? = null
@@ -108,7 +110,8 @@
         cameraDistance: Float,
         transformOrigin: TransformOrigin,
         shape: Shape,
-        clip: Boolean
+        clip: Boolean,
+        layoutDirection: LayoutDirection
     ) {
         this.transformOrigin = transformOrigin
         this.translationX = translationX
@@ -122,6 +125,7 @@
         this.clip = clip
         this.shadowElevation = shadowElevation
         outlineCache.shape = shape
+        outlineCache.layoutDirection = layoutDirection
         updateMatrix()
         invalidate()
     }
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
index 43ca949c..8085f8e 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
@@ -18,12 +18,38 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
 import androidx.compose.runtime.CompositionReference
+import androidx.compose.runtime.EmbeddingContext
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.Providers
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.compositionFor
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.node.LayoutNode
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.launch
+
+// TODO: Replace usages with an appropriately scoped implementation
+// Below is a local copy of the old Recomposer.current() implementation.
+@OptIn(ExperimentalCoroutinesApi::class)
+private val GlobalDefaultRecomposer = run {
+    val embeddingContext = EmbeddingContext()
+    val mainScope = CoroutineScope(
+        NonCancellable + embeddingContext.mainThreadCompositionContext()
+    )
+
+    Recomposer(mainScope.coroutineContext).also {
+        // NOTE: Launching undispatched so that compositions created with the
+        // singleton instance can assume the recomposer is running
+        // when they perform initial composition. The relevant Recomposer code is
+        // appropriately thread-safe for this.
+        mainScope.launch(start = CoroutineStart.UNDISPATCHED) {
+            it.runRecomposeAndApplyChanges()
+        }
+    }
+}
 
 /**
  * Composes the given composable into [DesktopOwner]
@@ -39,7 +65,11 @@
 ): Composition {
     GlobalSnapshotManager.ensureStarted()
 
-    val composition = compositionFor(root, DesktopUiApplier(root), parent ?: Recomposer.current())
+    val composition = compositionFor(
+        root,
+        DesktopUiApplier(root),
+        parent ?: GlobalDefaultRecomposer
+    )
     composition.setContent {
         ProvideDesktopAmbients(this) {
             DesktopSelectionContainer(content)
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
index e697605..637863c 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.round
 import org.junit.Assert.assertEquals
 import org.junit.Rule
@@ -336,7 +337,7 @@
     ) {
         updateLayerProperties(
             scaleX, scaleY, alpha, translationX, translationY, shadowElevation, rotationX,
-            rotationY, rotationZ, cameraDistance, transformOrigin, shape, clip
+            rotationY, rotationZ, cameraDistance, transformOrigin, shape, clip, LayoutDirection.Ltr
         )
     }
 }
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/graphics/GraphicsLayerScopeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/graphics/GraphicsLayerScopeTest.kt
index b90aba7..370030a 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/graphics/GraphicsLayerScopeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/graphics/GraphicsLayerScopeTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.toRect
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertEquals
@@ -49,8 +50,11 @@
         scope.cameraDistance = 5f
         scope.transformOrigin = TransformOrigin(0.7f, 0.1f)
         scope.shape = object : Shape {
-            override fun createOutline(size: Size, density: Density) =
-                Outline.Rectangle(size.toRect())
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ) = Outline.Rectangle(size.toRect())
         }
         scope.clip = true
         scope.reset()
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index 2aeab96..885e916 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -1795,7 +1795,8 @@
                 cameraDistance: Float,
                 transformOrigin: TransformOrigin,
                 shape: Shape,
-                clip: Boolean
+                clip: Boolean,
+                layoutDirection: LayoutDirection
             ) {
             }
 
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
index aa39f9d..b346fc8 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
@@ -30,6 +30,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Assume
@@ -301,15 +302,16 @@
         val child = scenario.withActivity { findViewById(R.id.view) }
         var parentListenerCalled = false
         var insetsAnimationCallbackCalled = false
-        var childListenerCalled = false
+        var childListenerCalledCount = 0
         var savedInsets: WindowInsetsCompat? = null
         var savedView: View? = null
-        val latch = CountDownLatch(3) // Insets will be dispatched 3 times
+        var applyInsetsLatch = CountDownLatch(2) // Insets will be dispatched 3 times
+        var onPrepareLatch = CountDownLatch(2)
         val childLatch = CountDownLatch(1)
         val animationCallback = createCallback(
             onPrepare = {
                 insetsAnimationCallbackCalled = true
-                latch.countDown()
+                onPrepareLatch.countDown()
             },
 
             onEnd = {
@@ -319,25 +321,26 @@
 
         val childCallback = createCallback(
             onEnd = {
-                childListenerCalled = true
+                ++childListenerCalledCount
                 childLatch.countDown()
             }
         )
 
-        val insetListener: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
+        // First we check that when the parent consume the insets, the child listener is not called
+        val consumingListener: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
             { v, insetsCompat ->
                 parentListenerCalled = true
                 savedInsets = insetsCompat
                 savedView = v
-                latch.countDown()
+                applyInsetsLatch.countDown()
                 WindowInsetsCompat.CONSUMED
             }
 
         ViewCompat.setWindowInsetsAnimationCallback(container, animationCallback)
         ViewCompat.setWindowInsetsAnimationCallback(child, childCallback)
-        ViewCompat.setOnApplyWindowInsetsListener(container, insetListener)
+        ViewCompat.setOnApplyWindowInsetsListener(container, consumingListener)
         triggerInsetAnimation(container)
-        latch.await(4, TimeUnit.SECONDS)
+        applyInsetsLatch.await(4, TimeUnit.SECONDS)
         assertTrue(
             "The WindowInsetsAnimationCallback has not been called",
             insetsAnimationCallbackCalled
@@ -347,12 +350,25 @@
             parentListenerCalled
         )
         // Parent consumed the insets, child listener won't be called
-        assertFalse(
-            "child listener should not have been called",
-            childListenerCalled
-        )
+        assertEquals("child listener should not have been called", 0, childListenerCalledCount)
 
-        // We dispatch with the same insets as before
+        // Then we do the same but without consuming the insets in the parent, so the child
+        // listener should be called.
+        resetBars(container)
+        applyInsetsLatch = CountDownLatch(2)
+        onPrepareLatch = CountDownLatch(2)
+        val nonConsumingListener: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
+            { v, insetsCompat ->
+                parentListenerCalled = true
+                savedInsets = insetsCompat
+                savedView = v
+                applyInsetsLatch.countDown()
+                insetsCompat
+            }
+
+        ViewCompat.setOnApplyWindowInsetsListener(container, nonConsumingListener)
+        triggerInsetAnimation(container)
+        applyInsetsLatch.await(4, TimeUnit.SECONDS)
         childLatch.await(4, TimeUnit.SECONDS)
         assertTrue(
             "The WindowInsetsAnimationCallback has not been called",
@@ -362,9 +378,12 @@
             "parent listener has not been called",
             parentListenerCalled
         )
-        assertTrue(
-            "child listener has not been called",
-            childListenerCalled
+        // Parent consumed the insets, child listener won't be called
+        assertEquals(
+            "child listener should have been called 1 time but was called " +
+                "$childListenerCalledCount times",
+            1,
+            childListenerCalledCount
         )
     }
 
diff --git a/core/core/src/main/java/androidx/core/view/ViewCompat.java b/core/core/src/main/java/androidx/core/view/ViewCompat.java
index 102d228..d9f2882 100644
--- a/core/core/src/main/java/androidx/core/view/ViewCompat.java
+++ b/core/core/src/main/java/androidx/core/view/ViewCompat.java
@@ -4756,8 +4756,8 @@
 
                         if (compatInsets.equals(mLastInsets)) {
                             // We got the same insets we just return the previously computed insets.
-                            listener.onApplyWindowInsets(view, compatInsets);
-                            return insets;
+                            return listener.onApplyWindowInsets(view, compatInsets)
+                                    .toWindowInsets();
                         }
                     }
                     mLastInsets = compatInsets;
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 e2f02e1..111571a 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
@@ -16,20 +16,15 @@
 package androidx.datastore.core
 
 import androidx.datastore.core.handlers.NoOpCorruptionHandler
-import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.channels.ConflatedBroadcastChannel
-import kotlinx.coroutines.channels.actor
 import kotlinx.coroutines.completeWith
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.dropWhile
 import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.sync.Mutex
@@ -42,11 +37,20 @@
 import java.io.IOException
 import java.io.OutputStream
 import java.lang.IllegalStateException
-import java.util.concurrent.atomic.AtomicReference
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.coroutineContext
 
-private class DataAndHash<T>(val value: T, val hashCode: Int) {
+/**
+ * Represents the current state of the DataStore.
+ */
+private sealed class State<T>
+
+private object UnInitialized : State<Any>()
+
+/**
+ * A read from disk has succeeded, value represents the current on disk state.
+ */
+private class Data<T>(val value: T, val hashCode: Int) : State<T>() {
     fun checkHashCode() {
         check(value.hashCode() == hashCode) {
             "Data in DataStore was mutated but DataStore is only compatible with Immutable types."
@@ -55,9 +59,18 @@
 }
 
 /**
+ * A read from disk has failed. ReadException is the exception that was thrown.
+ */
+private class ReadException<T>(val readException: Throwable) : State<T>()
+
+/**
+ * The scope has been cancelled. This DataStore cannot process any new reads or writes.
+ */
+private class Final<T>(val finalException: Throwable) : State<T>()
+
+/**
  * Single process implementation of DataStore. This is NOT multi-process safe.
  */
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
 internal class SingleProcessDataStore<T>(
     private val produceFile: () -> File,
     private val serializer: Serializer<T>,
@@ -74,24 +87,72 @@
 ) : DataStore<T> {
 
     override val data: Flow<T> = flow {
-        val curChannel = downstreamChannel()
-        actor.offer(Message.Read(curChannel))
-        emitAll(curChannel.asFlow().map { it.value })
+        /**
+         * If downstream flow is UnInitialized, no data has been read yet, we need to trigger a new
+         * read then start emitting values once we have seen a new value (or exception).
+         *
+         * If downstream flow has a ReadException, there was an exception last time we tried to read
+         * data. We need to trigger a new read then start emitting values once we have seen a new
+         * value (or exception).
+         *
+         * If downstream flow has Data, we should just start emitting from downstream flow.
+         *
+         * If Downstream flow is Final, the scope has been cancelled so the data store is no
+         * longer usable. We should just propagate this exception.
+         *
+         * State always starts at null. null can transition to ReadException, Data or
+         * Final. ReadException can transition to another ReadException, Data or Final.
+         * Data can transition to another Data or Final. Final will not change.
+         */
+
+        val currentDownStreamFlowState = downstreamFlow.value
+
+        if (currentDownStreamFlowState !is Data) {
+            // We need to send a read request because we don't have data yet.
+            actor.offer(Message.Read(currentDownStreamFlowState))
+        }
+
+        emitAll(
+            downstreamFlow.dropWhile {
+                if (currentDownStreamFlowState is Data<T> ||
+                    currentDownStreamFlowState is Final<T>
+                ) {
+                    // We don't need to drop any Data or Final values.
+                    false
+                } else {
+                    // we need to drop the last seen state since it was either an exception or
+                    // wasn't yet initialized. Since we sent a message to actor, we *will* see a
+                    // new value.
+                    it === currentDownStreamFlowState
+                }
+            }.map {
+                when (it) {
+                    is ReadException<T> -> throw it.readException
+                    is Final<T> -> throw it.finalException
+                    is Data<T> -> it.value
+                    is UnInitialized -> error(
+                        "This is a bug in DataStore. Please file a bug at: " +
+                            "https://issuetracker.google.com/issues/new?" +
+                            "component=907884&template=1466542"
+                    )
+                }
+            }
+        )
     }
 
     override suspend fun updateData(transform: suspend (t: T) -> T): T {
+        /**
+         * The states here are the same as the states for reads. Additionally we send an ack that
+         * the actor must respond to as long as it is active.
+         */
         val ack = CompletableDeferred<T>()
-        val dataChannel = downstreamChannel()
-        val updateMsg = Message.Update<T>(transform, ack, dataChannel, coroutineContext)
+        val currentDownStreamFlowState = downstreamFlow.value
+
+        val updateMsg =
+            Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)
 
         actor.offer(updateMsg)
 
-        // If no read has succeeded yet, we need to wait on the result of the next read so we can
-        // bubble exceptions up to the caller. Read exceptions are not bubbled up through ack.
-        if (dataChannel.valueOrNull == null) {
-            dataChannel.asFlow().first()
-        }
-
         // Wait with same scope as the actor, so we're not waiting on a cancelled actor.
         return withContext(scope.coroutineContext) { ack.await() }
     }
@@ -100,29 +161,22 @@
 
     private val file: File by lazy { produceFile() }
 
-    /**
-     * The external facing channel. The data flow emits the values from this channel.
-     *
-     * Once the read has completed successfully, downStreamChannel.get().value is the same as the
-     * current on disk data. If the read fails, downStreamChannel will be closed with that cause,
-     * and a new instance will be set in its place.
-     */
-    private val downstreamChannel: AtomicReference<ConflatedBroadcastChannel<DataAndHash<T>>> =
-        AtomicReference(ConflatedBroadcastChannel())
+    @Suppress("UNCHECKED_CAST")
+    private val downstreamFlow = MutableStateFlow(UnInitialized as State<T>)
 
     private var initTasks: List<suspend (api: InitializerApi<T>) -> Unit>? =
         initTasksList.toList()
 
     /** The actions for the actor. */
     private sealed class Message<T> {
-        abstract val dataChannel: ConflatedBroadcastChannel<DataAndHash<T>>
+        abstract val lastState: State<T>?
 
         /**
          * Represents a read operation. If the data is already cached, this is a no-op. If data
          * has not been cached, it triggers a new read to the specified dataChannel.
          */
         class Read<T>(
-            override val dataChannel: ConflatedBroadcastChannel<DataAndHash<T>>
+            override val lastState: State<T>?
         ) : Message<T>()
 
         /** Represents an update operation. */
@@ -132,61 +186,104 @@
              * Used to signal (un)successful completion of the update to the caller.
              */
             val ack: CompletableDeferred<T>,
-            override val dataChannel: ConflatedBroadcastChannel<DataAndHash<T>>,
+            override val lastState: State<T>?,
             val callerContext: CoroutineContext
         ) : Message<T>()
     }
 
-    /**
-     * Consumes messages. All state changes should happen within actor.
-     */
     private val actor = SimpleActor<Message<T>>(
         scope = scope,
         onComplete = {
-            // The scope has been cancelled. Cancel downstream in case there are any collectors
-            // still active.
-            if (it is CancellationException) {
-                downstreamChannel().cancel(it)
-            } else if (it is Throwable) {
-                downstreamChannel().close(it)
+            it?.let {
+                downstreamFlow.value = Final(it)
             }
+            // We expect it to always be non-null but we will leave the alternative as a no-op
+            // just in case.
         }
     ) { msg ->
-        if (msg.dataChannel.isClosedForSend) {
-            // The message was sent with an old, now closed, dataChannel. This means that
-            // our read failed.
-            return@SimpleActor
+        when (msg) {
+            is Message.Read -> {
+                handleRead(msg)
+            }
+            is Message.Update -> {
+                handleUpdate(msg)
+            }
         }
+    }
 
-        try {
-            readAndInitOnce(msg.dataChannel)
-        } catch (ex: Throwable) {
-            resetDataChannel(ex)
-            return@SimpleActor
-        }
-
-        // We have successfully read data and sent it to downstreamChannel.
-
-        if (msg is Message.Update) {
-            msg.ack.completeWith(
-                runCatching {
-                    transformAndWrite(msg.transform, downstreamChannel(), msg.callerContext)
+    private suspend fun handleRead(read: Message.Read<T>) {
+        when (val currentState = downstreamFlow.value) {
+            is Data -> {
+                // We already have data so just return...
+            }
+            is ReadException -> {
+                if (currentState === read.lastState) {
+                    readAndInitOrPropagateFailure()
                 }
-            )
+
+                // Someone else beat us but also failed. The collector has already
+                // been signalled so we don't need to do anything.
+            }
+            UnInitialized -> {
+                readAndInitOrPropagateFailure()
+            }
+            is Final -> error("Can't read in final state.") // won't happen
         }
     }
 
-    private fun resetDataChannel(ex: Throwable) {
-        val failedDataChannel = downstreamChannel.getAndSet(ConflatedBroadcastChannel())
+    private suspend fun handleUpdate(update: Message.Update<T>) {
+        // All branches of this *must* complete ack either successfully or exceptionally.
+        // We must *not* throw an exception, just propagate it to the ack.
+        update.ack.completeWith(
+            runCatching {
 
-        failedDataChannel.close(ex)
+                when (val currentState = downstreamFlow.value) {
+                    is Data -> {
+                        // We are already initialized, we just need to perform the update
+                        transformAndWrite(update.transform, update.callerContext)
+                    }
+                    is ReadException, is UnInitialized -> {
+                        if (currentState === update.lastState) {
+                            // we need to try to read again
+                            readAndInitOrPropagateAndThrowFailure()
+
+                            // We've successfully read, now we need to perform the update
+                            transformAndWrite(update.transform, update.callerContext)
+                        } else {
+                            // Someone else beat us to read but also failed. We just need to
+                            // signal the writer that is waiting on ack.
+                            // This cast is safe because we can't be in the UnInitialized
+                            // state if the state has changed.
+                            throw (currentState as ReadException).readException
+                        }
+                    }
+
+                    is Final -> throw currentState.finalException // won't happen
+                }
+            }
+        )
     }
 
-    private suspend fun readAndInitOnce(dataChannel: ConflatedBroadcastChannel<DataAndHash<T>>) {
-        if (dataChannel.valueOrNull != null) {
-            // If we already have cached data, we don't try to read it again.
-            return
+    private suspend fun readAndInitOrPropagateAndThrowFailure() {
+        try {
+            readAndInit()
+        } catch (throwable: Throwable) {
+            downstreamFlow.value = ReadException(throwable)
+            throw throwable
         }
+    }
+
+    private suspend fun readAndInitOrPropagateFailure() {
+        try {
+            readAndInit()
+        } catch (throwable: Throwable) {
+            downstreamFlow.value = ReadException(throwable)
+        }
+    }
+
+    private suspend fun readAndInit() {
+        // This should only be called if we don't already have cached data.
+        check(downstreamFlow.value == UnInitialized || downstreamFlow.value is ReadException)
 
         val updateLock = Mutex()
         var initData = readDataOrHandleCorruption()
@@ -221,7 +318,7 @@
             initializationComplete = true
         }
 
-        dataChannel.offer(DataAndHash(initData, initData.hashCode()))
+        downstreamFlow.value = Data(initData, initData.hashCode())
     }
 
     private suspend fun readDataOrHandleCorruption(): T {
@@ -258,19 +355,16 @@
         }
     }
 
+    // downstreamFlow.value must be successfully set to data before calling this
     private suspend fun transformAndWrite(
         transform: suspend (t: T) -> T,
-        /**
-         * This is the channel that contains the data that will be used for the transformation.
-         * It *must* already have a value -- otherwise this will throw IllegalStateException.
-         * Once the transformation is completed and data is durably persisted to disk, and the new
-         * value will be offered to this channel.
-         */
-        updateDataChannel: ConflatedBroadcastChannel<DataAndHash<T>>,
         callerContext: CoroutineContext
     ): T {
-        val curDataAndHash = updateDataChannel.value
+        // value is not null or an exception because we must have the value set by now so this cast
+        // is safe.
+        val curDataAndHash = downstreamFlow.value as Data<T>
         curDataAndHash.checkHashCode()
+
         val curData = curDataAndHash.value
         val newData = withContext(callerContext) { transform(curData) }
 
@@ -281,7 +375,7 @@
             curData
         } else {
             writeData(newData)
-            updateDataChannel.offer(DataAndHash(newData, newData.hashCode()))
+            downstreamFlow.value = Data(newData, newData.hashCode())
             newData
         }
     }
@@ -353,10 +447,4 @@
             fileOutputStream.flush()
         }
     }
-
-    // Convenience function:
-    @Suppress("NOTHING_TO_INLINE")
-    private inline fun downstreamChannel(): ConflatedBroadcastChannel<DataAndHash<T>> {
-        return downstreamChannel.get()
-    }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..dba9c3c
--- /dev/null
+++ b/datastore/datastore-core/src/test/java/androidx/datastore/core/SingleProcessDataStoreStressTest.kt
@@ -0,0 +1,228 @@
+/*
+ * 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.datastore.core
+
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.flow.reduce
+import kotlinx.coroutines.flow.retry
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.concurrent.Executors
+
+@RunWith(JUnit4::class)
+class SingleProcessDataStoreStressTest {
+    @get:Rule
+    val tempFolder = TemporaryFolder()
+
+    @Test
+    fun testManyConcurrentReadsAndWrites() = runBlocking<Unit> {
+        val myScope = CoroutineScope(
+            Job() + Executors.newFixedThreadPool(4).asCoroutineDispatcher()
+        )
+
+        val file = tempFolder.newFile()
+        file.delete()
+
+        val dataStore = DataStoreFactory.create(
+            serializer = LongSerializer(failWrites = false, failReads = false),
+            scope = myScope
+        ) { file }
+
+        val readers = mutableListOf<Deferred<*>>()
+        val writers = mutableListOf<Deferred<*>>()
+
+        repeat(100) {
+            readers += myScope.async {
+                dataStore.data.takeWhile {
+                    it != 100_000L
+                }.reduce { acc, value ->
+                    assertThat(acc).isLessThan(value)
+                    value
+                }
+            }
+        }
+
+        repeat(1000) {
+            writers += myScope.async {
+                repeat(100) {
+                    dataStore.updateData {
+                        it.inc()
+                    }
+                }
+            }
+        }
+
+        readers.awaitAll()
+        writers.awaitAll()
+    }
+
+    @Test
+    fun testManyConcurrentReadsAndWrites_withIntermittentWriteFailures() = runBlocking<Unit> {
+        val myScope = CoroutineScope(
+            Job() + Executors.newFixedThreadPool(4).asCoroutineDispatcher()
+        )
+
+        val file = tempFolder.newFile()
+        file.delete()
+
+        val serializer = LongSerializer(false, false)
+
+        val dataStore = DataStoreFactory.create(
+            serializer = serializer,
+            scope = myScope
+        ) { file }
+
+        val readers = mutableListOf<Deferred<*>>()
+        val writers = mutableListOf<Deferred<*>>()
+
+        repeat(100) {
+            readers += myScope.async {
+                dataStore.data.takeWhile {
+                    it != 100_000L
+                }.reduce { acc, value ->
+                    assertThat(acc).isLessThan(value)
+                    value
+                }
+            }
+        }
+
+        repeat(1000) {
+            writers += myScope.async {
+                repeat(100) {
+                    var success = false
+                    while (!success) {
+                        try {
+                            dataStore.updateData { it.inc() }
+                            success = true
+                        } catch (expected: IOException) {
+                        }
+                    }
+                }
+            }
+        }
+
+        val intermittentWriteFailures = myScope.launch {
+            while (true) {
+                ensureActive()
+                delay(10)
+                serializer.failWrites = !serializer.failWrites
+            }
+        }
+
+        readers.awaitAll()
+        writers.awaitAll()
+
+        intermittentWriteFailures.cancelAndJoin()
+    }
+
+    @Test
+    fun testManyConcurrentReadsAndWrites_withBeginningReadFailures() = runBlocking<Unit> {
+        val myScope = CoroutineScope(
+            Job() + Executors.newFixedThreadPool(4).asCoroutineDispatcher()
+        )
+
+        val file = tempFolder.newFile()
+        file.delete()
+
+        val serializer = LongSerializer(failWrites = false, failReads = true)
+
+        val dataStore = DataStoreFactory.create(
+            serializer = serializer,
+            scope = myScope
+        ) { file }
+
+        val readers = mutableListOf<Deferred<*>>()
+        val writers = mutableListOf<Deferred<*>>()
+
+        repeat(100) {
+            readers += myScope.async {
+                dataStore.data
+                    .retry { true }
+                    .takeWhile {
+                        it != 1_000L
+                    }.reduce { acc, value ->
+                        assertThat(acc).isLessThan(value)
+                        value
+                    }
+            }
+        }
+
+        repeat(100) {
+            writers += myScope.async {
+                repeat(10) {
+                    var success = false
+                    while (!success) {
+                        try {
+                            dataStore.updateData { it.inc() }
+                            success = true
+                        } catch (expected: IOException) {
+                        }
+                    }
+                }
+            }
+        }
+
+        // Read failures for first 100 ms
+        delay(100)
+        serializer.failReads = false
+
+        readers.awaitAll()
+        writers.awaitAll()
+    }
+
+    private class LongSerializer(
+        @Volatile var failWrites: Boolean,
+        @Volatile var failReads: Boolean
+    ) :
+        Serializer<Long> {
+        override val defaultValue = 0L
+
+        override fun readFrom(input: InputStream): Long {
+            if (failReads) {
+                throw IOException("failing read")
+            }
+            return DataInputStream(input).readLong()
+        }
+
+        override fun writeTo(t: Long, output: OutputStream) {
+            if (failWrites) {
+                throw IOException("failing write")
+            }
+            DataOutputStream(output).writeLong(t)
+        }
+    }
+}
\ No newline at end of file
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 ac1dca1..40d3be1 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
@@ -22,6 +22,7 @@
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.async
@@ -56,10 +57,7 @@
 import kotlin.coroutines.AbstractCoroutineContextElement
 import kotlin.coroutines.CoroutineContext
 
-@kotlinx.coroutines.ExperimentalCoroutinesApi
-@kotlinx.coroutines.InternalCoroutinesApi
-@kotlinx.coroutines.ObsoleteCoroutinesApi
-@kotlinx.coroutines.FlowPreview
+@ExperimentalCoroutinesApi
 @RunWith(JUnit4::class)
 class SingleProcessDataStoreTest {
     @get:Rule
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 398996c..f88df31 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -624,7 +624,7 @@
 Some warnings are typically a sign of using an outdated Java toolchain\. To fix, recompile the source with an updated toolchain\.
 Type information in locals\-table is inconsistent\. Cannot constrain type: BOTTOM \(empty\) for value: v[0-9]+ by constraint FLOAT\.
 java\.lang\.Object androidx\.compose\.foundation\.gestures\.DragGestureDetectorKt\.awaitHorizontalTouchSlopOrCancellation\-qFc19kk\(androidx\.compose\.ui\.input\.pointer\.AwaitPointerEventScope, long, kotlin\.jvm\.functions\.Function[0-9]+, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.MultitouchGestureDetectorKt\$detectMultitouchGestures\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
+java\.lang\.Object androidx\.compose\.foundation\.gestures\.TransformGestureDetectorKt\$detectTransformGestures\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 Attempt to define local of type int as it\$iv:java\.lang\.Object
 Type information in locals\-table is inconsistent\. Cannot constrain type: INT for value: v379\(index\$iv\$iv\) by constraint FLOAT\.
 java\.lang\.Object androidx\.compose\.foundation\.gestures\.TapGestureDetectorKt\.waitForUpOrCancellation\(androidx\.compose\.ui\.input\.pointer\.AwaitPointerEventScope, kotlin\.coroutines\.Continuation\)
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.java b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.java
index 1cd0e0b..0cc817b 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.java
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.java
@@ -122,6 +122,9 @@
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
     }
 
+    // Adding @RequiresApi(30) would prevent unbundled implementations from offering this
+    // functionality to lower API levels.
+    @SuppressWarnings("UnsafeNewApiCall")
     @Override
     public void execPerConnectionSQL(@NonNull String sql, @Nullable Object[] bindArgs) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {