Merge "Text  doesn't crash when text is very very tall" into androidx-main
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.jvm.kt
old mode 100755
new mode 100644
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.jvm.kt
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java
index 88d96ad..635428a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java
@@ -19,6 +19,7 @@
 import static androidx.camera.core.impl.utils.TransformUtils.within360;
 
 import android.os.Build;
+import android.view.Surface;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
@@ -61,6 +62,11 @@
     }
 
     @Override
+    public int getSensorRotationDegrees() {
+        return getSensorRotationDegrees(Surface.ROTATION_0);
+    }
+
+    @Override
     public int getSensorRotationDegrees(@ImageOutputConfig.RotationValue int relativeRotation) {
         // The child UseCase calls this method to get the remaining rotation degrees, which is the
         // original rotation minus the rotation applied by the virtual camera.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt
index 65446d4..1b2a88e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt
@@ -80,6 +80,8 @@
         assertThat(parentCamera.cameraInfoInternal.getSensorRotationDegrees(Surface.ROTATION_0))
             .isEqualTo(90)
         virtualCamera.setRotationDegrees(180)
+        assertThat(virtualCamera.cameraInfoInternal.getSensorRotationDegrees())
+            .isEqualTo(270)
         assertThat(virtualCamera.cameraInfoInternal.getSensorRotationDegrees(Surface.ROTATION_0))
             .isEqualTo(270)
     }
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java b/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java
new file mode 100644
index 0000000..1888109
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.viewfinder.core;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Detects scaling transformation gestures that interprets zooming events using the supplied
+ * {@link MotionEvent}s.
+ *
+ * <p>The {@link OnZoomGestureListener} callback will notify users when a particular
+ * gesture event has occurred.
+ *
+ * <p>This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * <p>To use this class:
+ * <ul>
+ *  <li>Create an instance of the {@code ZoomGestureDetector} for your
+ *      {@link View}
+ *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ *          callback will be executed when the events occur.
+ * </ul>
+ */
+// TODO(b/314701735): update the documentation with examples using camera classes.
+// TODO(b/314701401): convert to kotlin implementation.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ZoomGestureDetector {
+    private static final String TAG = "ZoomGestureDetector";
+    // The default minimum span that the detector interprets a zooming event with. It's set to 0
+    // to give the most responsiveness.
+    // TODO(b/314702145): define a different span if appropriate.
+    private static final int DEFAULT_MIN_SPAN = 0;
+
+    /**
+     * The listener for receiving notifications when gestures occur.
+     * If you want to listen for all the different gestures then implement
+     * this interface.
+     *
+     * <p>An application will receive events in the following order:
+     * <ul>
+     *  <li>One {@link OnZoomGestureListener#onZoomBegin(ZoomGestureDetector)}
+     *  <li>Zero or more {@link OnZoomGestureListener#onZoom(ZoomGestureDetector)}
+     *  <li>One {@link OnZoomGestureListener#onZoomEnd(ZoomGestureDetector)}
+     * </ul>
+     */
+    public interface OnZoomGestureListener {
+        /**
+         * Responds to zooming events for a gesture in progress.
+         * Reported by pointer motion.
+         *
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         * @return Whether or not the detector should consider this event
+         *          as handled. If an event was not handled, the detector
+         *          will continue to accumulate movement until an event is
+         *          handled. This can be useful if an application, for example,
+         *          only wants to update scaling factors if the change is
+         *          greater than 0.01.
+         */
+        default boolean onZoom(@NonNull ZoomGestureDetector detector) {
+            return false;
+        }
+
+        /**
+         * Responds to the beginning of a zooming gesture. Reported by
+         * new pointers going down.
+         *
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         * @return Whether or not the detector should continue recognizing
+         *          this gesture. For example, if a gesture is beginning
+         *          with a focal point outside of a region where it makes
+         *          sense, onZoomBegin() may return false to ignore the
+         *          rest of the gesture.
+         */
+        default boolean onZoomBegin(@NonNull ZoomGestureDetector detector) {
+            return true;
+        }
+
+        /**
+         * Responds to the end of a zoom gesture. Reported by existing
+         * pointers going up.
+         *
+         * <p>Once a zoom has ended, {@link ZoomGestureDetector#getFocusX()}
+         * and {@link ZoomGestureDetector#getFocusY()} will return focal point
+         * of the pointers remaining on the screen.
+         *
+         * @param detector The detector reporting the event - use this to
+         *          retrieve extended info about event state.
+         */
+        default void onZoomEnd(@NonNull ZoomGestureDetector detector) {
+            // Intentionally empty
+        }
+    }
+
+    private final Context mContext;
+    private final OnZoomGestureListener mListener;
+
+    private float mFocusX;
+    private float mFocusY;
+
+    private boolean mQuickZoomEnabled;
+    private boolean mStylusZoomEnabled;
+
+    private float mCurrSpan;
+    private float mPrevSpan;
+    private float mInitialSpan;
+    private float mCurrSpanX;
+    private float mCurrSpanY;
+    private float mPrevSpanX;
+    private float mPrevSpanY;
+    private long mCurrTime;
+    private long mPrevTime;
+    private boolean mInProgress;
+    private int mSpanSlop;
+
+    private int mMinSpan;
+
+    private final Handler mHandler;
+
+    private float mAnchoredZoomStartX;
+    private float mAnchoredZoomStartY;
+    private int mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
+
+    private static final float SCALE_FACTOR = .5f;
+    private static final int ANCHORED_ZOOM_MODE_NONE = 0;
+    private static final int ANCHORED_ZOOM_MODE_DOUBLE_TAP = 1;
+    private static final int ANCHORED_ZOOM_MODE_STYLUS = 2;
+    private GestureDetector mGestureDetector;
+
+    private boolean mEventBeforeOrAboveStartingGestureEvent;
+
+    /**
+     * Creates a ZoomGestureDetector with the supplied listener.
+     * You may only use this constructor from a {@link android.os.Looper Looper} thread.
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public ZoomGestureDetector(@NonNull Context context,
+            @NonNull OnZoomGestureListener listener) {
+        this(context, null, listener);
+    }
+
+    /**
+     * Creates a ZoomGestureDetector with the supplied listener.
+     * @see android.os.Handler#Handler()
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * @param handler the handler to use for running deferred listener events.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public ZoomGestureDetector(@NonNull Context context, @Nullable Handler handler,
+            @NonNull OnZoomGestureListener listener) {
+        this(context, ViewConfiguration.get(context).getScaledTouchSlop() * 2,
+                DEFAULT_MIN_SPAN, handler, listener);
+    }
+
+    /**
+     * Creates a ZoomGestureDetector with span slop and min span.
+     *
+     * @param context the application's context.
+     * @param spanSlop the threshold for interpreting a touch movement as zooming.
+     * @param minSpan the minimum threshold of zooming span. The span could be
+     *                overridden by other usages to specify a different zooming span, for instance,
+     *                if you need pinch gestures to continue closer together than the default.
+     * @param listener the listener invoked for all the callbacks, this must not be null.
+     * @param handler the handler to use for running deferred listener events.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    @SuppressLint("ExecutorRegistration")
+    public ZoomGestureDetector(@NonNull Context context, int spanSlop,
+            int minSpan, @Nullable Handler handler,
+            @NonNull OnZoomGestureListener listener) {
+        mContext = context;
+        mListener = listener;
+        mSpanSlop = spanSlop;
+        mMinSpan = minSpan;
+        mHandler = handler;
+        // Quick zoom is enabled by default after JB_MR2
+        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+        if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            setQuickZoomEnabled(true);
+        }
+        // Stylus zoom is enabled by default after LOLLIPOP_MR1
+        if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+            setStylusZoomEnabled(true);
+        }
+    }
+
+    /**
+     * Accepts MotionEvents and dispatches events to a {@link OnZoomGestureListener}
+     * when appropriate.
+     *
+     * <p>Applications should pass a complete and consistent event stream to this method.
+     * A complete and consistent event stream involves all MotionEvents from the initial
+     * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
+     *
+     * @param event The event to process
+     * @return true if the event was processed and the detector wants to receive the
+     *         rest of the MotionEvents in this event stream.
+     */
+    public boolean onTouchEvent(@NonNull MotionEvent event) {
+        mCurrTime = event.getEventTime();
+
+        final int action = event.getActionMasked();
+
+        // Forward the event to check for double tap gesture
+        if (mQuickZoomEnabled) {
+            mGestureDetector.onTouchEvent(event);
+        }
+
+        final int count = event.getPointerCount();
+        final boolean isStylusButtonDown =
+                (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
+
+        final boolean anchoredZoomCancelled =
+                mAnchoredZoomMode == ANCHORED_ZOOM_MODE_STYLUS && !isStylusButtonDown;
+        final boolean streamComplete = action == MotionEvent.ACTION_UP
+                || action == MotionEvent.ACTION_CANCEL
+                || anchoredZoomCancelled;
+
+        if (action == MotionEvent.ACTION_DOWN || streamComplete) {
+            // Reset any scale in progress with the listener.
+            // If it's an ACTION_DOWN we're beginning a new event stream.
+            // This means the app probably didn't give us all the events. Shame on it.
+            if (mInProgress) {
+                mListener.onZoomEnd(this);
+                mInProgress = false;
+                mInitialSpan = 0;
+                mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
+            } else if (inAnchoredZoomMode() && streamComplete) {
+                mInProgress = false;
+                mInitialSpan = 0;
+                mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
+            }
+
+            if (streamComplete) {
+                return true;
+            }
+        }
+
+        if (!mInProgress && mStylusZoomEnabled && !inAnchoredZoomMode()
+                && !streamComplete && isStylusButtonDown) {
+            // Start of a button zoom gesture
+            mAnchoredZoomStartX = event.getX();
+            mAnchoredZoomStartY = event.getY();
+            mAnchoredZoomMode = ANCHORED_ZOOM_MODE_STYLUS;
+            mInitialSpan = 0;
+        }
+
+        final boolean configChanged = action == MotionEvent.ACTION_DOWN
+                || action == MotionEvent.ACTION_POINTER_UP
+                || action == MotionEvent.ACTION_POINTER_DOWN
+                || anchoredZoomCancelled;
+
+        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+        // Determine focal point
+        float sumX = 0, sumY = 0;
+        final int div = pointerUp ? count - 1 : count;
+        final float focusX;
+        final float focusY;
+        if (inAnchoredZoomMode()) {
+            // In anchored scale mode, the focal pt is always where the double tap
+            // or button down gesture started
+            focusX = mAnchoredZoomStartX;
+            focusY = mAnchoredZoomStartY;
+            if (event.getY() < focusY) {
+                mEventBeforeOrAboveStartingGestureEvent = true;
+            } else {
+                mEventBeforeOrAboveStartingGestureEvent = false;
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                if (skipIndex == i) continue;
+                sumX += event.getX(i);
+                sumY += event.getY(i);
+            }
+
+            focusX = sumX / div;
+            focusY = sumY / div;
+        }
+
+        // Determine average deviation from focal point
+        float devSumX = 0, devSumY = 0;
+        for (int i = 0; i < count; i++) {
+            if (skipIndex == i) continue;
+
+            // Convert the resulting diameter into a radius.
+            devSumX += Math.abs(event.getX(i) - focusX);
+            devSumY += Math.abs(event.getY(i) - focusY);
+        }
+        final float devX = devSumX / div;
+        final float devY = devSumY / div;
+
+        // Span is the average distance between touch points through the focal point;
+        // i.e. the diameter of the circle with a radius of the average deviation from
+        // the focal point.
+        final float spanX = devX * 2;
+        final float spanY = devY * 2;
+        final float span;
+        if (inAnchoredZoomMode()) {
+            span = spanY;
+        } else {
+            span = (float) Math.hypot(spanX, spanY);
+        }
+
+        // Dispatch begin/end events as needed.
+        // If the configuration changes, notify the app to reset its current state by beginning
+        // a fresh zoom event stream.
+        final boolean wasInProgress = mInProgress;
+        mFocusX = focusX;
+        mFocusY = focusY;
+        if (!inAnchoredZoomMode() && mInProgress && (span < mMinSpan || configChanged)) {
+            mListener.onZoomEnd(this);
+            mInProgress = false;
+            mInitialSpan = span;
+        }
+        if (configChanged) {
+            mPrevSpanX = mCurrSpanX = spanX;
+            mPrevSpanY = mCurrSpanY = spanY;
+            mInitialSpan = mPrevSpan = mCurrSpan = span;
+        }
+
+        final int minSpan = inAnchoredZoomMode() ? mSpanSlop : mMinSpan;
+        if (!mInProgress && span >=  minSpan
+                && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
+            mPrevSpanX = mCurrSpanX = spanX;
+            mPrevSpanY = mCurrSpanY = spanY;
+            mPrevSpan = mCurrSpan = span;
+            mPrevTime = mCurrTime;
+            mInProgress = mListener.onZoomBegin(this);
+        }
+
+        // Handle motion; focal point and span/scale factor are changing.
+        if (action == MotionEvent.ACTION_MOVE) {
+            mCurrSpanX = spanX;
+            mCurrSpanY = spanY;
+            mCurrSpan = span;
+
+            boolean updatePrev = true;
+
+            if (mInProgress) {
+                updatePrev = mListener.onZoom(this);
+            }
+
+            if (updatePrev) {
+                mPrevSpanX = mCurrSpanX;
+                mPrevSpanY = mCurrSpanY;
+                mPrevSpan = mCurrSpan;
+                mPrevTime = mCurrTime;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean inAnchoredZoomMode() {
+        return mAnchoredZoomMode != ANCHORED_ZOOM_MODE_NONE;
+    }
+
+    /**
+     * Set whether the associated {@link OnZoomGestureListener} should receive onZoom callbacks
+     * when the user performs a doubleTap followed by a swipe.
+     *
+     * <p>If not set, this is enabled by default.
+     *
+     * @param enabled {@code true} to enable quick zooming, {@code false} to disable.
+     */
+    public void setQuickZoomEnabled(boolean enabled) {
+        mQuickZoomEnabled = enabled;
+        if (mQuickZoomEnabled && mGestureDetector == null) {
+            GestureDetector.SimpleOnGestureListener gestureListener =
+                    new GestureDetector.SimpleOnGestureListener() {
+                        @Override
+                        public boolean onDoubleTap(MotionEvent e) {
+                            // Double tap: start watching for a swipe
+                            mAnchoredZoomStartX = e.getX();
+                            mAnchoredZoomStartY = e.getY();
+                            mAnchoredZoomMode = ANCHORED_ZOOM_MODE_DOUBLE_TAP;
+                            return true;
+                        }
+                    };
+            mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
+        }
+    }
+
+    /**
+     * Return whether the quick zoom gesture, in which the user performs a double tap followed by a
+     * swipe, should perform zooming.
+     *
+     * @see #setQuickZoomEnabled(boolean)
+     */
+    public boolean isQuickZoomEnabled() {
+        return mQuickZoomEnabled;
+    }
+
+    /**
+     * Sets whether the associates {@link OnZoomGestureListener} should receive
+     * onZoom callbacks when the user uses a stylus and presses the button.
+     *
+     * <p>If not set, this is enabled by default.
+     *
+     * @param enabled {@code true} to enable stylus zooming, {@code false} to disable.
+     */
+    public void setStylusZoomEnabled(boolean enabled) {
+        mStylusZoomEnabled = enabled;
+    }
+
+    /**
+     * Return whether the stylus zoom gesture, in which the user uses a stylus and presses the
+     * button, should perform zooming. {@see #setStylusScaleEnabled(boolean)}
+     */
+    public boolean isStylusZoomEnabled() {
+        return mStylusZoomEnabled;
+    }
+
+    /**
+     * Returns {@code true} if a zoom gesture is in progress.
+     */
+    public boolean isInProgress() {
+        return mInProgress;
+    }
+
+    /**
+     * Get the X coordinate of the current gesture's focal point.
+     * If a gesture is in progress, the focal point is between
+     * each of the pointers forming the gesture.
+     *
+     * <p>If {@link #isInProgress()} would return false, the result of this
+     * function is undefined.
+     *
+     * @return X coordinate of the focal point in pixels.
+     */
+    public float getFocusX() {
+        return mFocusX;
+    }
+
+    /**
+     * Get the Y coordinate of the current gesture's focal point.
+     * If a gesture is in progress, the focal point is between
+     * each of the pointers forming the gesture.
+     *
+     * <p>If {@link #isInProgress()} would return false, the result of this
+     * function is undefined.
+     *
+     * @return Y coordinate of the focal point in pixels.
+     */
+    public float getFocusY() {
+        return mFocusY;
+    }
+
+    /**
+     * Return the average distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpan() {
+        return mCurrSpan;
+    }
+
+    /**
+     * Return the average X distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpanX() {
+        return mCurrSpanX;
+    }
+
+    /**
+     * Return the average Y distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Distance between pointers in pixels.
+     */
+    public float getCurrentSpanY() {
+        return mCurrSpanY;
+    }
+
+    /**
+     * Return the previous average distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpan() {
+        return mPrevSpan;
+    }
+
+    /**
+     * Return the previous average X distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpanX() {
+        return mPrevSpanX;
+    }
+
+    /**
+     * Return the previous average Y distance between each of the pointers forming the
+     * gesture in progress through the focal point.
+     *
+     * @return Previous distance between pointers in pixels.
+     */
+    public float getPreviousSpanY() {
+        return mPrevSpanY;
+    }
+
+    /**
+     * Return the scaling factor from the previous zoom event to the current
+     * event. This value is defined as
+     * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
+     *
+     * @return The current scaling factor.
+     */
+    public float getScaleFactor() {
+        if (inAnchoredZoomMode()) {
+            // Drag is moving up; the further away from the gesture
+            // start, the smaller the span should be, the closer,
+            // the larger the span, and therefore the larger the scale
+            final boolean scaleUp =
+                    (mEventBeforeOrAboveStartingGestureEvent
+                            && (mCurrSpan < mPrevSpan))
+                            || (!mEventBeforeOrAboveStartingGestureEvent
+                            && (mCurrSpan > mPrevSpan));
+            final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
+            return mPrevSpan <= mSpanSlop ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
+        }
+        return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
+    }
+
+    /**
+     * Return the time difference in milliseconds between the previous
+     * accepted zooming event and the current zooming event.
+     *
+     * @return Time difference since the last zooming event in milliseconds.
+     */
+    public long getTimeDelta() {
+        return mCurrTime - mPrevTime;
+    }
+
+    /**
+     * Return the event time of the current event being processed.
+     *
+     * @return Current event time in milliseconds.
+     */
+    public long getEventTime() {
+        return mCurrTime;
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsAnimationTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsAnimationTest.kt
deleted file mode 100644
index 9cabe4e..0000000
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsAnimationTest.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.compose.foundation.layout
-
-import android.view.View
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import java.util.concurrent.TimeUnit
-import org.junit.After
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-
-@MediumTest
-class WindowInsetsAnimationTest {
-    @get:Rule
-    val rule = createAndroidComposeRule<WindowInsetsActionBarActivity>()
-
-    @Before
-    fun setup() {
-        rule.activity.createdLatch.await(1, TimeUnit.SECONDS)
-        rule.activity.attachedToWindowLatch.await(1, TimeUnit.SECONDS)
-    }
-
-    @After
-    fun teardown() {
-        rule.runOnUiThread {
-            val window = rule.activity.window
-            val view = window.decorView
-            WindowInsetsControllerCompat(window, view).hide(WindowInsetsCompat.Type.ime())
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 22) // b/266742122
-    @OptIn(ExperimentalLayoutApi::class)
-    @Test
-    @Ignore("b/266742122")
-    fun imeAnimationWhenShowingIme() {
-        val imeAnimationSourceValues = mutableListOf<Int>()
-        val imeAnimationTargetValues = mutableListOf<Int>()
-        val focusRequester = FocusRequester()
-        rule.setContent {
-            val density = LocalDensity.current
-            val source = WindowInsets.imeAnimationSource
-            val target = WindowInsets.imeAnimationTarget
-            val sourceBottom = source.getBottom(density)
-            imeAnimationSourceValues += sourceBottom
-            val targetBottom = target.getBottom(density)
-            imeAnimationTargetValues += targetBottom
-            BasicTextField(
-                value = "Hello World",
-                onValueChange = {},
-                Modifier.focusRequester(focusRequester)
-            )
-        }
-
-        rule.waitForIdle()
-        rule.runOnUiThread {
-            focusRequester.requestFocus()
-        }
-
-        rule.waitForIdle()
-        rule.waitUntil(timeoutMillis = 3000) {
-            imeAnimationSourceValues.last() > imeAnimationTargetValues.first()
-        }
-
-        rule.waitUntil(timeoutMillis = 3000) {
-            imeAnimationTargetValues.last() == imeAnimationSourceValues.last()
-        }
-    }
-
-    @OptIn(ExperimentalLayoutApi::class)
-    @FlakyTest(bugId = 256020254)
-    @Test
-    fun imeAnimationWhenHidingIme() {
-        val imeAnimationSourceValues = mutableListOf<Int>()
-        val imeAnimationTargetValues = mutableListOf<Int>()
-        val focusRequester = FocusRequester()
-        lateinit var view: View
-        rule.setContent {
-            view = LocalView.current
-            val density = LocalDensity.current
-            val source = WindowInsets.imeAnimationSource
-            val target = WindowInsets.imeAnimationTarget
-            val sourceBottom = source.getBottom(density)
-            imeAnimationSourceValues += sourceBottom
-            val targetBottom = target.getBottom(density)
-            imeAnimationTargetValues += targetBottom
-            BasicTextField(
-                value = "Hello World",
-                onValueChange = {},
-                Modifier.focusRequester(focusRequester)
-            )
-        }
-
-        rule.waitForIdle()
-        rule.runOnUiThread {
-            focusRequester.requestFocus()
-        }
-
-        rule.waitUntil(timeoutMillis = 3000) {
-            val target = imeAnimationTargetValues.last()
-            val source = imeAnimationSourceValues.last()
-            target > imeAnimationSourceValues.first() && target == source
-        }
-
-        rule.runOnUiThread {
-            val window = rule.activity.window
-            WindowInsetsControllerCompat(window, view).hide(WindowInsetsCompat.Type.ime())
-        }
-
-        rule.waitForIdle()
-
-        rule.waitUntil(timeoutMillis = 3000) {
-            imeAnimationTargetValues.last() == imeAnimationSourceValues.first()
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
index 614d0d4..1350683 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
@@ -58,6 +58,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import kotlin.math.absoluteValue
 import kotlin.test.assertTrue
 import kotlinx.coroutines.CoroutineScope
 
@@ -357,12 +358,12 @@
             pagerState.currentPageOffsetFraction != 0.0f
         } // wait for first move from drag
         rule.mainClock.advanceTimeUntil {
-            pagerState.currentPageOffsetFraction == 0.0f
+            pagerState.currentPageOffsetFraction.absoluteValue < 0.00001
         } // wait for fling settling
         // pump the clock twice and check we're still settled.
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
-        assertTrue { pagerState.currentPageOffsetFraction == 0.0f }
+        assertTrue { pagerState.currentPageOffsetFraction.absoluteValue < 0.00001 }
     }
 }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt
index 69ef744..71635fb 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt
@@ -89,10 +89,10 @@
                                 add(
                                     ParamConfig(
                                         orientation = orientation,
-                                        pageSpacing = pageSpacing,
                                         mainAxisContentPadding = contentPadding,
                                         reverseLayout = reverseLayout,
-                                        layoutDirection = layoutDirection
+                                        layoutDirection = layoutDirection,
+                                        pageSpacing = pageSpacing
                                     )
                                 )
                             }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
index 219d247..37a5df7 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.gestures.snapping.SnapPosition
 import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
 import androidx.compose.foundation.layout.Box
@@ -36,11 +37,14 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastMaxBy
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
 import kotlin.math.abs
+import kotlin.math.roundToInt
 import kotlin.test.assertFalse
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
@@ -93,6 +97,42 @@
     }
 
     @Test
+    fun pageSizeIsZero_offsetFractionShouldNotBeNan() {
+        // Arrange
+        val zeroPageSize = object : PageSize {
+            override fun Density.calculateMainAxisPageSize(
+                availableSpace: Int,
+                pageSpacing: Int
+            ): Int {
+                return 0
+            }
+        }
+
+        rule.setContent {
+            pagerState = rememberPagerState {
+                DefaultPageCount
+            }
+            HorizontalOrVerticalPager(
+                state = pagerState,
+                modifier = Modifier
+                    .size(0.dp)
+                    .testTag(PagerTestTag)
+                    .onSizeChanged { pagerSize = if (vertical) it.height else it.width },
+                pageSize = zeroPageSize,
+                reverseLayout = config.reverseLayout,
+                pageSpacing = config.pageSpacing,
+                contentPadding = config.mainAxisContentPadding,
+            ) {
+                Page(index = it)
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(pagerState.currentPageOffsetFraction).isNotNaN()
+        }
+    }
+
+    @Test
     fun initialPageOnPagerState_shouldDisplayThatPageFirst() {
         // Arrange
 
@@ -500,6 +540,105 @@
         }
     }
 
+    @Test
+    fun canScrollForwardAndBackward_afterSmallScrollFromStart() {
+        val pageSizePx = 100
+        val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
+        createPager(
+            modifier = Modifier.size(pageSizeDp * 1.5f),
+            pageSize = { PageSize.Fixed(pageSizeDp) })
+
+        val delta = (pageSizePx / 3f).roundToInt()
+
+        runBlocking {
+            withContext(Dispatchers.Main + AutoTestFrameClock()) {
+                // small enough scroll to not cause any new items to be composed or old ones disposed.
+                pagerState.scrollBy(delta.toFloat())
+            }
+            rule.runOnIdle {
+                assertThat(pagerState.firstVisiblePageOffset).isEqualTo(delta)
+                assertThat(pagerState.canScrollForward).isTrue()
+                assertThat(pagerState.canScrollBackward).isTrue()
+            }
+            // and scroll back to start
+            withContext(Dispatchers.Main + AutoTestFrameClock()) {
+                pagerState.scrollBy(-delta.toFloat())
+            }
+            rule.runOnIdle {
+                assertThat(pagerState.canScrollForward).isTrue()
+                assertThat(pagerState.canScrollBackward).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun canScrollForwardAndBackward_afterSmallScrollFromEnd() {
+        val pageSizePx = 100
+        val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
+        createPager(
+            modifier = Modifier.size(pageSizeDp * 1.5f),
+            pageSize = { PageSize.Fixed(pageSizeDp) })
+        val delta = -(pageSizePx / 3f).roundToInt()
+        runBlocking {
+            withContext(Dispatchers.Main + AutoTestFrameClock()) {
+                // scroll to the end of the list.
+                pagerState.scrollToPage(DefaultPageCount)
+                // small enough scroll to not cause any new items to be composed or old ones disposed.
+                pagerState.scrollBy(delta.toFloat())
+            }
+            rule.runOnIdle {
+                assertThat(pagerState.canScrollForward).isTrue()
+                assertThat(pagerState.canScrollBackward).isTrue()
+            }
+            // and scroll back to the end
+            withContext(Dispatchers.Main + AutoTestFrameClock()) {
+                pagerState.scrollBy(-delta.toFloat())
+            }
+            rule.runOnIdle {
+                assertThat(pagerState.canScrollForward).isFalse()
+                assertThat(pagerState.canScrollBackward).isTrue()
+            }
+        }
+    }
+
+    @Test
+    fun canScrollForwardAndBackward_afterSmallScrollFromEnd_withContentPadding() {
+        val pageSizePx = 100
+        val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
+        val afterContentPaddingDp = with(rule.density) { 2.toDp() }
+        createPager(
+            modifier = Modifier.size(pageSizeDp * 1.5f),
+            pageSize = { PageSize.Fixed(pageSizeDp) },
+            contentPadding = PaddingValues(afterContent = afterContentPaddingDp)
+        )
+
+        val delta = -(pageSizePx / 3f).roundToInt()
+        runBlocking {
+            withContext(Dispatchers.Main + AutoTestFrameClock()) {
+                // scroll to the end of the list.
+                pagerState.scrollToPage(DefaultPageCount)
+
+                assertThat(pagerState.canScrollForward).isFalse()
+                assertThat(pagerState.canScrollBackward).isTrue()
+
+                // small enough scroll to not cause any new pages to be composed or old ones disposed.
+                pagerState.scrollBy(delta.toFloat())
+            }
+            rule.runOnIdle {
+                assertThat(pagerState.canScrollForward).isTrue()
+                assertThat(pagerState.canScrollBackward).isTrue()
+            }
+            // and scroll back to the end
+            withContext(Dispatchers.Main + AutoTestFrameClock()) {
+                pagerState.scrollBy(-delta.toFloat())
+            }
+            rule.runOnIdle {
+                assertThat(pagerState.canScrollForward).isFalse()
+                assertThat(pagerState.canScrollBackward).isTrue()
+            }
+        }
+    }
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
index 45748d4..6586433 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
@@ -78,14 +78,14 @@
             if (isVertical) {
                 placeableOffsets[indexInArray] =
                     requireNotNull(horizontalAlignment) { "null horizontalAlignment" }
-                    .align(placeable.width, layoutWidth, layoutDirection)
+                        .align(placeable.width, layoutWidth, layoutDirection)
                 placeableOffsets[indexInArray + 1] = mainAxisOffset
                 mainAxisOffset += placeable.height
             } else {
                 placeableOffsets[indexInArray] = mainAxisOffset
                 placeableOffsets[indexInArray + 1] =
                     requireNotNull(verticalAlignment) { "null verticalAlignment" }
-                    .align(placeable.height, layoutHeight)
+                        .align(placeable.height, layoutHeight)
                 mainAxisOffset += placeable.width
             }
         }
@@ -110,8 +110,20 @@
         }
     }
 
+    fun applyScrollDelta(delta: Int) {
+        offset += delta
+        repeat(placeableOffsets.size) { index ->
+            // placeableOffsets consist of x and y pairs for each placeable.
+            // if isVertical is true then the main axis offsets are located at indexes 1, 3, 5 etc.
+            if ((isVertical && index % 2 == 1) || (!isVertical && index % 2 == 0)) {
+                placeableOffsets[index] += delta
+            }
+        }
+    }
+
     private fun getOffset(index: Int) =
         IntOffset(placeableOffsets[index * 2], placeableOffsets[index * 2 + 1])
+
     private val Placeable.mainAxisSize get() = if (isVertical) height else width
     private inline fun IntOffset.copy(mainAxisMap: (Int) -> Int): IntOffset =
         IntOffset(if (isVertical) x else mainAxisMap(x), if (isVertical) mainAxisMap(y) else y)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
index 70b4f7a..bef2c4f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
 import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
+import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
@@ -56,6 +57,7 @@
     beyondBoundsPageCount: Int,
     pinnedPages: List<Int>,
     snapPosition: SnapPosition,
+    placementScopeInvalidator: ObservableScopeInvalidator,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): PagerMeasureResult {
     require(beforeContentPadding >= 0) { "negative beforeContentPadding" }
@@ -86,7 +88,8 @@
             canScrollForward = false,
             currentPage = null,
             currentPageOffsetFraction = 0.0f,
-            snapPosition = snapPosition
+            snapPosition = snapPosition,
+            remeasureNeeded = false
         )
     } else {
 
@@ -185,10 +188,24 @@
         val maxMainAxis = (maxOffset + afterContentPadding).coerceAtLeast(0)
         var currentMainAxisOffset = -currentFirstPageScrollOffset
 
+        // will be set to true if we composed some items only to know their size and apply scroll,
+        // while in the end this item will not end up in the visible viewport. we will need an
+        // extra remeasure in order to dispose such items.
+        var remeasureNeeded = false
+
         // first we need to skip pages we already composed while composing backward
-        visiblePages.fastForEach {
-            index++
-            currentMainAxisOffset += pageSizeWithSpacing
+        var indexInVisibleItems = 0
+
+        while (indexInVisibleItems < visiblePages.size) {
+            if (currentMainAxisOffset >= maxMainAxis) {
+                // this item is out of the bounds and will not be visible.
+                visiblePages.removeAt(indexInVisibleItems)
+                remeasureNeeded = true
+            } else {
+                index++
+                currentMainAxisOffset += pageSizeWithSpacing
+                indexInVisibleItems++
+            }
         }
 
         debugLog { "Composing Forward Starting at Index=$index" }
@@ -223,9 +240,10 @@
             }
 
             if (currentMainAxisOffset <= minOffset && index != pageCount - 1) {
-                // this page is offscreen and will not be placed. advance firstVisiblePage
+                // this page is offscreen and will not be visible. advance currentFirstPage
                 currentFirstPage = index + 1
                 currentFirstPageScrollOffset -= pageSizeWithSpacing
+                remeasureNeeded = true
             } else {
                 maxCrossAxis = maxOf(maxCrossAxis, measuredPage.crossAxisSize)
                 visiblePages.add(measuredPage)
@@ -401,11 +419,14 @@
 
         val currentPagePositionOffset = (newCurrentPage?.offset ?: 0)
 
-        val currentPageOffsetFraction =
+        val currentPageOffsetFraction = if (pageSizeWithSpacing == 0) {
+            0.0f
+        } else {
             ((snapOffset - currentPagePositionOffset) / (pageSizeWithSpacing.toFloat())).coerceIn(
                 MinPageOffset,
                 MaxPageOffset
             )
+        }
 
         debugLog {
             "Finished Measure Pass" +
@@ -421,6 +442,8 @@
                 positionedPages.fastForEach {
                     it.place(this)
                 }
+                // we attach it during the placement so PagerState can trigger re-placement
+                placementScopeInvalidator.attachToScope()
             },
             viewportStartOffset = -beforeContentPadding,
             viewportEndOffset = maxOffset + afterContentPadding,
@@ -434,7 +457,8 @@
             canScrollForward = index < pageCount || currentMainAxisOffset > maxOffset,
             currentPage = newCurrentPage,
             currentPageOffsetFraction = currentPageOffsetFraction,
-            snapPosition = snapPosition
+            snapPosition = snapPosition,
+            remeasureNeeded = remeasureNeeded
         )
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
index f255c9e..f5e80fa 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
@@ -181,8 +181,8 @@
             reverseLayout = reverseLayout,
             pinnedPages = pinnedPages,
             snapPosition = snapPosition,
+            placementScopeInvalidator = state.placementScopeInvalidator,
             layout = { width, height, placement ->
-                state.remeasureTrigger // read state to trigger remeasures on state write
                 layout(
                     containerConstraints.constrainWidth(width + totalHorizontalPadding),
                     containerConstraints.constrainHeight(height + totalVerticalPadding),
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
index c236604..4d193eb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
@@ -21,10 +21,11 @@
 import androidx.compose.foundation.gestures.snapping.SnapPosition
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastForEach
 
 @OptIn(ExperimentalFoundationApi::class)
 internal class PagerMeasureResult(
-    override val visiblePagesInfo: List<PageInfo>,
+    override val visiblePagesInfo: List<MeasuredPage>,
     override val pageSize: Int,
     override val pageSpacing: Int,
     override val afterContentPadding: Int,
@@ -35,13 +36,86 @@
     override val beyondBoundsPageCount: Int,
     val firstVisiblePage: MeasuredPage?,
     val currentPage: MeasuredPage?,
-    val currentPageOffsetFraction: Float,
-    val firstVisiblePageScrollOffset: Int,
-    val canScrollForward: Boolean,
+    var currentPageOffsetFraction: Float,
+    var firstVisiblePageScrollOffset: Int,
+    var canScrollForward: Boolean,
     override val snapPosition: SnapPosition,
     measureResult: MeasureResult,
+    /** True when extra remeasure is required. */
+    val remeasureNeeded: Boolean,
 ) : PagerLayoutInfo, MeasureResult by measureResult {
     override val viewportSize: IntSize
         get() = IntSize(width, height)
     override val beforeContentPadding: Int get() = -viewportStartOffset
+
+    val canScrollBackward
+        get() = (firstVisiblePage?.index ?: 0) != 0 || firstVisiblePageScrollOffset != 0
+
+    /**
+     * Tries to apply a scroll [delta] for this layout info. In some cases we can apply small
+     * scroll deltas by just changing the offsets for each [visiblePagesInfo].
+     * But we can only do so if after applying the delta we would not need to compose a new item
+     * or dispose an item which is currently visible. In this case this function will not apply
+     * the [delta] and return false.
+     *
+     * @return true if we can safely apply a passed scroll [delta] to this layout info.
+     * If true is returned, only the placement phase is needed to apply new offsets.
+     * If false is returned, it means we have to rerun the full measure phase to apply the [delta].
+     */
+    fun tryToApplyScrollWithoutRemeasure(delta: Int): Boolean {
+        val pageSizeWithSpacing = pageSize + pageSpacing
+        if (remeasureNeeded || visiblePagesInfo.isEmpty() || firstVisiblePage == null ||
+            // applying this delta will change firstVisibleItem
+            (firstVisiblePageScrollOffset - delta) !in 0 until pageSizeWithSpacing
+        ) {
+            return false
+        }
+
+        val deltaFraction = if (pageSizeWithSpacing != 0) {
+            (delta / pageSizeWithSpacing.toFloat())
+        } else {
+            0.0f
+        }
+
+        val newCurrentPageOffsetFraction = currentPageOffsetFraction - deltaFraction
+        if (currentPage == null ||
+            //  applying this delta will change current page
+            newCurrentPageOffsetFraction >= MaxPageOffset ||
+            newCurrentPageOffsetFraction <= MinPageOffset
+        ) {
+            return false
+        }
+
+        val first = visiblePagesInfo.first()
+        val last = visiblePagesInfo.last()
+        val canApply = if (delta < 0) {
+            // scrolling forward
+            val deltaToFirstItemChange =
+                first.offset + pageSizeWithSpacing - viewportStartOffset
+            val deltaToLastItemChange =
+                last.offset + pageSizeWithSpacing - viewportEndOffset
+            minOf(deltaToFirstItemChange, deltaToLastItemChange) > -delta
+        } else {
+            // scrolling backward
+            val deltaToFirstItemChange =
+                viewportStartOffset - first.offset
+            val deltaToLastItemChange =
+                viewportEndOffset - last.offset
+            minOf(deltaToFirstItemChange, deltaToLastItemChange) > delta
+        }
+        return if (canApply) {
+            currentPageOffsetFraction -= deltaFraction
+            firstVisiblePageScrollOffset -= delta
+            visiblePagesInfo.fastForEach {
+                it.applyScrollDelta(delta)
+            }
+            if (!canScrollForward && delta > 0) {
+                // we scrolled backward, so now we can scroll forward
+                canScrollForward = true
+            }
+            true
+        } else {
+            false
+        }
+    }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
index 425b1c4..c7a96e6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
@@ -106,6 +106,10 @@
         currentPageOffsetFraction = offsetFraction
     }
 
+    fun updateCurrentPageOffsetFraction(offsetFraction: Float) {
+        currentPageOffsetFraction = offsetFraction
+    }
+
     fun currentAbsoluteScrollOffset(): Int {
         return ((currentPage +
             currentPageOffsetFraction) * state.pageSizeWithSpacing).roundToInt()
@@ -113,9 +117,12 @@
 
     fun applyScrollDelta(delta: Int) {
         debugLog { "Applying Delta=$delta" }
-        val fractionUpdate = delta / state.pageSizeWithSpacing.toFloat()
+        val fractionUpdate = if (state.pageSizeWithSpacing == 0) {
+            0.0f
+        } else {
+            delta / state.pageSizeWithSpacing.toFloat()
+        }
         currentPageOffsetFraction += fractionUpdate
-        state.remeasureTrigger = Unit // trigger remeasure
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 5781bf4..0459e93 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -31,6 +31,7 @@
 import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
 import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
+import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
@@ -46,11 +47,12 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Remeasurement
 import androidx.compose.ui.layout.RemeasurementModifier
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import kotlin.math.abs
 import kotlin.math.absoluteValue
@@ -175,8 +177,6 @@
 
     private var isScrollingForward: Boolean by mutableStateOf(false)
 
-    internal var remeasureTrigger by mutableStateOf(Unit, neverEqualPolicy())
-
     internal val scrollPosition = PagerScrollPosition(currentPage, currentPageOffsetFraction, this)
 
     internal var firstVisiblePage = currentPage
@@ -218,14 +218,28 @@
         val newValue = absolute.coerceIn(0.0f, maxScrollOffset)
         val changed = absolute != newValue
         val consumed = newValue - currentScrollPosition
-
+        previousPassDelta = consumed
         if (consumed.absoluteValue != 0.0f) {
             isScrollingForward = consumed > 0.0f
         }
 
         val consumedInt = consumed.roundToInt()
-        scrollPosition.applyScrollDelta(consumedInt)
-        previousPassDelta = consumed
+
+        val layoutInfo = pagerLayoutInfoState.value
+
+        if (layoutInfo.tryToApplyScrollWithoutRemeasure(-consumedInt)) {
+            debugLog { "Will Apply Without Remeasure" }
+            applyMeasureResult(
+                result = layoutInfo,
+                visibleItemsStayedTheSame = true
+            )
+            // we don't need to remeasure, so we only trigger re-placement:
+            placementScopeInvalidator.invalidateScope()
+        } else {
+            debugLog { "Will Apply With Remeasure" }
+            scrollPosition.applyScrollDelta(consumedInt)
+            remeasurement?.forceRemeasure()
+        }
         accumulator = consumed - consumedInt
 
         // Avoid floating-point rounding error
@@ -260,7 +274,8 @@
     private var wasPrefetchingForward = false
 
     /** Backing state for PagerLayoutInfo */
-    private var pagerLayoutInfoState = mutableStateOf<PagerLayoutInfo>(EmptyLayoutInfo)
+    private var pagerLayoutInfoState =
+        mutableStateOf(EmptyLayoutInfo, neverEqualPolicy())
 
     /**
      * A [PagerLayoutInfo] that contains useful information about the Pager's last layout pass.
@@ -425,6 +440,8 @@
 
     internal val nearestRange: IntRange by scrollPosition.nearestRangeState
 
+    internal val placementScopeInvalidator = ObservableScopeInvalidator()
+
     /**
      * Scroll (jump immediately) to a given [page].
      *
@@ -592,20 +609,27 @@
     /**
      *  Updates the state with the new calculated scroll position and consumed scroll.
      */
-    internal fun applyMeasureResult(result: PagerMeasureResult) {
+    internal fun applyMeasureResult(
+        result: PagerMeasureResult,
+        visibleItemsStayedTheSame: Boolean = false
+    ) {
         debugLog { "Applying Measure Result" }
-        scrollPosition.updateFromMeasureResult(result)
+        if (visibleItemsStayedTheSame) {
+            scrollPosition.updateCurrentPageOffsetFraction(result.currentPageOffsetFraction)
+        } else {
+            scrollPosition.updateFromMeasureResult(result)
+            cancelPrefetchIfVisibleItemsChanged(result)
+        }
         pagerLayoutInfoState.value = result
         canScrollForward = result.canScrollForward
-        canScrollBackward = (result.firstVisiblePage?.index ?: 0) != 0 ||
-            result.firstVisiblePageScrollOffset != 0
+        canScrollBackward = result.canScrollBackward
         numMeasurePasses++
         result.firstVisiblePage?.let { firstVisiblePage = it.index }
         firstVisiblePageOffset = result.firstVisiblePageScrollOffset
-        cancelPrefetchIfVisibleItemsChanged(result)
         tryRunPrefetch(result)
         maxScrollOffset = result.calculateNewMaxScrollOffset(pageCount)
-        debugLog { "Finished Applying Measure Result" }
+        debugLog { "Finished Applying Measure Result" +
+            "\nNew maxScrollOffset=$maxScrollOffset" }
     }
 
     private fun tryRunPrefetch(result: PagerMeasureResult) = Snapshot.withoutReadObservation {
@@ -725,20 +749,33 @@
 internal const val PagesToPrefetch = 1
 
 @OptIn(ExperimentalFoundationApi::class)
-internal object EmptyLayoutInfo : PagerLayoutInfo {
-    override val visiblePagesInfo: List<PageInfo> = emptyList()
-    override val pageSize: Int = 0
-    override val pageSpacing: Int = 0
-    override val beforeContentPadding: Int = 0
-    override val afterContentPadding: Int = 0
-    override val viewportSize: IntSize = IntSize.Zero
-    override val orientation: Orientation = Orientation.Horizontal
-    override val viewportStartOffset: Int = 0
-    override val viewportEndOffset: Int = 0
-    override val reverseLayout: Boolean = false
-    override val beyondBoundsPageCount: Int = 0
-    override val snapPosition: SnapPosition = SnapPosition.Start
-}
+internal val EmptyLayoutInfo = PagerMeasureResult(
+    visiblePagesInfo = emptyList(),
+    pageSize = 0,
+    pageSpacing = 0,
+    afterContentPadding = 0,
+    orientation = Orientation.Horizontal,
+    viewportStartOffset = 0,
+    viewportEndOffset = 0,
+    reverseLayout = false,
+    beyondBoundsPageCount = 0,
+    firstVisiblePage = null,
+    firstVisiblePageScrollOffset = 0,
+    currentPage = null,
+    currentPageOffsetFraction = 0.0f,
+    canScrollForward = false,
+    snapPosition = SnapPosition.Start,
+    measureResult = object : MeasureResult {
+        override val width: Int = 0
+
+        override val height: Int = 0
+        @Suppress("PrimitiveInCollection")
+        override val alignmentLines: Map<AlignmentLine, Int> = mapOf()
+
+        override fun placeChildren() {}
+    },
+    remeasureNeeded = false
+)
 
 private val UnitDensity = object : Density {
     override val density: Float = 1f
@@ -757,7 +794,7 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 private fun PagerMeasureResult.calculateNewMaxScrollOffset(pageCount: Int): Float {
-    return beforeContentPadding +
+    return (beforeContentPadding +
         pageCount * (pageSpacing + pageSize).toFloat() +
-        afterContentPadding - pageSpacing - singleAxisViewPort
+        afterContentPadding - pageSpacing - singleAxisViewPort).coerceAtLeast(0.0f)
 }
diff --git a/compose/integration-tests/material-catalog/build.gradle b/compose/integration-tests/material-catalog/build.gradle
index 80f662f..c7bf51b 100644
--- a/compose/integration-tests/material-catalog/build.gradle
+++ b/compose/integration-tests/material-catalog/build.gradle
@@ -45,7 +45,7 @@
     implementation project(":compose:runtime:runtime")
     implementation project(":compose:foundation:foundation-layout")
     implementation project(":compose:ui:ui")
-    implementation project(":compose:material:material")
+    implementation("androidx.compose.material:material:1.6.0-beta01")
     implementation project(":compose:material3:material3")
     implementation project(":compose:material:material:integration-tests:material-catalog")
     implementation project(":compose:material3:material3:integration-tests:material3-catalog")
diff --git a/compose/material/material/integration-tests/material-catalog/build.gradle b/compose/material/material/integration-tests/material-catalog/build.gradle
index 15bc9f9..49c18e2 100644
--- a/compose/material/material/integration-tests/material-catalog/build.gradle
+++ b/compose/material/material/integration-tests/material-catalog/build.gradle
@@ -30,8 +30,8 @@
     implementation project(":compose:runtime:runtime")
     implementation project(":compose:foundation:foundation-layout")
     implementation project(":compose:ui:ui")
-    implementation project(":compose:material:material")
-    implementation project(":compose:material:material:material-samples")
+    implementation("androidx.compose.material:material:1.6.0-beta01")
+    implementation("androidx.compose.material:material-samples:1.6.0-beta01")
     implementation project(":navigation:navigation-compose")
 }
 
diff --git a/compose/material3/material3-adaptive/api/current.txt b/compose/material3/material3-adaptive/api/current.txt
index e1ffa3b..6ed20b5 100644
--- a/compose/material3/material3-adaptive/api/current.txt
+++ b/compose/material3/material3-adaptive/api/current.txt
@@ -17,8 +17,8 @@
 
   public final class AndroidWindowInfo_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> collectWindowSizeAsState();
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
   }
 
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
diff --git a/compose/material3/material3-adaptive/api/restricted_current.txt b/compose/material3/material3-adaptive/api/restricted_current.txt
index e1ffa3b..6ed20b5 100644
--- a/compose/material3/material3-adaptive/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive/api/restricted_current.txt
@@ -17,8 +17,8 @@
 
   public final class AndroidWindowInfo_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> collectWindowSizeAsState();
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
   }
 
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
diff --git a/compose/material3/material3-adaptive/benchmark/build.gradle b/compose/material3/material3-adaptive/benchmark/build.gradle
new file mode 100644
index 0000000..48f6444
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXComposePlugin")
+    id("org.jetbrains.kotlin.android")
+    id("androidx.benchmark")
+}
+
+dependencies {
+    androidTestImplementation(project(":compose:material3:material3-adaptive"))
+    androidTestImplementation(project(":benchmark:benchmark-junit4"))
+    androidTestImplementation(project(":compose:runtime:runtime"))
+    androidTestImplementation(project(":compose:benchmark-utils"))
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.kotlinStdlib)
+    androidTestImplementation(libs.kotlinTestCommon)
+    androidTestImplementation(libs.junit)
+}
+
+android {
+    namespace "androidx.compose.material3.adaptive.benchmark"
+}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
new file mode 100644
index 0000000..b2e7be5
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.material3.adaptive.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.adaptive.AnimatedPane
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+class ListDetailPaneScaffoldBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun singlePane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            ListDetailPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_navigateToDetail() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : ListDetailPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        currentDestination = ListDetailPaneScaffoldRole.Detail
+                    }
+                }.apply {
+                    currentScaffoldDirective = singlePaneDirective
+                }
+            },
+            assertOneRecomposition = false,
+        )
+    }
+
+    @Test
+    fun dualPane_navigateToExtra() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : ListDetailPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        currentDestination = ListDetailPaneScaffoldRole.Extra
+                    }
+                }.apply {
+                    currentScaffoldDirective = dualPaneDirective
+                }
+            },
+            assertOneRecomposition = false,
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal open class ListDetailPaneScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+    var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
+    var currentDestination by mutableStateOf(ListDetailPaneScaffoldRole.List)
+
+    @Composable
+    override fun MeasuredContent() {
+        ListDetailPaneScaffold(
+            scaffoldState = calculateListDetailPaneScaffoldState(
+                scaffoldDirective = currentScaffoldDirective,
+                currentPaneDestination = currentDestination
+            ),
+            listPane = {
+                AnimatedPane(
+                    modifier = Modifier.testTag(tag = "ListPane")
+                ) {
+                    Box(modifier = Modifier.fillMaxSize().background(Color.Red))
+                }
+            },
+            extraPane = {
+                AnimatedPane(
+                    modifier = Modifier.testTag(tag = "ExtraPane")
+                ) {
+                    Box(modifier = Modifier.fillMaxSize().background(Color.Blue))
+                }
+            }
+        ) {
+            AnimatedPane(
+                modifier = Modifier.testTag(tag = "DetailPane")
+            ) {
+                Box(modifier = Modifier.fillMaxSize().background(Color.Yellow))
+            }
+        }
+    }
+
+    override fun toggleState() {}
+}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
new file mode 100644
index 0000000..932422a
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.material3.adaptive.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.adaptive.AnimatedPane
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.SupportingPaneScaffold
+import androidx.compose.material3.adaptive.SupportingPaneScaffoldRole
+import androidx.compose.material3.adaptive.calculateSupportingPaneScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+class SupportingPaneScaffoldBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun singlePane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = singlePaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun dualPane_firstCompose() {
+        benchmarkRule.benchmarkFirstCompose {
+            SupportingPaneScaffoldTestCase().apply {
+                currentScaffoldDirective = dualPaneDirective
+            }
+        }
+    }
+
+    @Test
+    fun singlePane_navigateToSupporting() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : SupportingPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        currentDestination = SupportingPaneScaffoldRole.Supporting
+                    }
+                }.apply {
+                    currentScaffoldDirective = singlePaneDirective
+                }
+            },
+            assertOneRecomposition = false,
+        )
+    }
+
+    @Test
+    fun dualPane_navigateToExtra() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            {
+                object : SupportingPaneScaffoldTestCase() {
+                    override fun toggleState() {
+                        currentDestination = SupportingPaneScaffoldRole.Extra
+                    }
+                }.apply {
+                    currentScaffoldDirective = dualPaneDirective
+                }
+            },
+            assertOneRecomposition = false,
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal open class SupportingPaneScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+    var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
+    var currentDestination by mutableStateOf(SupportingPaneScaffoldRole.Main)
+
+    @Composable
+    override fun MeasuredContent() {
+        SupportingPaneScaffold(
+            scaffoldState = calculateSupportingPaneScaffoldState(
+                scaffoldDirective = currentScaffoldDirective,
+                currentPaneDestination = currentDestination
+            ),
+            supportingPane = {
+                AnimatedPane(
+                    modifier = Modifier.testTag(tag = "SupportingPane")
+                ) {
+                    Box(modifier = Modifier.fillMaxSize().background(Color.Red))
+                }
+            },
+            extraPane = {
+                AnimatedPane(
+                    modifier = Modifier.testTag(tag = "ExtraPane")
+                ) {
+                    Box(modifier = Modifier.fillMaxSize().background(Color.Blue))
+                }
+            }
+        ) {
+            AnimatedPane(
+                modifier = Modifier.testTag(tag = "MainPane")
+            ) {
+                Box(modifier = Modifier.fillMaxSize().background(Color.Yellow))
+            }
+        }
+    }
+
+    override fun toggleState() {}
+}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
new file mode 100644
index 0000000..4b2e283d
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.material3.adaptive.benchmark
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.PaneScaffoldDirective
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+val singlePaneDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(16.dp),
+    maxHorizontalPartitions = 1,
+    horizontalPartitionSpacerSize = 0.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+val dualPaneDirective = PaneScaffoldDirective(
+    contentPadding = PaddingValues(24.dp),
+    maxHorizontalPartitions = 2,
+    horizontalPartitionSpacerSize = 24.dp,
+    maxVerticalPartitions = 1,
+    verticalPartitionSpacerSize = 0.dp,
+    excludedBounds = emptyList()
+)
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
index ec1328d..8ff84ab 100644
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
+++ b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
@@ -58,7 +58,7 @@
             testConfiguration.screenWidthDp = mockWindowSize.value.width
             testConfiguration.screenHeightDp = mockWindowSize.value.height
             CompositionLocalProvider(LocalConfiguration provides testConfiguration) {
-                actualWindowSize = collectWindowSizeAsState().value
+                actualWindowSize = currentWindowSize()
             }
         }
 
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
index 6aa8ade..8ed7c70 100644
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
+++ b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
@@ -37,7 +37,7 @@
     setContent {
         val currentDensity = LocalDensity.current
         val windowSize = with(currentDensity) {
-            collectWindowSizeAsState().value.toSize().toDpSize();
+            currentWindowSize().toSize().toDpSize();
         }
         val simulatedDensity = Density(
             currentDensity.density * (windowSize.width / simulatedWidth)
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
index 5b7b34c..37ff942 100644
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
+++ b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
@@ -48,39 +47,31 @@
     WindowAdaptiveInfo(
         WindowSizeClass.calculateFromSize(
             with(LocalDensity.current) {
-                collectWindowSizeAsState().value.toSize().toDpSize()
+                currentWindowSize().toSize().toDpSize()
             }
         ),
         calculatePosture(collectFoldingFeaturesAsState().value)
     )
 
 /**
- * Collects the current window size from [WindowMetricsCalculator] in to a [State].
+ * Returns and automatically update the current window size from [WindowMetricsCalculator].
  *
- * @return a [State] of [IntSize] that represents the current window size.
+ * @return an [IntSize] that represents the current window size.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
-fun collectWindowSizeAsState(): State<IntSize> {
-    val size = remember {
-        mutableStateOf(IntSize(0, 0))
-    }
-
+fun currentWindowSize(): IntSize {
     // Observe view configuration changes and recalculate the size class on each change. We can't
     // use Activity#onConfigurationChanged as this will sometimes fail to be called on different
     // API levels, hence why this function needs to be @Composable so we can observe the
     // ComposeView's configuration changes.
-    val context = LocalContext.current
-    size.value = remember(context, LocalConfiguration.current) {
-        val windowBounds =
-            WindowMetricsCalculator
-                .getOrCreate()
-                .computeCurrentWindowMetrics(context)
-                .bounds
-        IntSize(windowBounds.width(), windowBounds.height())
-    }
-
-    return size
+    LocalConfiguration.current
+    val windowBounds =
+        WindowMetricsCalculator
+            .getOrCreate()
+            .computeCurrentWindowMetrics(LocalContext.current)
+            .bounds
+   return IntSize(windowBounds.width(), windowBounds.height())
 }
 
 /**
diff --git a/compose/material3/material3/api/1.2.0-beta01.txt b/compose/material3/material3/api/1.2.0-beta01.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/1.2.0-beta01.txt
+++ b/compose/material3/material3/api/1.2.0-beta01.txt
@@ -1097,9 +1097,6 @@
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ProgressIndicatorDefaults {
     method @androidx.compose.runtime.Composable public long getCircularColor();
     method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
     property public final long titleContentColor;
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ScaffoldDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
     method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
-    method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
-  }
-
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
     method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
 
   public final class TooltipKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
 
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1097,9 +1097,6 @@
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ProgressIndicatorDefaults {
     method @androidx.compose.runtime.Composable public long getCircularColor();
     method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
     property public final long titleContentColor;
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ScaffoldDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
     method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
-    method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
-  }
-
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
     method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
 
   public final class TooltipKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
 
diff --git a/compose/material3/material3/api/restricted_1.2.0-beta01.txt b/compose/material3/material3/api/restricted_1.2.0-beta01.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/restricted_1.2.0-beta01.txt
+++ b/compose/material3/material3/api/restricted_1.2.0-beta01.txt
@@ -1097,9 +1097,6 @@
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ProgressIndicatorDefaults {
     method @androidx.compose.runtime.Composable public long getCircularColor();
     method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
     property public final long titleContentColor;
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ScaffoldDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
     method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
-    method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
-  }
-
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
     method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
 
   public final class TooltipKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
 
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1097,9 +1097,6 @@
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ProgressIndicatorDefaults {
     method @androidx.compose.runtime.Composable public long getCircularColor();
     method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
     property public final long titleContentColor;
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
-  }
-
   public final class ScaffoldDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
     method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
-    method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
-  }
-
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
     method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
 
   public final class TooltipKt {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
 
diff --git a/compose/material3/material3/integration-tests/material3-catalog/build.gradle b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
index 1313927..4d02722 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/build.gradle
+++ b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
@@ -30,7 +30,7 @@
     implementation project(":compose:runtime:runtime")
     implementation project(":compose:foundation:foundation-layout")
     implementation project(":compose:ui:ui")
-    implementation project(":compose:material:material")
+    implementation("androidx.compose.material:material:1.6.0-beta01")
     implementation project(":compose:material:material-icons-extended")
     implementation project(":compose:material3:material3")
     implementation project(":compose:material3:material3:material3-samples")
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt
deleted file mode 100644
index 1074b7e..0000000
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.material3
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.window.Popup
-import androidx.compose.ui.window.PopupPositionProvider
-import androidx.compose.ui.window.PopupProperties
-
-@Composable
-@ExperimentalMaterial3Api
-internal actual fun TooltipPopup(
-    popupPositionProvider: PopupPositionProvider,
-    onDismissRequest: () -> Unit,
-    focusable: Boolean,
-    content: @Composable () -> Unit
-) = Popup(
-    popupPositionProvider = popupPositionProvider,
-    onDismissRequest = onDismissRequest,
-    content = content,
-    properties = PopupProperties(focusable = focusable)
-)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 9d4e08a..f0a309e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -25,9 +25,6 @@
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.waitForUpOrCancellation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
@@ -39,29 +36,17 @@
 import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
-import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.semantics.LiveRegionMode
-import androidx.compose.ui.semantics.liveRegion
-import androidx.compose.ui.semantics.onLongClick
-import androidx.compose.ui.semantics.paneTitle
-import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntRect
@@ -70,511 +55,10 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.PopupPositionProvider
 import kotlinx.coroutines.CancellableContinuation
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
 /**
- * Plain tooltip that provides a descriptive message for an anchor.
- *
- * Tooltip that is invoked when the anchor is long pressed:
- *
- * @sample androidx.compose.material3.samples.PlainTooltipSample
- *
- * If control of when the tooltip is shown is desired please see
- *
- * @sample androidx.compose.material3.samples.PlainTooltipWithManualInvocationSample
- *
- * @param tooltip the composable that will be used to populate the tooltip's content.
- * @param modifier the [Modifier] to be applied to the tooltip.
- * @param focusable [Boolean] that determines if the tooltip is focusable. When true,
- * the tooltip will consume touch events while it's shown and will have accessibility
- * focus move to the first element of the component. When false, the tooltip
- * won't consume touch events while it's shown but assistive-tech users will need
- * to swipe or drag to get to the first element of the component.
- * @param tooltipState handles the state of the tooltip's visibility.
- * @param shape the [Shape] that should be applied to the tooltip container.
- * @param containerColor [Color] that will be applied to the tooltip's container.
- * @param tonalElevation the tonal elevation of the tooltip.
- * @param shadowElevation the shadow elevation of the tooltip.
- * @param contentColor [Color] that will be applied to the tooltip's content.
- * @param content the composable that the tooltip will anchor to.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipBox with PlainTooltip")
-fun PlainTooltipBox(
-    tooltip: @Composable () -> Unit,
-    modifier: Modifier = Modifier,
-    focusable: Boolean = true,
-    tooltipState: PlainTooltipState = rememberPlainTooltipState(),
-    shape: Shape = TooltipDefaults.plainTooltipContainerShape,
-    containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
-    tonalElevation: Dp = 0.dp,
-    shadowElevation: Dp = 0.dp,
-    contentColor: Color = TooltipDefaults.plainTooltipContentColor,
-    content: @Composable TooltipBoxScope.() -> Unit
-) {
-    Material3TooltipBox(
-        tooltipContent = {
-            Box(modifier = Modifier.padding(PlainTooltipContentPadding)) {
-                val textStyle =
-                    MaterialTheme.typography.fromToken(PlainTooltipTokens.SupportingTextFont)
-                CompositionLocalProvider(
-                    LocalContentColor provides contentColor,
-                    LocalTextStyle provides textStyle,
-                    content = tooltip
-                )
-            }
-        },
-        modifier = modifier,
-        focusable = focusable,
-        tooltipState = tooltipState,
-        shape = shape,
-        containerColor = containerColor,
-        tooltipPositionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
-        tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation,
-        maxWidth = PlainTooltipMaxWidth,
-        content = content
-    )
-}
-
-/**
- * Rich text tooltip that allows the user to pass in a title, text, and action.
- * Tooltips are used to provide a descriptive message for an anchor.
- *
- * Tooltip that is invoked when the anchor is long pressed:
- *
- * @sample androidx.compose.material3.samples.RichTooltipSample
- *
- * If control of when the tooltip is shown is desired please see
- *
- * @sample androidx.compose.material3.samples.RichTooltipWithManualInvocationSample
- *
- * @param text the message to be displayed in the center of the tooltip.
- * @param modifier the [Modifier] to be applied to the tooltip.
- * @param focusable [Boolean] that determines if the tooltip is focusable. When true,
- * the tooltip will consume touch events while it's shown and will have accessibility
- * focus move to the first element of the component. When false, the tooltip
- * won't consume touch events while it's shown but assistive-tech users will need
- * to swipe or drag to get to the first element of the component.
- * @param tooltipState handles the state of the tooltip's visibility.
- * @param title An optional title for the tooltip.
- * @param action An optional action for the tooltip.
- * @param shape the [Shape] that should be applied to the tooltip container.
- * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
- * @param tonalElevation the tonal elevation of the tooltip.
- * @param shadowElevation the shadow elevation of the tooltip.
- * @param content the composable that the tooltip will anchor to.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipBox with RichTooltip")
-fun RichTooltipBox(
-    text: @Composable () -> Unit,
-    modifier: Modifier = Modifier,
-    focusable: Boolean = true,
-    title: (@Composable () -> Unit)? = null,
-    action: (@Composable () -> Unit)? = null,
-    tooltipState: RichTooltipState = rememberRichTooltipState(action != null),
-    shape: Shape = TooltipDefaults.richTooltipContainerShape,
-    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
-    tonalElevation: Dp = RichTooltipTokens.ContainerElevation,
-    shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
-    content: @Composable TooltipBoxScope.() -> Unit
-) {
-    Material3TooltipBox(
-        tooltipContent = {
-            val actionLabelTextStyle =
-                MaterialTheme.typography.fromToken(RichTooltipTokens.ActionLabelTextFont)
-            val subheadTextStyle =
-                MaterialTheme.typography.fromToken(RichTooltipTokens.SubheadFont)
-            val supportingTextStyle =
-                MaterialTheme.typography.fromToken(RichTooltipTokens.SupportingTextFont)
-            Column(
-                modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
-            ) {
-                title?.let {
-                    Box(
-                        modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
-                    ) {
-                        CompositionLocalProvider(
-                            LocalContentColor provides colors.titleContentColor,
-                            LocalTextStyle provides subheadTextStyle,
-                            content = it
-                        )
-                    }
-                }
-                Box(
-                    modifier = Modifier.textVerticalPadding(title != null, action != null)
-                ) {
-                    CompositionLocalProvider(
-                        LocalContentColor provides colors.contentColor,
-                        LocalTextStyle provides supportingTextStyle,
-                        content = text
-                    )
-                }
-                action?.let {
-                    Box(
-                        modifier = Modifier
-                            .requiredHeightIn(min = ActionLabelMinHeight)
-                            .padding(bottom = ActionLabelBottomPadding)
-                    ) {
-                        CompositionLocalProvider(
-                            LocalContentColor provides colors.actionContentColor,
-                            LocalTextStyle provides actionLabelTextStyle,
-                            content = it
-                        )
-                    }
-                }
-            }
-        },
-        shape = shape,
-        containerColor = colors.containerColor,
-        tooltipPositionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
-        tooltipState = tooltipState,
-        tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation,
-        maxWidth = RichTooltipMaxWidth,
-        modifier = modifier,
-        focusable = focusable,
-        content = content
-    )
-}
-
-@Suppress("DEPRECATION")
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun Material3TooltipBox(
-    tooltipContent: @Composable () -> Unit,
-    tooltipPositionProvider: PopupPositionProvider,
-    modifier: Modifier,
-    focusable: Boolean,
-    shape: Shape,
-    tooltipState: BasicTooltipState,
-    containerColor: Color,
-    tonalElevation: Dp,
-    shadowElevation: Dp,
-    maxWidth: Dp,
-    content: @Composable TooltipBoxScope.() -> Unit,
-) {
-    val coroutineScope = rememberCoroutineScope()
-    val longPressLabel = getString(string = Strings.TooltipLongPressLabel)
-
-    val scope = remember(tooltipState) {
-        object : TooltipBoxScope {
-            override fun Modifier.tooltipTrigger(): Modifier {
-                val onLongPress = {
-                    coroutineScope.launch {
-                        tooltipState.show()
-                    }
-                }
-                return pointerInput(tooltipState) {
-                    awaitEachGesture {
-                        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
-                        val pass = PointerEventPass.Initial
-
-                        // wait for the first down press
-                        awaitFirstDown(pass = pass)
-
-                        try {
-                            // listen to if there is up gesture within the longPressTimeout limit
-                            withTimeout(longPressTimeout) {
-                                waitForUpOrCancellation(pass = pass)
-                            }
-                        } catch (_: PointerEventTimeoutCancellationException) {
-                            // handle long press - Show the tooltip
-                            onLongPress()
-
-                            // consume the children's click handling
-                            val changes = awaitPointerEvent(pass = pass).changes
-                            for (i in 0 until changes.size) { changes[i].consume() }
-                        }
-                    }
-                }.semantics(mergeDescendants = true) {
-                    onLongClick(
-                        label = longPressLabel,
-                        action = {
-                            onLongPress()
-                            true
-                        }
-                    )
-                }
-            }
-        }
-    }
-
-    Box {
-        val transition = updateTransition(tooltipState.isVisible, label = "Tooltip transition")
-        if (transition.currentState || transition.targetState) {
-            val tooltipPaneDescription = getString(Strings.TooltipPaneDescription)
-            TooltipPopup(
-                popupPositionProvider = tooltipPositionProvider,
-                onDismissRequest = {
-                    if (tooltipState.isVisible) {
-                        coroutineScope.launch { tooltipState.dismiss() }
-                    }
-                },
-                focusable = focusable
-            ) {
-                Surface(
-                    modifier = modifier
-                        .sizeIn(
-                            minWidth = TooltipMinWidth,
-                            maxWidth = maxWidth,
-                            minHeight = TooltipMinHeight
-                        )
-                        .animateTooltip(transition)
-                        .semantics {
-                            liveRegion = LiveRegionMode.Assertive
-                            paneTitle = tooltipPaneDescription
-                        },
-                    shape = shape,
-                    color = containerColor,
-                    tonalElevation = tonalElevation,
-                    shadowElevation = shadowElevation,
-                    content = tooltipContent
-                )
-            }
-        }
-
-        scope.content()
-    }
-
-    DisposableEffect(tooltipState) {
-        onDispose { tooltipState.onDispose() }
-    }
-}
-
-/**
- * Scope of [PlainTooltipBox] and RichTooltipBox
- */
-@Suppress("DEPRECATION")
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipBox with enableUserInput to handle default gestures")
-interface TooltipBoxScope {
-    /**
-     * [Modifier] that should be applied to the anchor composable when showing the tooltip
-     * after long pressing the anchor composable is desired. It appends a long click to
-     * the composable that this modifier is chained with.
-     */
-    fun Modifier.tooltipTrigger(): Modifier
-}
-
-@Composable
-@ExperimentalMaterial3Api
-internal expect fun TooltipPopup(
-    popupPositionProvider: PopupPositionProvider,
-    onDismissRequest: () -> Unit,
-    focusable: Boolean,
-    content: @Composable () -> Unit
-)
-
-/**
- * Create and remember the default [PlainTooltipState].
- *
- * @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
- * with the mutator mutex, only one will be shown on the screen at any time.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use rememberTooltipState with TooltipBox.")
-fun rememberPlainTooltipState(
-    mutatorMutex: MutatorMutex = BasicTooltipDefaults.GlobalMutatorMutex
-): PlainTooltipState =
-    remember(mutatorMutex) { PlainTooltipStateImpl(mutatorMutex) }
-
-/**
- * Create and remember the default [RichTooltipState].
- *
- * @param isPersistent [Boolean] that determines if the tooltip associated with this
- * [RichTooltipState] will be persistent or not. If isPersistent is true, then the tooltip will
- * only be dismissed when the user clicks outside the bounds of the tooltip or if
- * [TooltipState.dismiss] is called. When isPersistent is false, the tooltip will dismiss after
- * a short duration. Ideally, this should be set to true when an action is provided to the
- * [RichTooltipBox] that this [RichTooltipState] is associated with.
- * @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
- * with the mutator mutex, only one will be shown on the screen at any time.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use rememberTooltipState with TooltipBox")
-fun rememberRichTooltipState(
-    isPersistent: Boolean,
-    mutatorMutex: MutatorMutex = BasicTooltipDefaults.GlobalMutatorMutex
-): RichTooltipState =
-    remember(
-        isPersistent,
-        mutatorMutex
-    ) { RichTooltipStateImpl(isPersistent = isPersistent, mutatorMutex = mutatorMutex) }
-
-/**
- * The default implementation for [RichTooltipState]
- *
- * @param isPersistent [Boolean] that determines if the tooltip associated with this
- * [RichTooltipState] will be persistent or not. If isPersistent is true, then the tooltip will
- * only be dismissed when the user clicks outside the bounds of the tooltip or if
- * [TooltipState.dismiss] is called. When isPersistent is false, the tooltip will dismiss after
- * a short duration. Ideally, this should be set to true when an action is provided to the
- * [RichTooltipBox] that this [RichTooltipState] is associated with.
- * @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
- * with the mutator mutex, only one will be shown on the screen at any time.
- */
-@Suppress("DEPRECATION")
-@OptIn(ExperimentalMaterial3Api::class)
-@Stable
-internal class RichTooltipStateImpl(
-    override val isPersistent: Boolean,
-    private val mutatorMutex: MutatorMutex
-) : RichTooltipState {
-
-    /**
-     * [Boolean] that will be used to update the visibility
-     * state of the associated tooltip.
-     */
-    override var isVisible: Boolean by mutableStateOf(false)
-        private set
-
-    /**
-     * Show the tooltip associated with the current [RichTooltipState].
-     * It will persist or dismiss after a short duration depending on [isPersistent].
-     * When this method is called, all of the other tooltips currently
-     * being shown will dismiss.
-     */
-    override suspend fun show(mutatePriority: MutatePriority) {
-        val cancellableShow: suspend () -> Unit = {
-            suspendCancellableCoroutine { continuation ->
-                isVisible = true
-                job = continuation
-            }
-        }
-
-        // Show associated tooltip for [TooltipDuration] amount of time
-        // or until tooltip is explicitly dismissed depending on [isPersistent].
-        mutatorMutex.mutate(mutatePriority) {
-            try {
-                if (isPersistent) {
-                    cancellableShow()
-                } else {
-                    withTimeout(BasicTooltipDefaults.TooltipDuration) {
-                        cancellableShow()
-                    }
-                }
-            } finally {
-                // timeout or cancellation has occurred
-                // and we close out the current tooltip.
-                isVisible = false
-            }
-        }
-    }
-
-    /**
-     * continuation used to clean up
-     */
-    private var job: (CancellableContinuation<Unit>)? = null
-
-    /**
-     * Dismiss the tooltip associated with
-     * this [RichTooltipState] if it's currently being shown.
-     */
-    override fun dismiss() {
-        isVisible = false
-    }
-
-    /**
-     * Cleans up [MutatorMutex] when the tooltip associated
-     * with this state leaves Composition.
-     */
-    override fun onDispose() {
-        job?.cancel()
-    }
-}
-
-/**
- * The default implementation for [PlainTooltipState]
- */
-@Suppress("DEPRECATION")
-@OptIn(ExperimentalMaterial3Api::class)
-@Stable
-internal class PlainTooltipStateImpl(private val mutatorMutex: MutatorMutex) : PlainTooltipState {
-
-    /**
-     * [Boolean] that will be used to update the visibility
-     * state of the associated tooltip.
-     */
-    override var isVisible by mutableStateOf(false)
-        private set
-
-    override val isPersistent: Boolean = false
-
-    /**
-     * Show the tooltip associated with the current [PlainTooltipState].
-     * It will dismiss after a short duration. When this method is called,
-     * all of the other tooltips currently being shown will dismiss.
-     */
-    override suspend fun show(mutatePriority: MutatePriority) {
-        mutatorMutex.mutate(mutatePriority) {
-            try {
-                withTimeout(BasicTooltipDefaults.TooltipDuration) {
-                    suspendCancellableCoroutine { continuation ->
-                        isVisible = true
-                        job = continuation
-                    }
-                }
-            } finally {
-                // timeout or cancellation has occurred
-                // and we close out the current tooltip.
-                isVisible = false
-            }
-        }
-    }
-
-    /**
-     * continuation used to clean up
-     */
-    private var job: (CancellableContinuation<Unit>)? = null
-
-    /**
-     * Dismiss the tooltip associated with
-     * this [PlainTooltipState] if it's currently being shown.
-     */
-    override fun dismiss() {
-        isVisible = false
-    }
-
-    /**
-     * Cleans up [MutatorMutex] when the tooltip associated
-     * with this state leaves Composition.
-     */
-    override fun onDispose() {
-        job?.cancel()
-    }
-}
-
-/**
- * The [TooltipState] that should be used with [RichTooltipBox]
- */
-@Suppress("DEPRECATION")
-@Stable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipState with TooltipBox")
-interface PlainTooltipState : BasicTooltipState
-
-/**
- * The [TooltipState] that should be used with [RichTooltipBox]
- */
-@Suppress("DEPRECATION")
-@Stable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipState with TooltipBox")
-interface RichTooltipState : BasicTooltipState
-
-/**
  * Material TooltipBox that wraps a composable with a tooltip.
  *
  * tooltips provide a descriptive message for an anchor.
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt
deleted file mode 100644
index cd83c7c..0000000
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.material3
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.window.Popup
-import androidx.compose.ui.window.PopupPositionProvider
-
-@Composable
-@ExperimentalMaterial3Api
-internal actual fun TooltipPopup(
-    popupPositionProvider: PopupPositionProvider,
-    onDismissRequest: () -> Unit,
-    focusable: Boolean,
-    content: @Composable () -> Unit
-) = Popup(
-    popupPositionProvider = popupPositionProvider,
-    onDismissRequest = onDismissRequest,
-    content = content
-)
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
index 635450c..2da7184 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
@@ -31,6 +31,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.material.AlertDialog
 import androidx.compose.material.Button
@@ -60,17 +61,21 @@
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.inspection.compose.flatten
 import androidx.compose.ui.inspection.testdata.TestActivity
+import androidx.compose.ui.inspection.util.ThreadUtils
 import androidx.compose.ui.layout.GraphicLayerInfo
 import androidx.compose.ui.node.Ref
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.text
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToIndex
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.Font
@@ -974,7 +979,36 @@
         val builder = LayoutInspectorTree()
         builder.hideSystemNodes = false
         builder.includeAllParameters = false
-        builder.convert(androidComposeView)
+    }
+
+    @Test // regression test for b/311436726
+    fun testLazyColumn() {
+        val slotTableRecord = CompositionDataRecord.create()
+
+        show {
+            Inspectable(slotTableRecord) {
+                LazyColumn(modifier = Modifier.testTag("LazyColumn")) {
+                    items(100) { index ->
+                        Text(text = "Item: $index")
+                    }
+                }
+            }
+        }
+
+        val androidComposeView = findAndroidComposeView()
+        androidComposeView.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
+        val builder = LayoutInspectorTree()
+        builder.hideSystemNodes = false
+        builder.includeAllParameters = true
+        ThreadUtils.runOnMainThread {
+            builder.convert(androidComposeView)
+        }
+        for (index in 20..40) {
+            composeTestRule.onNodeWithTag("LazyColumn").performScrollToIndex(index)
+        }
+        ThreadUtils.runOnMainThread {
+            builder.convert(androidComposeView)
+        }
     }
 
     @Suppress("SameParameterValue")
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
index a36e3d6..8a72392 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
@@ -503,7 +503,7 @@
         val size = box.size.toSize()
         val coordinates = layoutInfo.coordinates
         var bounds: QuadBounds? = null
-        if (layoutInfo.isAttached) {
+        if (layoutInfo.isAttached && coordinates.isAttached) {
             val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
             val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
             val bottomRight =
diff --git a/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt b/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt
index d2505db..c439766 100644
--- a/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt
+++ b/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt
@@ -557,7 +557,8 @@
 }
 
 private fun boundsOfLayoutNode(node: LayoutInfo): IntRect {
-    if (!node.isAttached) {
+    val coordinates = node.coordinates
+    if (!node.isAttached || !coordinates.isAttached) {
         return IntRect(
             left = 0,
             top = 0,
@@ -565,8 +566,8 @@
             bottom = node.height
         )
     }
-    val position = node.coordinates.positionInWindow()
-    val size = node.coordinates.size
+    val position = coordinates.positionInWindow()
+    val size = coordinates.size
     val left = position.x.roundToInt()
     val top = position.y.roundToInt()
     val right = left + size.width
diff --git a/development/build_log_simplifier/message-flakes.ignore b/development/build_log_simplifier/message-flakes.ignore
index ec9a2fc..21a9218 100644
--- a/development/build_log_simplifier/message-flakes.ignore
+++ b/development/build_log_simplifier/message-flakes.ignore
@@ -42,11 +42,6 @@
 [0-9]+ problems were found reusing the configuration cache\.
 [0-9]+ problem was found reusing the configuration cache\.
 Configuration cache entry reused with [0-9]+ problem\.
-# > Task :webkit:integration-tests:testapp:compileReleaseJavaWithJavac
-\[ant\:jacocoReport\] Note\: Some input files use or override a deprecated API\.
-\[ant\:jacocoReport\] Note\: Recompile with \-Xlint\:deprecation for details\.
-\[ant\:jacocoReport\] Note\: Some input files use unchecked or unsafe operations\.
-\[ant\:jacocoReport\] Note\: Recompile with \-Xlint\:unchecked for details\.
 # b/179833331 , https://youtrack.jetbrains.com/issue/KT-35156
 # b/181258249 , https://youtrack.jetbrains.com/issue/KT-43881
 \.\.\. [0-9]+ more
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt
index 1181f98..190983d 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt
@@ -20,10 +20,8 @@
 import androidx.glance.text.Text
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.UiSelector
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runTest
 import org.junit.After
@@ -32,7 +30,6 @@
 import org.junit.Rule
 import org.junit.Test
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SdkSuppress(minSdkVersion = 29)
 @MediumTest
 class GlanceAppWidgetManagerTest {
@@ -47,45 +44,53 @@
 
     @After
     fun tearDown() {
-        getInstrumentation().context.startActivity(Intent(Intent.ACTION_MAIN).apply {
+        context.startActivity(Intent(Intent.ACTION_MAIN).apply {
             addCategory(Intent.CATEGORY_HOME)
             flags = Intent.FLAG_ACTIVITY_NEW_TASK
         })
     }
 
     @Test
-    fun noAppWidget() {
-        mHostRule.onHostActivity { activity ->
-            val manager = GlanceAppWidgetManager(activity)
-            runBlocking {
-                assertThat(manager.getGlanceIds(TestGlanceAppWidget::class.java)).isEmpty()
-            }
-        }
+    fun noAppWidget() = runBlocking {
+        val manager = GlanceAppWidgetManager(context)
+
+        assertThat(manager.getGlanceIds(TestGlanceAppWidget::class.java)).isEmpty()
     }
 
     @Test
-    fun withAppWidget() {
+    fun withAppWidget() = runBlocking {
         TestGlanceAppWidget.uiDefinition = {
             Text("Something")
         }
+        val manager = GlanceAppWidgetManager(context)
+
+        suspend fun verifyGlanceIdsAndSizes() {
+            val glanceIds = manager.getGlanceIds(TestGlanceAppWidget::class.java)
+            assertThat(glanceIds).hasSize(1)
+            assertThat(manager.listKnownReceivers()).containsExactly(
+                TestGlanceAppWidgetReceiver::class.java.canonicalName
+            )
+
+            val glanceId = manager.getGlanceIdBy((glanceIds[0] as AppWidgetId).appWidgetId)
+            assertThat(glanceId).isEqualTo(glanceIds[0])
+
+            val sizes = manager.getAppWidgetSizes(glanceIds[0])
+            assertThat(sizes).containsExactly(
+                mHostRule.portraitSize,
+                mHostRule.landscapeSize
+            )
+        }
 
         mHostRule.startHost()
 
-        mHostRule.onHostActivity { activity ->
-            val manager = GlanceAppWidgetManager(activity)
+        verifyGlanceIdsAndSizes()
 
-            runBlocking {
-                val glanceIds = manager.getGlanceIds(TestGlanceAppWidget::class.java)
-                assertThat(glanceIds).hasSize(1)
-                val glanceId = manager.getGlanceIdBy((glanceIds[0] as AppWidgetId).appWidgetId)
-                assertThat(glanceId).isEqualTo(glanceIds[0])
-                val sizes = manager.getAppWidgetSizes(glanceIds[0])
-                assertThat(sizes).containsExactly(
-                    mHostRule.portraitSize,
-                    mHostRule.landscapeSize
-                )
-            }
-        }
+        // using "pm clear <package>" is not suitable here - it will crash process.
+        // See https://github.com/android/testing-samples/issues/98
+        // So, we clear datastore to mimic clearing appData.
+        manager.clearDataStore()
+
+        verifyGlanceIdsAndSizes()
     }
 
     @Test
@@ -99,6 +104,7 @@
             TestGlanceAppWidgetReceiver::class.java,
             preview = TestGlanceAppWidget
         )
+
         assertThat(result).isTrue()
         mHostRule.onHostActivity {
             assertThat(mHostRule.device.findObject(UiSelector().text(text)).exists())
@@ -110,28 +116,27 @@
         val result = GlanceAppWidgetManager(context).requestPinGlanceAppWidget(
             DummyGlanceAppWidgetReceiver::class.java,
         )
+
         assertThat(result).isFalse()
     }
 
     @Ignore("b/285198114")
     @Test
-    fun cleanReceivers() {
-        mHostRule.onHostActivity { activity ->
-            val manager = GlanceAppWidgetManager(activity)
+    fun cleanReceivers() = runBlocking<Unit> {
+        val manager = GlanceAppWidgetManager(context)
 
-            runBlocking {
-                manager.updateReceiver(DummyGlanceAppWidgetReceiver(), TestGlanceAppWidget)
-                assertThat(manager.listKnownReceivers()).containsExactly(
-                    DummyGlanceAppWidgetReceiver::class.java.canonicalName,
-                    TestGlanceAppWidgetReceiver::class.java.canonicalName
-                )
+        manager.updateReceiver(DummyGlanceAppWidgetReceiver(), TestGlanceAppWidget)
 
-                manager.cleanReceivers()
-                assertThat(manager.listKnownReceivers()).containsExactly(
-                    TestGlanceAppWidgetReceiver::class.java.canonicalName
-                )
-            }
-        }
+        assertThat(manager.listKnownReceivers()).containsExactly(
+            DummyGlanceAppWidgetReceiver::class.java.canonicalName,
+            TestGlanceAppWidgetReceiver::class.java.canonicalName
+        )
+
+        manager.cleanReceivers()
+
+        assertThat(manager.listKnownReceivers()).containsExactly(
+            TestGlanceAppWidgetReceiver::class.java.canonicalName
+        )
     }
 }
 
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
index d344989..934465e 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
@@ -54,6 +54,9 @@
  * checked is provided to this action in its ActionParameters, and can be retrieved using the
  * [ToggleableStateKey]. If this action launches an activity, the current value of checked will be
  * passed as an intent extra with the name [RemoteViews.EXTRA_CHECKED].
+ * In order to allow the Launcher to provide this extra on Android version S and later, we use a
+ * mutable PendingIntent ([android.app.PendingIntent.FLAG_MUTABLE]) when this action is not a
+ * lambda. Before S, and for lambda actions, this will be an immutable PendingIntent.
  * @param modifier the modifier to apply to the check box
  * @param text the text to display to the end of the check box
  * @param style the style to apply to [text]
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
index 41c7633..9f86183 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent
 import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
@@ -29,10 +30,12 @@
 import androidx.compose.ui.unit.DpSize
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.stringPreferencesKey
 import androidx.datastore.preferences.core.stringSetPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
 import androidx.glance.GlanceId
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.firstOrNull
 
 /**
@@ -70,8 +73,8 @@
         receiver: R,
         provider: P,
     ) {
-        val receiverName = requireNotNull(receiver.javaClass.canonicalName) { "no receiver name" }
-        val providerName = requireNotNull(provider.javaClass.canonicalName) { "no provider name" }
+        val receiverName = receiver.canonicalName()
+        val providerName = provider.canonicalName()
         dataStore.updateData { pref ->
             pref.toMutablePreferences().also { builder ->
                 builder[providersKey] = (pref[providersKey] ?: emptySet()) + receiverName
@@ -92,8 +95,20 @@
         )
     }
 
-    private suspend fun getState() =
-        dataStore.data.firstOrNull()?.let { createState(it) } ?: State()
+    private suspend fun getState(): State {
+        // Preferences won't contain value for providersKey when either -
+        // 1. App doesn't have any widget placed, but app requested for glanceIds for a widget class
+        // 2. User cleared app data, so, the provider to receivers mapping is lost (even if widgets
+        // are still pinned).
+        // In case of #2, we want to return an appropriate list of glance ids, so, we back-fill the
+        // datastore with all known glance receivers and providers.
+        // #1 isn't something that an app would commonly do, and even if it does, it would get empty
+        // IDs as expected.
+        return createState(
+            prefs = dataStore.data.first().takeIf { it[providersKey] != null }
+                ?: addAllReceiversAndProvidersToPreferences()
+        )
+    }
 
     /**
      * Returns the [GlanceId] of the App Widgets installed for a particular provider.
@@ -237,10 +252,53 @@
         }
     }
 
+    /**
+     * Identifies [GlanceAppWidget] (provider) for each [GlanceAppWidgetReceiver] in the app and
+     * saves the mapping in the preferences datastore. Also stores the set of glance-based
+     * receiver class names.
+     *
+     * [getGlanceIds] looks up the set of associated receivers for the given [GlanceAppWidget]
+     * (provider) from the datastore to be able to get the appwidget ids from [AppWidgetManager].
+     *
+     * Typically, the information is stored / overwritten by [updateReceiver] during widget
+     * lifecycle, however, when app data is cleared by the user, it is lost. So, we reconstruct it
+     * (for all known glance-based receivers).
+     *
+     * Follow b/305232907 to know the recommendation from appWidgets on handling cleared app data
+     * scenarios for widgets.
+     */
+    @Suppress("ListIterator")
+    private suspend fun addAllReceiversAndProvidersToPreferences(): Preferences {
+        val installedGlanceAppWidgetReceivers = appWidgetManager.installedProviders
+            .filter { it.provider.packageName == context.packageName }
+            .mapNotNull { it.maybeGlanceAppWidgetReceiver() }
+
+        return dataStore.updateData { prefs ->
+            prefs.toMutablePreferences().apply {
+                this[providersKey] =
+                    installedGlanceAppWidgetReceivers.map { it.javaClass.name }.toSet()
+                installedGlanceAppWidgetReceivers.forEach {
+                    this[providerKey(it.canonicalName())] = it.glanceAppWidget.canonicalName()
+                }
+            }.toPreferences()
+        }
+    }
+
     @VisibleForTesting
     internal suspend fun listKnownReceivers(): Collection<String>? =
         dataStore.data.firstOrNull()?.let { it[providersKey] }
 
+    /**
+     * Clears the datastore that holds information about glance app widgets (providers) and
+     * receivers.
+     *
+     * Useful for tests that wish to mimic clearing app data.
+     */
+    @VisibleForTesting
+    internal suspend fun clearDataStore() {
+        dataStore.edit { it.clear() }
+    }
+
     private companion object {
         private val Context.appManagerDataStore
             by preferencesDataStore(name = "GlanceAppWidgetManager")
@@ -248,6 +306,20 @@
         private val providersKey = stringSetPreferencesKey("list::Providers")
 
         private fun providerKey(provider: String) = stringPreferencesKey("provider:$provider")
+
+        private fun GlanceAppWidgetReceiver.canonicalName() =
+            requireNotNull(this.javaClass.canonicalName) { "no receiver name" }
+
+        private fun GlanceAppWidget.canonicalName() =
+            requireNotNull(this.javaClass.canonicalName) { "no provider name" }
+
+        private fun AppWidgetProviderInfo.maybeGlanceAppWidgetReceiver(): GlanceAppWidgetReceiver? {
+            val receiver = Class.forName(provider.className).getDeclaredConstructor().newInstance()
+            if (receiver is GlanceAppWidgetReceiver) {
+                return receiver
+            }
+            return null
+        }
     }
 
     @RequiresApi(Build.VERSION_CODES.O)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
index 44f7f99..2e43830 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
@@ -54,6 +54,9 @@
  * checked is provided to this action in its ActionParameters, and can be retrieved using the
  * [ToggleableStateKey]. If this action launches an activity, the current value of checked will be
  * passed as an intent extra with the name [RemoteViews.EXTRA_CHECKED].
+ * In order to allow the Launcher to provide this extra on Android version S and later, we use a
+ * mutable PendingIntent ([android.app.PendingIntent.FLAG_MUTABLE]) when this action is not a
+ * lambda. Before S, and for lambda actions, this will be an immutable PendingIntent.
  * @param modifier the modifier to apply to the switch
  * @param text the text to display to the end of the switch
  * @param style the style to apply to [text]
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
index 6e2a201..7a94b75 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
@@ -75,6 +75,7 @@
     translationContext: TranslationContext,
     @IdRes viewId: Int,
     editParams: (ActionParameters) -> ActionParameters = { it },
+    mutability: Int = PendingIntent.FLAG_IMMUTABLE,
 ): PendingIntent {
     when (action) {
         is StartActivityAction -> {
@@ -93,7 +94,7 @@
                         )
                     }
                 },
-                PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+                mutability or PendingIntent.FLAG_UPDATE_CURRENT,
                 action.activityOptions,
             )
         }
@@ -119,7 +120,7 @@
                     translationContext.context,
                     0,
                     intent,
-                    PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+                    mutability or PendingIntent.FLAG_UPDATE_CURRENT,
                 )
             }
         }
@@ -136,7 +137,7 @@
                         )
                     }
                 },
-                PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+                mutability or PendingIntent.FLAG_UPDATE_CURRENT,
             )
         }
         is RunCallbackAction -> {
@@ -156,7 +157,7 @@
                             ActionTrampolineType.CALLBACK,
                         )
                 },
-                PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+                mutability or PendingIntent.FLAG_UPDATE_CURRENT,
             )
         }
         is LambdaAction -> {
@@ -179,7 +180,7 @@
                             action.key,
                         )
                 },
-                PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+                mutability or PendingIntent.FLAG_UPDATE_CURRENT,
             )
         }
         is CompoundButtonAction -> {
@@ -188,6 +189,15 @@
                 translationContext,
                 viewId,
                 action.getActionParameters(),
+                mutability = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
+                    action.innerAction !is LambdaAction) {
+                    // RemoteViews.setOnCheckedChangedResponse (API 31+) requires a mutable
+                    // PendingIntent in order to set the EXTRA_CHECKED extra with the current state
+                    // of the button. Lambda actions do not use this extra so they can be immutable.
+                    PendingIntent.FLAG_MUTABLE
+                } else {
+                    mutability
+                }
             )
         }
         else -> error("Cannot create PendingIntent for action type: $action")
@@ -370,7 +380,7 @@
             context,
             0,
             intent,
-            PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
         )
     }
 }
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ef9344a..073053b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -27,14 +27,14 @@
 byteBuddy = "1.12.10"
 asm = "9.3"
 cmake = "3.22.1"
-dagger = "2.48"
+dagger = "2.49"
 dexmaker = "2.28.3"
 dokka = "1.8.20-dev-214"
 espresso = "3.6.0-alpha01"
 espressoDevice = "1.0.0-alpha05"
 grpc = "1.52.0"
 guavaJre = "31.1-jre"
-hilt = "2.48"
+hilt = "2.49"
 incap = "0.2"
 jcodec = "0.2.5"
 kotlin17 = "1.7.10"
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
index 3637e56..d4d4753 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
@@ -650,9 +650,34 @@
                 assertNotNull(textureView)
 
                 glRenderer = GLRenderer().apply { start() }
-                target = glRenderer!!.attach(it.textureView, ColorRenderCallback(Color.BLUE) {
-                    renderLatch.get().countDown()
-                })
+                target = glRenderer!!.attach(it.textureView, ColorRenderCallback(Color.BLUE))
+                val listener = textureView!!.surfaceTextureListener
+                textureView!!.surfaceTextureListener = object : SurfaceTextureListener {
+                    override fun onSurfaceTextureAvailable(
+                        surface: SurfaceTexture,
+                        width: Int,
+                        height: Int
+                    ) {
+                        listener?.onSurfaceTextureAvailable(surface, width, height)
+                    }
+
+                    override fun onSurfaceTextureSizeChanged(
+                        surface: SurfaceTexture,
+                        width: Int,
+                        height: Int
+                    ) {
+                        listener?.onSurfaceTextureSizeChanged(surface, width, height)
+                    }
+
+                    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+                        return listener?.onSurfaceTextureDestroyed(surface) ?: true
+                    }
+
+                    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
+                        listener?.onSurfaceTextureUpdated(surface)
+                        renderLatch.get().countDown()
+                    }
+                }
             }
 
         scenario.moveToState(State.RESUMED)
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
index a63e68b..81c25e2 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
@@ -1725,59 +1725,25 @@
         createTransaction: (SurfaceView) -> SurfaceControlCompat.Transaction,
         verifyOutput: (Bitmap, Rect) -> Boolean
     ) {
-        val transactionLatch = CountDownLatch(1)
-        var surfaceView: SurfaceView? = null
-        val destroyLatch = CountDownLatch(1)
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                it.setDestroyCallback { destroyLatch.countDown() }
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        surfaceView = it.mSurfaceView
-                        val transaction = createTransaction(surfaceView!!)
-                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-                            transaction.addTransactionCommittedListener(
-                                executor!!,
-                                object : SurfaceControlCompat.TransactionCommittedListener {
-                                    override fun onTransactionCommitted() {
-                                        transactionLatch.countDown()
-                                    }
-                                }
-                            )
-                        } else {
-                            transactionLatch.countDown()
+        SurfaceControlUtils.surfaceControlTestHelper(
+            { surfaceView, latch ->
+                val transaction = createTransaction(surfaceView)
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                    transaction.addTransactionCommittedListener(
+                        executor!!,
+                        object : SurfaceControlCompat.TransactionCommittedListener {
+                            override fun onTransactionCommitted() {
+                                latch.countDown()
+                            }
                         }
-                        transaction.commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-                surfaceView = it.mSurfaceView
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED)
-
-        assertTrue(transactionLatch.await(3000, TimeUnit.MILLISECONDS))
-        val coords = intArrayOf(0, 0)
-        surfaceView!!.getLocationOnScreen(coords)
-        try {
-            SurfaceControlUtils.validateOutput { bitmap ->
-                verifyOutput(
-                    bitmap,
-                    Rect(
-                        coords[0],
-                        coords[1],
-                        coords[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                        coords[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
                     )
-                )
-            }
-        } finally {
-            scenario.moveToState(Lifecycle.State.DESTROYED)
-            assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
-        }
+                } else {
+                    latch.countDown()
+                }
+                transaction.commit()
+            },
+            verifyOutput
+        )
     }
 
     fun Color.compositeOver(background: Color): Color {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
index 4e89941..74031ed 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
@@ -19,19 +19,86 @@
 import android.app.Instrumentation
 import android.graphics.Bitmap
 import android.graphics.Color
+import android.graphics.Rect
 import android.hardware.HardwareBuffer
 import android.os.Build
 import android.os.SystemClock
+import android.view.SurfaceHolder
+import android.view.SurfaceView
 import android.view.Window
 import androidx.annotation.RequiresApi
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import org.junit.Assert
 
 @SdkSuppress(minSdkVersion = 29)
 internal class SurfaceControlUtils {
     companion object {
 
+        @RequiresApi(Build.VERSION_CODES.Q)
+        fun surfaceControlTestHelper(
+            onSurfaceCreated: (SurfaceView, CountDownLatch) -> Unit,
+            verifyOutput: (Bitmap, Rect) -> Boolean
+        ) {
+            val setupLatch = CountDownLatch(1)
+            var surfaceView: SurfaceView? = null
+            val destroyLatch = CountDownLatch(1)
+            val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
+                .moveToState(
+                    Lifecycle.State.CREATED
+                ).onActivity {
+                    it.setDestroyCallback { destroyLatch.countDown() }
+                    val callback = object : SurfaceHolder.Callback {
+                        override fun surfaceCreated(sh: SurfaceHolder) {
+                            surfaceView = it.mSurfaceView
+                            onSurfaceCreated(surfaceView!!, setupLatch)
+                        }
+
+                        override fun surfaceChanged(
+                            holder: SurfaceHolder,
+                            format: Int,
+                            width: Int,
+                            height: Int
+                        ) {
+                            // NO-OP
+                        }
+
+                        override fun surfaceDestroyed(holder: SurfaceHolder) {
+                            // NO-OP
+                        }
+                    }
+
+                    it.addSurface(it.mSurfaceView, callback)
+                    surfaceView = it.mSurfaceView
+                }
+
+            scenario.moveToState(Lifecycle.State.RESUMED)
+
+            Assert.assertTrue(setupLatch.await(3000, TimeUnit.MILLISECONDS))
+            val coords = intArrayOf(0, 0)
+            surfaceView!!.getLocationOnScreen(coords)
+            try {
+                validateOutput { bitmap ->
+                    verifyOutput(
+                        bitmap,
+                        Rect(
+                            coords[0],
+                            coords[1],
+                            coords[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            coords[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
+                        )
+                    )
+                }
+            } finally {
+                scenario.moveToState(Lifecycle.State.DESTROYED)
+                Assert.assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
+            }
+        }
+
         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
         fun validateOutput(window: Window, block: (bitmap: Bitmap) -> Boolean) {
             val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
index 89891d4..0902082 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.graphics.surface
 
+import android.graphics.Bitmap
 import android.graphics.Color
 import android.graphics.ColorSpace
 import android.graphics.Rect
@@ -25,6 +26,8 @@
 import android.view.Surface
 import android.view.SurfaceControl
 import android.view.SurfaceHolder
+import android.view.SurfaceView
+import androidx.annotation.RequiresApi
 import androidx.lifecycle.Lifecycle
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -47,7 +50,7 @@
 @SmallTest
 @SdkSuppress(minSdkVersion = 29)
 class SurfaceControlWrapperTest {
-    var executor: Executor? = null
+    private var executor: Executor? = null
 
     @Before
     fun setup() {
@@ -64,16 +67,20 @@
 
     @Test
     fun testCreateFromWindow() {
-        var surfaceControl = SurfaceControl.Builder()
+        val surfaceControl = SurfaceControl.Builder()
             .setName("SurfaceControlCompact_createFromWindow")
             .build()
+        var scWrapper: SurfaceControlWrapper? = null
         try {
-            SurfaceControlWrapper.Builder()
+            scWrapper = SurfaceControlWrapper.Builder()
                 .setParent(Surface(surfaceControl))
                 .setDebugName("SurfaceControlWrapperTest")
                 .build()
         } catch (e: IllegalArgumentException) {
             fail()
+        } finally {
+            scWrapper?.release()
+            surfaceControl.release()
         }
     }
 
@@ -82,22 +89,29 @@
         val surfaceControl = SurfaceControl.Builder()
             .setName("SurfaceControlCompact_createFromWindow")
             .build()
+        var scWrapper: SurfaceControlWrapper? = null
         try {
-            SurfaceControlWrapper.Builder()
+            scWrapper = SurfaceControlWrapper.Builder()
                 .setParent(Surface(surfaceControl))
                 .setDebugName("SurfaceControlWrapperTest")
                 .build()
         } catch (e: IllegalArgumentException) {
             fail()
+        } finally {
+            scWrapper?.release()
+            surfaceControl.release()
         }
     }
 
     @Test
     fun testSurfaceTransactionCreate() {
+        var scWrapperTransaction: SurfaceControlWrapper.Transaction? = null
         try {
-            SurfaceControlWrapper.Transaction()
+            scWrapperTransaction = SurfaceControlWrapper.Transaction()
         } catch (e: java.lang.IllegalArgumentException) {
             fail()
+        } finally {
+            scWrapperTransaction?.close()
         }
     }
 
@@ -128,8 +142,10 @@
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
             .moveToState(Lifecycle.State.CREATED)
 
+        val destroyLatch = CountDownLatch(1)
         try {
             scenario.onActivity {
+                it.setDestroyCallback { destroyLatch.countDown() }
                 SurfaceControlWrapper.Transaction()
                     .addTransactionCompletedListener(listener)
                     .commit()
@@ -143,6 +159,7 @@
         } finally {
             // ensure activity is destroyed after any failures
             scenario.moveToState(Lifecycle.State.DESTROYED)
+            assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
         }
     }
 
@@ -154,8 +171,10 @@
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
             .moveToState(Lifecycle.State.CREATED)
 
+        val destroyLatch = CountDownLatch(1)
         try {
             scenario.onActivity {
+                it.setDestroyCallback { destroyLatch.countDown() }
                 SurfaceControlWrapper.Transaction()
                     .addTransactionCommittedListener(executor!!, listener)
                     .commit()
@@ -168,6 +187,7 @@
         } finally {
             // ensure activity is destroyed after any failures
             scenario.moveToState(Lifecycle.State.DESTROYED)
+            assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
         }
     }
 
@@ -180,8 +200,10 @@
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
             .moveToState(Lifecycle.State.CREATED)
 
+        val destroyLatch = CountDownLatch(1)
         try {
             scenario.onActivity {
+                it.setDestroyCallback { destroyLatch.countDown() }
                 SurfaceControlWrapper.Transaction()
                     .addTransactionCommittedListener(executor!!, listener)
                     .addTransactionCommittedListener(executor!!, listener2)
@@ -201,6 +223,7 @@
         } finally {
             // ensure activity is destroyed after any failures
             scenario.moveToState(Lifecycle.State.DESTROYED)
+            assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
         }
     }
 
@@ -213,8 +236,10 @@
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
             .moveToState(Lifecycle.State.CREATED)
 
+        val destroyLatch = CountDownLatch(1)
         try {
             scenario.onActivity {
+                it.setDestroyCallback { destroyLatch.countDown() }
                 SurfaceControlWrapper.Transaction()
                     .addTransactionCommittedListener(executor!!, listener1)
                     .addTransactionCompletedListener(listener2)
@@ -233,12 +258,13 @@
         } finally {
             // ensure activity is destroyed after any failures
             scenario.moveToState(Lifecycle.State.DESTROYED)
+            assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
         }
     }
 
     @Test
     fun testSurfaceControlIsValid_valid() {
-        var surfaceControl = SurfaceControl.Builder()
+        val surfaceControl = SurfaceControl.Builder()
             .setName("SurfaceControlCompact_createFromWindow")
             .build()
         var scCompat: SurfaceControlWrapper? = null
@@ -247,16 +273,19 @@
                 .setParent(Surface(surfaceControl))
                 .setDebugName("SurfaceControlWrapperTest")
                 .build()
+
+            assertTrue(scCompat.isValid())
         } catch (e: IllegalArgumentException) {
             fail()
+        } finally {
+            scCompat?.release()
+            surfaceControl.release()
         }
-
-        assertTrue(scCompat!!.isValid())
     }
 
     @Test
     fun testSurfaceControlIsValid_validNotValid() {
-        var surfaceControl = SurfaceControl.Builder()
+        val surfaceControl = SurfaceControl.Builder()
             .setName("SurfaceControlCompact_createFromWindow")
             .build()
         var scCompat: SurfaceControlWrapper? = null
@@ -266,18 +295,23 @@
                 .setParent(Surface(surfaceControl))
                 .setDebugName("SurfaceControlWrapperTest")
                 .build()
+            assertTrue(scCompat.isValid())
+
+            scCompat.release()
+            assertFalse(scCompat.isValid())
         } catch (e: IllegalArgumentException) {
             fail()
+        } finally {
+            if (scCompat != null && scCompat.isValid()) {
+                scCompat.release()
+            }
+            surfaceControl.release()
         }
-
-        assertTrue(scCompat!!.isValid())
-        scCompat.release()
-        assertFalse(scCompat.isValid())
     }
 
     @Test
     fun testSurfaceControlIsValid_multipleRelease() {
-        var surfaceControl = SurfaceControl.Builder()
+        val surfaceControl = SurfaceControl.Builder()
             .setName("SurfaceControlCompact_createFromWindow")
             .build()
         var scCompat: SurfaceControlWrapper? = null
@@ -287,668 +321,458 @@
                 .setParent(Surface(surfaceControl))
                 .setDebugName("SurfaceControlWrapperTest")
                 .build()
+            assertTrue(scCompat.isValid())
+            scCompat.release()
+            scCompat.release()
+            assertFalse(scCompat.isValid())
         } catch (e: IllegalArgumentException) {
             fail()
+        } finally {
+            if (scCompat != null && scCompat.isValid()) {
+                scCompat.release()
+            }
+            surfaceControl.release()
         }
-
-        assertTrue(scCompat!!.isValid())
-        scCompat.release()
-        scCompat.release()
-        assertFalse(scCompat.isValid())
     }
 
     @Test
     fun testTransactionReparent_null() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer =
-                            SurfaceControlUtils.getSolidBuffer(
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                Color.BLUE
-                            )
-                        assertNotNull(buffer)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer =
+                    SurfaceControlUtils.getSolidBuffer(
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        Color.BLUE
+                    )
+                assertNotNull(buffer)
 
-                        SurfaceControlWrapper.Transaction()
-                            .setBuffer(scCompat, buffer)
-                            .reparent(scCompat, null)
-                            .commit()
-
-                        // Trying to set a callback with a transaction of a null reparent doesn't
-                        // get called, so lets set a listener for a 2nd transaction instead. This
-                        // should be placed in the queue where this will be executed after the
-                        // reparent transaction
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .reparent(scCompat, null)
+            },
+            { bitmap, rect ->
+                Color.BLACK == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.BLACK == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionReparent_childOfSibling() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
-                        val scCompat2 = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapper")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
+                val scCompat2 = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapper")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer =
-                            SurfaceControlUtils.getSolidBuffer(
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                Color.BLUE
-                            )
-                        assertNotNull(buffer)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer =
+                    SurfaceControlUtils.getSolidBuffer(
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        Color.BLUE
+                    )
+                assertNotNull(buffer)
 
-                        val buffer2 =
-                            SurfaceControlUtils.getSolidBuffer(
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                Color.GREEN
-                            )
-                        assertNotNull(buffer2)
+                val buffer2 =
+                    SurfaceControlUtils.getSolidBuffer(
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        Color.GREEN
+                    )
+                assertNotNull(buffer2)
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setBuffer(scCompat2, buffer2)
-                            .reparent(scCompat, scCompat2)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setBuffer(scCompat2, buffer2)
+                    .reparent(scCompat, scCompat2)
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetVisibility_show() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer =
-                            SurfaceControlUtils.getSolidBuffer(
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                Color.BLUE
-                            )
-                        assertNotNull(buffer)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer =
+                    SurfaceControlUtils.getSolidBuffer(
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        Color.BLUE
+                    )
+                assertNotNull(buffer)
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(
-                                scCompat,
-                                true
-                            ).commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(
+                        scCompat,
+                        true
+                    )
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetVisibility_hide() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer =
-                            SurfaceControlUtils.getSolidBuffer(
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                Color.BLUE
-                            )
-                        assertNotNull(buffer)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer =
+                    SurfaceControlUtils.getSolidBuffer(
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        Color.BLUE
+                    )
+                assertNotNull(buffer)
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(
-                                scCompat,
-                                false
-                            ).commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(
+                        scCompat,
+                        false
+                    )
+            },
+            { bitmap, rect ->
+                Color.BLACK == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.BLACK == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetLayer_zero() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat1 = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
-                        val scCompat2 = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat1 = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
+                val scCompat2 = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setLayer(scCompat1, 1)
-                            .setBuffer(
-                                scCompat1,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.BLUE
-                                )
-                            )
-                            .setLayer(scCompat2, 0)
-                            .setBuffer(
-                                scCompat2,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.GREEN
-                                )
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                SurfaceControlWrapper.Transaction()
+                    .setLayer(scCompat1, 1)
+                    .setBuffer(
+                        scCompat1,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.BLUE
+                        )
+                    )
+                    .setLayer(scCompat2, 0)
+                    .setBuffer(
+                        scCompat2,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.GREEN
+                        )
+                    )
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetLayer_positive() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat1 = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
-                        val scCompat2 = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat1 = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
+                val scCompat2 = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setLayer(scCompat1, 1)
-                            .setBuffer(
-                                scCompat1,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.GREEN
-                                )
-                            )
-                            .setLayer(scCompat2, 24)
-                            .setBuffer(
-                                scCompat2,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.BLUE
-                                )
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                SurfaceControlWrapper.Transaction()
+                    .setLayer(scCompat1, 1)
+                    .setBuffer(
+                        scCompat1,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.GREEN
+                        )
+                    )
+                    .setLayer(scCompat2, 24)
+                    .setBuffer(
+                        scCompat2,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.BLUE
+                        )
+                    )
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetLayer_negative() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat1 = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
-                        val scCompat2 = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat1 = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
+                val scCompat2 = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setLayer(scCompat1, 1)
-                            .setBuffer(
-                                scCompat1,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.BLUE
-                                )
-                            )
-                            .setLayer(scCompat2, -7)
-                            .setBuffer(
-                                scCompat2,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.GREEN
-                                )
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                SurfaceControlWrapper.Transaction()
+                    .setLayer(scCompat1, 1)
+                    .setBuffer(
+                        scCompat1,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.BLUE
+                        )
+                    )
+                    .setLayer(scCompat2, -7)
+                    .setBuffer(
+                        scCompat2,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.GREEN
+                        )
+                    )
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetDamageRegion_all() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setDamageRegion(
-                                scCompat,
-                                Region(
-                                    0,
-                                    0,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
-                                )
-                            )
-                            .setBuffer(
-                                scCompat,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.BLUE
-                                )
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                SurfaceControlWrapper.Transaction()
+                    .setDamageRegion(
+                        scCompat,
+                        Region(
+                            0,
+                            0,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
+                        )
+                    )
+                    .setBuffer(
+                        scCompat,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.BLUE
+                        )
+                    )
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetDamageRegion_null() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setDamageRegion(
-                                scCompat,
-                                null
-                            )
-                            .setBuffer(
-                                scCompat,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.BLUE
-                                )
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                SurfaceControlWrapper.Transaction()
+                    .setDamageRegion(
+                        scCompat,
+                        null
+                    )
+                    .setBuffer(
+                        scCompat,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.BLUE
+                        )
+                    )
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetDesiredPresentTime_now() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(
-                                scCompat,
-                                SurfaceControlUtils.getSolidBuffer(
-                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                    Color.BLUE
-                                )
-                            )
-                            .setDesiredPresentTime(0)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(
+                        scCompat,
+                        SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.BLUE
+                        )
+                    )
+                    .setDesiredPresentTime(0)
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetBufferTransparency_opaque() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setOpaque(
-                                scCompat,
-                                true
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setOpaque(
+                        scCompat,
+                        true
+                    )
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetAlpha_0_0() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setOpaque(
-                                scCompat,
-                                false
-                            )
-                            .setAlpha(scCompat, 0.0f)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setOpaque(
+                        scCompat,
+                        false
+                    )
+                    .setAlpha(scCompat, 0.0f)
+            },
+            { bitmap, rect ->
+                Color.BLACK == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.BLACK == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetAlpha_0_5() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setAlpha(scCompat, 0.5f)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setAlpha(scCompat, 0.5f)
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
                 val fConnector: ColorSpace.Connector = ColorSpace.connect(
                     ColorSpace.get(ColorSpace.Named.SRGB),
                     bitmap.colorSpace!!
@@ -969,139 +793,93 @@
                         expectedResult.blue() - bitmap.getColor(coord[0], coord[1]).blue()
                     ) < 2.5e-3f)
             }
-        }
+        )
     }
 
     @Test
     fun testTransactionSetAlpha_1_0() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setAlpha(scCompat, 1.0f)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setAlpha(scCompat, 1.0f)
+            },
+            { bitmap, rect ->
+                Color.RED == bitmap.getPixel(rect.left, rect.top)
             }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                Color.RED == bitmap.getPixel(coord[0], coord[1])
-            }
-        }
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     fun testTransactionSetCrop_null() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlWrapperTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlWrapperTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setCrop(scCompat, null)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setCrop(scCompat, null)
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
                 SurfaceControlUtils.checkNullCrop(bitmap, coord)
             }
-        }
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     fun testTransactionSetCrop_standardCrop() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlCompatTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlCompatTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setCrop(scCompat, Rect(20, 30, 90, 60))
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setCrop(scCompat, Rect(20, 30, 90, 60))
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
                 SurfaceControlUtils.checkStandardCrop(bitmap, coord)
             }
-        }
+        )
     }
 
     @Test
@@ -1172,44 +950,28 @@
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     fun testTransactionSetPosition() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlCompatTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlCompatTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setPosition(scCompat, 30f, 30f)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
-                // Ensure it actually shifted by checking its outer bounds are black
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setPosition(scCompat, 30f, 30f)
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
                 Color.BLACK ==
                     bitmap.getPixel(
                         coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH / 2,
@@ -1222,49 +984,34 @@
                     ) &&
                     Color.RED == bitmap.getPixel(coord[0] + 30, coord[1] + 30)
             }
-        }
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     fun testTransactionSetScale() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlCompatTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlCompatTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getSolidBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getSolidBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setScale(scCompat, 0.5f, 0.5f)
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setScale(scCompat, 0.5f, 0.5f)
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
                 // Check outer bounds of square to ensure its scaled correctly
                 Color.RED == bitmap.getPixel(coord[0], coord[1]) &&
                     Color.RED ==
@@ -1279,55 +1026,40 @@
                         coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2
                     )
             }
-        }
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     fun testTransactionSetBufferTransform_identity() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlCompatTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlCompatTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getQuadrantBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE,
-                            Color.BLACK,
-                            Color.BLACK,
-                            Color.BLACK
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getQuadrantBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE,
+                    Color.BLACK,
+                    Color.BLACK,
+                    Color.BLACK
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setBufferTransform(
-                                scCompat,
-                                SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setBufferTransform(
+                        scCompat,
+                        SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
+                    )
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
 
                 // Check outer bounds of square to ensure its scaled correctly
                 Color.RED == bitmap.getPixel(coord[0], coord[1]) &&
@@ -1342,59 +1074,44 @@
                         coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2
                     )
             }
-        }
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun testTransactionSetGeometry_identity() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlCompatTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlCompatTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getQuadrantBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE,
-                            Color.BLACK,
-                            Color.BLACK,
-                            Color.BLACK
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getQuadrantBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE,
+                    Color.BLACK,
+                    Color.BLACK,
+                    Color.BLACK
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setGeometry(
-                                scCompat,
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setGeometry(
+                        scCompat,
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
+                    )
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
 
                 // Check outer bounds of square to ensure its scaled correctly
                 Color.RED == bitmap.getPixel(coord[0], coord[1]) &&
@@ -1409,55 +1126,40 @@
                         coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2
                     )
             }
-        }
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     fun testTransactionSetBufferTransform_singleTransform() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlCompatTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlCompatTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getQuadrantBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE,
-                            Color.BLACK,
-                            Color.BLACK,
-                            Color.BLACK
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getQuadrantBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE,
+                    Color.BLACK,
+                    Color.BLACK,
+                    Color.BLACK
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setBufferTransform(
-                                scCompat,
-                                SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setBufferTransform(
+                        scCompat,
+                        SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
+                    )
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
                 // Ensure it actually rotated by checking its outer bounds are black
                 Color.BLACK ==
                     bitmap.getPixel(
@@ -1475,59 +1177,44 @@
                         coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 - 1
                     )
             }
-        }
+        )
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun testTransactionSetGeometry_singleTransform() {
-        val listener = TransactionOnCompleteListener()
-        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-            .moveToState(
-                Lifecycle.State.CREATED
-            ).onActivity {
-                val callback = object : SurfaceHolderCallback() {
-                    override fun surfaceCreated(sh: SurfaceHolder) {
-                        val scCompat = SurfaceControlWrapper
-                            .Builder()
-                            .setParent(it.getSurfaceView().holder.surface)
-                            .setDebugName("SurfaceControlCompatTest")
-                            .build()
+        verifySurfaceControlWrapperTest(
+            { surfaceView ->
+                val scCompat = SurfaceControlWrapper
+                    .Builder()
+                    .setParent(surfaceView.holder.surface)
+                    .setDebugName("SurfaceControlCompatTest")
+                    .build()
 
-                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                        val buffer = SurfaceControlUtils.getQuadrantBuffer(
-                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                            Color.BLUE,
-                            Color.BLACK,
-                            Color.BLACK,
-                            Color.BLACK
-                        )
+                // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                val buffer = SurfaceControlUtils.getQuadrantBuffer(
+                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                    Color.BLUE,
+                    Color.BLACK,
+                    Color.BLACK,
+                    Color.BLACK
+                )
 
-                        SurfaceControlWrapper.Transaction()
-                            .addTransactionCompletedListener(listener)
-                            .setBuffer(scCompat, buffer)
-                            .setVisibility(scCompat, true)
-                            .setGeometry(
-                                scCompat,
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
-                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
-                                SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
-                            )
-                            .commit()
-                    }
-                }
-
-                it.addSurface(it.mSurfaceView, callback)
-            }
-
-        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-            assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-            SurfaceControlUtils.validateOutput { bitmap ->
-                val coord = intArrayOf(0, 0)
-                it.mSurfaceView.getLocationOnScreen(coord)
+                SurfaceControlWrapper.Transaction()
+                    .setBuffer(scCompat, buffer)
+                    .setVisibility(scCompat, true)
+                    .setGeometry(
+                        scCompat,
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                        SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                        SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
+                    )
+            },
+            { bitmap, rect ->
+                val coord = intArrayOf(rect.left, rect.top)
                 // Ensure it actually rotated by checking its outer bounds are black
                 Color.BLACK ==
                     bitmap.getPixel(
@@ -1545,7 +1232,33 @@
                         coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 - 1
                     )
             }
-        }
+        )
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    private fun verifySurfaceControlWrapperTest(
+        createTransaction: (SurfaceView) -> SurfaceControlWrapper.Transaction,
+        verifyOutput: (Bitmap, Rect) -> Boolean
+    ) {
+        SurfaceControlUtils.surfaceControlTestHelper(
+            { surfaceView, latch ->
+                val transaction = createTransaction(surfaceView)
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                    transaction.addTransactionCommittedListener(
+                        executor!!,
+                        object : SurfaceControlCompat.TransactionCommittedListener {
+                            override fun onTransactionCommitted() {
+                                latch.countDown()
+                            }
+                        }
+                    )
+                } else {
+                    latch.countDown()
+                }
+                transaction.commit()
+            },
+            verifyOutput
+        )
     }
 
     fun Color.compositeOver(background: Color): Color {
diff --git a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt
index 2ac9a18..79201f0 100644
--- a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt
+++ b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt
@@ -200,6 +200,46 @@
         }
     }
 
+    @Test
+    fun creatingFullSizeTest() {
+        val radius = 400f
+        val innerRadiusFactor = 0.35f
+        val innerRadius = radius * innerRadiusFactor
+        val roundingFactor = 0.32f
+
+        val fullSizeShape = RoundedPolygon.star(
+            numVerticesPerRadius = 4,
+            radius,
+            innerRadius,
+            rounding = CornerRounding(radius * roundingFactor),
+            innerRounding = CornerRounding(radius * roundingFactor),
+            centerX = radius,
+            centerY = radius
+        ).transformed { x, y -> TransformResult((x - radius) / radius, (y - radius) / radius) }
+
+        val canonicalShape = RoundedPolygon.star(
+            numVerticesPerRadius = 4,
+            1f,
+            innerRadiusFactor,
+            rounding = CornerRounding(roundingFactor),
+            innerRounding = CornerRounding(roundingFactor)
+        )
+
+        val cubics = canonicalShape.cubics
+        val cubics1 = fullSizeShape.cubics
+        assertEquals(cubics.size, cubics1.size)
+        cubics.zip(cubics1).forEach { (cubic, cubic1) ->
+            assertEqualish(cubic.anchor0X, cubic1.anchor0X)
+            assertEqualish(cubic.anchor0Y, cubic1.anchor0Y)
+            assertEqualish(cubic.anchor1X, cubic1.anchor1X)
+            assertEqualish(cubic.anchor1Y, cubic1.anchor1Y)
+            assertEqualish(cubic.control0X, cubic1.control0X)
+            assertEqualish(cubic.control0Y, cubic1.control0Y)
+            assertEqualish(cubic.control1X, cubic1.control1X)
+            assertEqualish(cubic.control1Y, cubic1.control1Y)
+        }
+    }
+
     private fun doUnevenSmoothTest(
         // Corner rounding parameter for vertex 0 (top left)
         rounding0: CornerRounding,
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
index 375ab64..99b577e 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
@@ -572,8 +572,12 @@
     private fun lineIntersection(p0: Point, d0: Point, p1: Point, d1: Point): Point? {
         val rotatedD1 = d1.rotate90()
         val den = d0.dotProduct(rotatedD1)
-        if (abs(den) < AngleEpsilon) return null
-        val k = (p1 - p0).dotProduct(rotatedD1) / den
+        if (abs(den) < DistanceEpsilon) return null
+        val num = (p1 - p0).dotProduct(rotatedD1)
+        // Also check the relative value. This is equivalent to abs(den/num) < DistanceEpsilon,
+        // but avoid doing a division
+        if (abs(den) < DistanceEpsilon * abs(num)) return null
+        val k = num / den
         return p0 + d0 * k
     }
 }
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt
index 5c2b868..7964629 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt
@@ -55,11 +55,10 @@
 
         override fun toString(): String = name
 
-        internal fun toProto(): DataProto.HealthEvent.HealthEventType =
-            DataProto.HealthEvent.HealthEventType.forNumber(id)
-                ?: DataProto.HealthEvent.HealthEventType.HEALTH_EVENT_TYPE_UNKNOWN
+        internal fun toProto(): Int = id
 
         public companion object {
+            private const val CUSTOM_TYPE_NAME_PREFIX = "health_services.device_private."
             /**
              * The Health Event is unknown, or is represented by a value too new for this library
              * version to parse.
@@ -76,20 +75,30 @@
 
             internal fun fromProto(proto: DataProto.HealthEvent.HealthEventType): Type =
                 VALUES.firstOrNull { it.id == proto.number } ?: UNKNOWN
+
+            internal fun fromProto(typeId: Int): Type {
+                if (isInCustomHealthEventRange(typeId)) {
+                    return Type(typeId, CUSTOM_TYPE_NAME_PREFIX + typeId)
+                }
+
+                return VALUES.firstOrNull { it.id == typeId } ?: UNKNOWN
+            }
+
+            private fun isInCustomHealthEventRange(id: Int) = id in 0x40000..0x4ffff
         }
     }
 
     internal constructor(
         proto: DataProto.HealthEvent
     ) : this(
-        Type.fromProto(proto.type),
+        Type.fromProto(proto.healthEventTypeId),
         Instant.ofEpochMilli(proto.eventTimeEpochMs),
         fromHealthEventProto(proto)
     )
 
     internal val proto: DataProto.HealthEvent =
         DataProto.HealthEvent.newBuilder()
-            .setType(type.toProto())
+            .setHealthEventTypeId(type.toProto())
             .setEventTimeEpochMs(eventTime.toEpochMilli())
             .addAllMetrics(toEventProtoList(metrics))
             .build()
diff --git a/health/health-services-client/src/main/proto/data.proto b/health/health-services-client/src/main/proto/data.proto
index 2a2e5ee..a25ee7c5 100644
--- a/health/health-services-client/src/main/proto/data.proto
+++ b/health/health-services-client/src/main/proto/data.proto
@@ -499,7 +499,7 @@
     reserved 3 to max;  // Next ID
   }
 
-  optional HealthEventType type = 1;
+  optional int32 health_event_type_id = 1;
   optional int64 event_time_epoch_ms = 2;
   repeated MetricsEntry metrics = 3;
   reserved 4 to max;  // Next ID
@@ -561,7 +561,7 @@
   repeated DataType supported_data_types_passive_monitoring = 1;
   repeated DataType supported_data_types_passive_goals = 2;
   repeated int32 supported_hr_sampling_intervals_seconds = 3 [packed = true];
-  repeated HealthEvent.HealthEventType supported_health_event_types = 4
+  repeated int32 supported_health_event_types = 4
       [packed = true];
   repeated UserActivityState supported_user_activity_states = 5 [packed = true];
   reserved 6 to max;  // Next ID
@@ -571,7 +571,7 @@
   repeated DataType data_types = 1;
   optional bool include_user_activity_state = 2;
   repeated PassiveGoal passive_goals = 3;
-  repeated HealthEvent.HealthEventType health_event_types = 4;
+  repeated int32 health_event_types = 4;
   reserved 5 to max;  // Next ID
 }
 
diff --git a/health/health-services-client/src/main/proto/requests.proto b/health/health-services-client/src/main/proto/requests.proto
index 0a6d865..b548312 100644
--- a/health/health-services-client/src/main/proto/requests.proto
+++ b/health/health-services-client/src/main/proto/requests.proto
@@ -63,7 +63,7 @@
 message HealthEventsRegistrationRequest {
   optional string package_name = 1;
   optional string receiver_class_name = 2;
-  repeated HealthEvent.HealthEventType event_types = 3;
+  repeated int32 event_types = 3;
   reserved 4 to max;  // Next ID
 }
 
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
index cd408a3..c56d440e 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
@@ -48,7 +48,6 @@
 import androidx.health.services.client.impl.response.PassiveMonitoringUpdateResponse
 import androidx.health.services.client.proto.DataProto
 import androidx.health.services.client.proto.DataProto.ComparisonType.COMPARISON_TYPE_GREATER_THAN
-import androidx.health.services.client.proto.DataProto.HealthEvent.HealthEventType.HEALTH_EVENT_TYPE_FALL_DETECTED
 import androidx.health.services.client.proto.DataProto.PassiveGoal.TriggerFrequency.TRIGGER_FREQUENCY_ONCE
 import androidx.health.services.client.proto.DataProto.UserActivityState.USER_ACTIVITY_STATE_PASSIVE
 import androidx.health.services.client.proto.ResponsesProto
@@ -290,7 +289,7 @@
             HealthEventResponse(
                 ResponsesProto.HealthEventResponse.newBuilder().setHealthEvent(
                     DataProto.HealthEvent.newBuilder()
-                        .setType(HEALTH_EVENT_TYPE_FALL_DETECTED)
+                        .setHealthEventTypeId(FALL_DETECTED.id)
                         .build()
                 ).build()
             )
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
index 3403d9bf..36ca19e 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
@@ -72,11 +72,14 @@
     @NonNull
     static MotionEventPredictor newInstance(@NonNull View view) {
         Context context = view.getContext();
+        Configuration configuration = Configuration.getInstance();
         if (Build.VERSION.SDK_INT >= 34
-                && Configuration.getInstance().preferSystemPrediction()) {
-            return SystemMotionEventPredictor.newInstance(context);
+                && configuration.preferSystemPrediction()) {
+            return SystemMotionEventPredictor.newInstance(
+                    context,
+                    configuration.predictionStrategy());
         } else {
-            return new KalmanMotionEventPredictor(context);
+            return new KalmanMotionEventPredictor(context, configuration.predictionStrategy());
         }
     }
 }
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
index c67cfcf..46a1247 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
@@ -25,11 +25,17 @@
  */
 @RestrictTo(LIBRARY)
 public class Configuration {
+    public static final int STRATEGY_BALANCED = 0;
+    public static final int STRATEGY_SAFE = 1;
+    public static final int STRATEGY_AGGRESSIVE = 2;
+
     private static volatile Configuration sInstance = null;
     private static final Object sLock = new Object();
 
-    private boolean mPreferSystemPrediction;
-    private int mPredictionOffset;
+    private final boolean mPredictLift;
+    private final boolean mPreferSystemPrediction;
+    private final int mPredictionOffset;
+    private final int mPredictionStrategy;
 
     /**
      * Returns the configuration for prediction in this system.
@@ -52,6 +58,8 @@
         mPreferSystemPrediction = SystemProperty
                 .getBoolean("debug.input.androidx_prefer_system_prediction");
         mPredictionOffset = SystemProperty.getInt("debug.input.androidx_prediction_offset");
+        mPredictLift = SystemProperty.getBoolean("debug.input.androidx_predict_lift");
+        mPredictionStrategy = SystemProperty.getInt("debug.input.androidx_prediction_strategy");
     }
 
     /**
@@ -71,4 +79,22 @@
     public int predictionOffset() {
         return mPredictionOffset;
     }
+
+    /**
+     * Returns whether or not the pressure should be used to adjust the distance of the prediction
+     *
+     * @return true if the pressure should be used to determine the prediction length
+     */
+    public boolean predictLift() {
+        return mPredictLift;
+    }
+
+    /**
+     * Returns the default prediction strategy
+     *
+     * @return the strategy to use as default; 0 is balanced
+     */
+    public int predictionStrategy() {
+        return mPredictionStrategy;
+    }
 }
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
index 64adc26..12eb416 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
@@ -31,11 +31,12 @@
  */
 @RestrictTo(LIBRARY)
 public class KalmanMotionEventPredictor implements MotionEventPredictor {
-    private final MultiPointerPredictor mMultiPointerPredictor = new MultiPointerPredictor();
+    private final MultiPointerPredictor mMultiPointerPredictor;
     private final PredictionEstimator mPredictionEstimator;
 
-    public KalmanMotionEventPredictor(@NonNull Context context) {
+    public KalmanMotionEventPredictor(@NonNull Context context, int strategy) {
         mPredictionEstimator = new PredictionEstimator(context);
+        mMultiPointerPredictor = new MultiPointerPredictor(strategy);
     }
 
     @Override
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
index f9fdcfb..ae35971 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
@@ -37,8 +37,11 @@
 
     private final SparseArray<SinglePointerPredictor> mPredictorMap = new SparseArray<>();
     private int mReportRateMs = 0;
+    private final int mStrategy;
 
-    public MultiPointerPredictor() {}
+    public MultiPointerPredictor(int strategy) {
+        mStrategy = strategy;
+    }
 
     @Override
     public void setReportRate(int reportRateMs) {
@@ -60,6 +63,7 @@
         int pointerId = event.getPointerId(actionIndex);
         if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
             SinglePointerPredictor predictor = new SinglePointerPredictor(
+                    mStrategy,
                     pointerId,
                     event.getToolType(actionIndex)
             );
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
index fd64a94..5ad7111 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
@@ -24,6 +24,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.input.motionprediction.common.Configuration;
 import androidx.input.motionprediction.kalman.matrix.DVector2;
 
 import java.util.LinkedList;
@@ -92,6 +93,9 @@
     private double mLastOrientation = 0;
     private double mLastTilt = 0;
 
+    private final boolean mPredictLift;
+    private final int mStrategy;
+
     /**
      * Kalman based predictor, predicting the location of the pen `predictionTarget`
      * milliseconds into the future.
@@ -100,13 +104,15 @@
      * achieving close-to-zero latency, prediction errors can be more visible and the target should
      * be reduced to 20ms.
      */
-    public SinglePointerPredictor(int pointerId, int toolType) {
+    public SinglePointerPredictor(int strategy, int pointerId, int toolType) {
+        mStrategy = strategy;
         mKalman.reset();
         mLastSeenEventTime = 0;
         mLastPredictEventTime = 0;
         mDownEventTime = 0;
         mPointerId = pointerId;
         mToolType = toolType;
+        mPredictLift = Configuration.getInstance().predictLift();
     }
 
     private void update(float x, float y, float pressure, float orientation,
@@ -228,6 +234,11 @@
         double jankFactor = 1.0 - normalizeRange(jankAbs, lowJank, highJank);
         double confidenceFactor = speedFactor * jankFactor;
 
+        if (mStrategy == Configuration.STRATEGY_AGGRESSIVE) {
+            // We are very confident
+            confidenceFactor = 1;
+        }
+
         MotionEvent predictedEvent = null;
         final MotionEvent.PointerProperties[] pointerProperties =
                 new MotionEvent.PointerProperties[1];
@@ -256,6 +267,11 @@
             }
         }
 
+        if (mStrategy == Configuration.STRATEGY_SAFE) {
+            // Just a single prediction step is very accurate
+            predictionTargetInSamples = Math.max(predictionTargetInSamples, 1);
+        }
+
         long predictedEventTime = mLastSeenEventTime;
         int i = 0;
         for (; i < predictionTargetInSamples; i++) {
@@ -277,7 +293,8 @@
             long nextPredictedEventTime = predictedEventTime + Math.round(mReportRateMs);
 
             // Abort prediction if the pen is to be lifted.
-            if (mPressure < 0.1
+            if (mPredictLift
+                    && mPressure < 0.1
                     && nextPredictedEventTime > mLastPredictEventTime) {
                 //TODO: Should we generate ACTION_UP MotionEvent instead of ACTION_MOVE?
                 break;
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
index a1a6b20..91ceb59 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
@@ -48,10 +48,12 @@
     // at some point, the source is checked first, which means that it will never be read before
     // it has been written with a valid id.
     private int mLastRecordedDeviceId = -2;
+    private final int mStrategy;
 
-    public SystemMotionEventPredictor(@NonNull Context context) {
+    public SystemMotionEventPredictor(@NonNull Context context, int strategy) {
         mPredictionEstimator = new PredictionEstimator(context);
         mSystemPredictor = new MotionPredictor(context);
+        mStrategy = strategy;
     }
 
     @Override
@@ -86,7 +88,7 @@
 
     private MultiPointerPredictor getKalmanPredictor() {
         if (mKalmanPredictor == null) {
-            mKalmanPredictor = new MultiPointerPredictor();
+            mKalmanPredictor = new MultiPointerPredictor(mStrategy);
         }
         return mKalmanPredictor;
     }
@@ -95,11 +97,12 @@
      * Builds a new instance of the system motion event prediction
      *
      * @param context the application context
+     * @param strategy the strategy to use
      * @return the new instance
      */
     @NonNull
-    public static SystemMotionEventPredictor newInstance(@NonNull Context context) {
-        return new SystemMotionEventPredictor(context);
+    public static SystemMotionEventPredictor newInstance(@NonNull Context context, int strategy) {
+        return new SystemMotionEventPredictor(context, strategy);
     }
 }
 
diff --git a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
index 9987dc8..cec9bab 100644
--- a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
+++ b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
@@ -18,6 +18,7 @@
 
 import android.view.MotionEvent
 import androidx.input.motionprediction.MotionEventGenerator
+import androidx.input.motionprediction.common.Configuration
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -31,7 +32,7 @@
     // Ensures that the historical time is properly populated (b/302300930)
     @Test
     fun historicalTime() {
-        val predictor = MultiPointerPredictor()
+        val predictor = MultiPointerPredictor(Configuration.STRATEGY_BALANCED)
         val generator = MotionEventGenerator(
                 { delta: Long -> delta.toFloat() },
                 { delta: Long -> delta.toFloat() },
@@ -56,7 +57,7 @@
     // Ensures that the down time is properly populated
     @Test
     fun downTime() {
-        val predictor = MultiPointerPredictor()
+        val predictor = MultiPointerPredictor(Configuration.STRATEGY_BALANCED)
         val generator = MotionEventGenerator(
                 { delta: Long -> delta.toFloat() },
                 { delta: Long -> delta.toFloat() },
diff --git a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
index 2f66349..4200608 100644
--- a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
+++ b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
@@ -18,6 +18,7 @@
 
 import android.view.MotionEvent
 import androidx.input.motionprediction.MotionEventGenerator
+import androidx.input.motionprediction.common.Configuration
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -92,23 +93,22 @@
     }
 
     @Test
-    fun predictionNeverGoesBackwardsEvenWhenLifting() {
+    fun liftingDoesNotAffectPredictionDistance() {
         val predictor = constructPredictor()
         val coordGenerator = { delta: Long -> delta.toFloat() }
         // Pressure will be 1 at the beginning and trend to zero while never getting there
         val pressureGenerator = fun(delta: Long): Float {
-                if (delta > 500) {
-                    return ((700 - delta) / 500).toFloat()
-                }
-                return 1f
+            if (delta > 500) {
+                return ((700 - delta) / 500).toFloat()
             }
+            return 1f
+        }
         val motionGenerator =
                 MotionEventGenerator(coordGenerator, coordGenerator, pressureGenerator)
         var lastPredictedTime = 0L
         var lastPredictedEvent: MotionEvent? = null
         var predicted: MotionEvent?
-        var iteration = 0
-        do {
+        for (i in 1..MAX_ITERATIONS) {
             predictor.onTouchEvent(motionGenerator.next())
             predicted = predictor.predict(motionGenerator.getRateMs().toInt() * 10)
             if (predicted != null) {
@@ -118,15 +118,19 @@
                 assertThat(lastPredictedEvent.getHistorySize()).isEqualTo(0);
             }
             lastPredictedEvent = predicted
-            iteration++
-        } while (predicted != null || iteration < INITIAL_FEED)
+            if (i > INITIAL_FEED) {
+                assertThat(predicted).isNotNull()
+            }
+        }
     }
 }
 
 private fun constructPredictor(): SinglePointerPredictor = SinglePointerPredictor(
+        Configuration.STRATEGY_BALANCED,
         0,
         MotionEvent.TOOL_TYPE_STYLUS
 )
 
 private const val INITIAL_FEED = 20
+private const val MAX_ITERATIONS = 10000
 private const val PREDICT_LENGTH = 10
diff --git a/libraryversions.toml b/libraryversions.toml
index 872c259..8acc8f4 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -70,7 +70,7 @@
 GRAPHICS_CORE = "1.0.0-beta01"
 GRAPHICS_FILTERS = "1.0.0-alpha01"
 GRAPHICS_PATH = "1.0.0-beta01"
-GRAPHICS_SHAPES = "1.0.0-alpha03"
+GRAPHICS_SHAPES = "1.0.0-alpha04"
 GRIDLAYOUT = "1.1.0-beta02"
 HEALTH_CONNECT = "1.1.0-alpha07"
 HEALTH_SERVICES_CLIENT = "1.1.0-alpha02"
@@ -156,9 +156,9 @@
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
 WEAR_ONGOING = "1.1.0-alpha02"
 WEAR_PHONE_INTERACTIONS = "1.1.0-alpha04"
-WEAR_PROTOLAYOUT = "1.1.0-alpha03"
+WEAR_PROTOLAYOUT = "1.1.0-alpha04"
 WEAR_REMOTE_INTERACTIONS = "1.1.0-alpha01"
-WEAR_TILES = "1.3.0-alpha03"
+WEAR_TILES = "1.3.0-alpha04"
 WEAR_TOOLING_PREVIEW = "1.0.0-rc01"
 WEAR_WATCHFACE = "1.3.0-alpha01"
 WEBKIT = "1.10.0-beta01"
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml b/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
index 24c78da..1d7dbea 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
@@ -19,49 +19,4 @@
             file="src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorageTest.kt"/>
     </issue>
 
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="            sdkSandboxManager.unloadSdk(sdkName)"
-        errorLine2="                              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                sdkSandboxManager.addSdkSandboxProcessDeathCallback(callbackExecutor, delegate)"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                        sdkSandboxManager.removeSdkSandboxProcessDeathCallback(delegate)"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                sdkSandboxManager.loadSdk("
-        errorLine2="                                  ~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV5Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                .sandboxedSdks"
-        errorLine2="                 ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
-    </issue>
-
 </issues>
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
index 59ee471..e04bd1ff 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
@@ -294,7 +294,6 @@
     }
 
     @RequiresApi(34)
-    @SuppressLint("NewApi", "ClassVerificationFailure") // until updating checks to requires api 34
     private open class Api34Impl(context: Context) : PlatformApi {
         protected val sdkSandboxManager = context.getSystemService(
             SdkSandboxManager::class.java
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/lint-baseline.xml b/privacysandbox/sdkruntime/sdkruntime-core/lint-baseline.xml
deleted file mode 100644
index 3f6432a..0000000
--- a/privacysandbox/sdkruntime/sdkruntime-core/lint-baseline.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha14" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="            return LoadSdkException("
-        errorLine2="                   ^">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                toLoadSdkErrorCodeCompat(ex.loadSdkErrorCode),"
-        errorLine2="                                            ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                ex.extraInformation"
-        errorLine2="                   ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.controller.impl.PlatformImpl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="            .sandboxedSdks"
-        errorLine2="             ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformImpl.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="            return sandboxedSdk.getInterface()"
-        errorLine2="                                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.ApiAdServicesV4Impl.Companion is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                return SandboxedSdk(sdkInterface)"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.ApiAdServicesV5Impl is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="            val sharedLibraryInfo = sandboxedSdk.sharedLibraryInfo"
-        errorLine2="                                                 ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderAdapter is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1=") : SandboxedSdkProvider() {"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderAdapter is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="        val currentContext = context!!"
-        errorLine2="                             ~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt"/>
-    </issue>
-
-</issues>
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
index ca2302d..ea78cd7 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
@@ -16,32 +16,19 @@
 package androidx.privacysandbox.sdkruntime.core
 
 import android.app.sdksandbox.LoadSdkException
-import android.os.Build.VERSION_CODES.TIRAMISU
 import android.os.Bundle
-import android.os.ext.SdkExtensions.AD_SERVICES
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.toLoadCompatSdkException
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeTrue
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-// TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
-@RequiresExtension(extension = AD_SERVICES, version = 4)
-@SdkSuppress(minSdkVersion = TIRAMISU)
+@SdkSuppress(minSdkVersion = 34)
 class LoadSdkCompatExceptionTest {
-
-    @Before
-    fun setUp() {
-        assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
-    }
-
     @Test
     fun toLoadSdkException_returnLoadSdkException() {
         val loadSdkCompatException = LoadSdkCompatException(RuntimeException(), Bundle())
@@ -72,7 +59,4 @@
         assertThat(loadCompatSdkException.loadSdkErrorCode)
             .isEqualTo(loadSdkException.loadSdkErrorCode)
     }
-
-    private fun isSandboxApiAvailable() =
-        AdServicesInfo.isAtLeastV4()
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
index 7ff1ce7..f862dec 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
@@ -18,16 +18,11 @@
 import android.app.sdksandbox.SandboxedSdk
 import android.content.pm.SharedLibraryInfo
 import android.os.Binder
-import android.os.Build.VERSION_CODES.TIRAMISU
-import android.os.ext.SdkExtensions
 import androidx.annotation.RequiresApi
-import androidx.annotation.RequiresExtension
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
@@ -50,12 +45,8 @@
     }
 
     @Test
-    // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    @SdkSuppress(minSdkVersion = TIRAMISU)
+    @SdkSuppress(minSdkVersion = 34)
     fun toSandboxedSdk_whenCreatedFromBinder_returnsSandboxedSdkWithSameBinder() {
-        assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
-
         val binder = Binder()
 
         val toSandboxedSdkResult = SandboxedSdkCompat(binder).toSandboxedSdk()
@@ -65,12 +56,8 @@
     }
 
     @Test
-    // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    @SdkSuppress(minSdkVersion = TIRAMISU)
+    @SdkSuppress(minSdkVersion = 34)
     fun toSandboxedSdk_whenCreatedFromSandboxedSdk_returnsSameSandboxedSdk() {
-        assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
-
         val binder = Binder()
         val sandboxedSdk = SandboxedSdk(binder)
 
@@ -98,38 +85,12 @@
     }
 
     @Test
-    // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    @SdkSuppress(minSdkVersion = TIRAMISU)
-    fun getSdkInfo_whenCreatedFromSandboxedSdkAndNoSharedLibraryInfo_returnsNull() {
-        assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
-        assumeFalse(
-            "Requires SharedLibraryInfo not available",
-            isSharedLibraryInfoAvailable()
-        )
-
-        val binder = Binder()
-        val sandboxedSdk = SandboxedSdk(binder)
-        val sandboxedSdkCompat = SandboxedSdkCompat(sandboxedSdk)
-
-        assertThat(sandboxedSdkCompat.getSdkInfo()).isNull()
-    }
-
-    @Test
-    // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
-    @SdkSuppress(minSdkVersion = TIRAMISU)
-    fun getSdkInfo_whenCreatedFromSandboxedSdkAndSharedLibraryInfoAvailable_returnsSdkInfo() {
-        assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
-        assumeTrue(
-            "Requires SharedLibraryInfo available",
-            isSharedLibraryInfoAvailable()
-        )
-
+    @SdkSuppress(minSdkVersion = 34)
+    fun getSdkInfo_whenCreatedFromSandboxedSdk_returnsSdkInfo() {
         val sdkName = "sdkName"
         val sdkVersion = 1L
-        val sharedLibraryInfo = ApiAdServicesV5.mockSharedLibraryInfo(sdkName, sdkVersion)
-        val sandboxedSdk = ApiAdServicesV5.mockSandboxedSdkWithSharedLibraryInfo(sharedLibraryInfo)
+        val sharedLibraryInfo = Api34Impl.mockSharedLibraryInfo(sdkName, sdkVersion)
+        val sandboxedSdk = Api34Impl.mockSandboxedSdkWithSharedLibraryInfo(sharedLibraryInfo)
 
         val sandboxedSdkCompat = SandboxedSdkCompat(sandboxedSdk)
         val sdkInfo = sandboxedSdkCompat.getSdkInfo()
@@ -142,7 +103,7 @@
         )
     }
 
-    private object ApiAdServicesV5 {
+    private object Api34Impl {
         @RequiresApi(28)
         fun mockSharedLibraryInfo(
             sdkName: String,
@@ -155,7 +116,7 @@
             return sharedLibraryInfo
         }
 
-        @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+        @RequiresApi(34)
         fun mockSandboxedSdkWithSharedLibraryInfo(
             sharedLibraryInfo: SharedLibraryInfo
         ): SandboxedSdk {
@@ -167,10 +128,4 @@
             return sandboxedSdkSpy
         }
     }
-
-    private fun isSandboxApiAvailable() =
-        AdServicesInfo.isAtLeastV4()
-
-    private fun isSharedLibraryInfoAvailable() =
-        AdServicesInfo.isAtLeastV5()
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt
index f10c07b..b8104bd 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt
@@ -17,10 +17,7 @@
 package androidx.privacysandbox.sdkruntime.core
 
 import android.os.Build
-import android.os.ext.SdkExtensions
 import androidx.annotation.ChecksSdkIntAtLeast
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 
 /**
@@ -30,29 +27,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 object AdServicesInfo {
-
-    fun isAtLeastV4(): Boolean {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
-            Extensions30Impl.isAtLeastV4()
-    }
-
-    fun isAtLeastV5(): Boolean {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
-            Extensions30Impl.isAtLeastV5()
-    }
-
     @ChecksSdkIntAtLeast(codename = "UpsideDownCake")
     fun isDeveloperPreview(): Boolean =
         Build.VERSION.CODENAME.equals("UpsideDownCakePrivacySandbox")
-
-    @RequiresApi(30)
-    private object Extensions30Impl {
-        @DoNotInline
-        fun isAtLeastV4() =
-            SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES) >= 4
-
-        @DoNotInline
-        fun isAtLeastV5() =
-            SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES) >= 5
-    }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
index 53f7424..05e901d 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
@@ -17,10 +17,9 @@
 
 import android.app.sdksandbox.LoadSdkException
 import android.os.Bundle
-import android.os.ext.SdkExtensions.AD_SERVICES
 import androidx.annotation.DoNotInline
 import androidx.annotation.IntDef
-import androidx.annotation.RequiresExtension
+import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
 
@@ -110,14 +109,14 @@
      *
      *  @return Platform exception.
      */
-    @RequiresExtension(extension = AD_SERVICES, version = 4)
+    @RequiresApi(34)
     @RestrictTo(LIBRARY_GROUP)
     fun toLoadSdkException(): LoadSdkException {
-        return ApiAdServicesV4Impl.toLoadSdkException(this)
+        return Api34Impl.toLoadSdkException(this)
     }
 
-    @RequiresExtension(extension = AD_SERVICES, version = 4)
-    private object ApiAdServicesV4Impl {
+    @RequiresApi(34)
+    private object Api34Impl {
 
         @DoNotInline
         fun toLoadSdkException(ex: LoadSdkCompatException): LoadSdkException {
@@ -212,10 +211,10 @@
          *  @param ex Platform exception
          *  @return Compat exception.
          */
-        @RequiresExtension(extension = AD_SERVICES, version = 4)
+        @RequiresApi(34)
         @RestrictTo(LIBRARY_GROUP)
         fun toLoadCompatSdkException(ex: LoadSdkException): LoadSdkCompatException {
-            return ApiAdServicesV4Impl.toLoadCompatSdkException(ex)
+            return Api34Impl.toLoadCompatSdkException(ex)
         }
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
index 6a0d8015..fa98a22 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
@@ -17,11 +17,9 @@
 
 import android.app.sdksandbox.SandboxedSdk
 import android.os.IBinder
-import android.os.ext.SdkExtensions.AD_SERVICES
 import androidx.annotation.DoNotInline
 import androidx.annotation.Keep
 import androidx.annotation.RequiresApi
-import androidx.annotation.RequiresExtension
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
 
@@ -73,10 +71,10 @@
      *
      * @param sandboxedSdk SandboxedSdk object. All calls will be delegated to that object.
      */
-    @RequiresExtension(extension = AD_SERVICES, version = 4)
+    @RequiresApi(34)
     @RestrictTo(LIBRARY_GROUP)
     constructor(sandboxedSdk: SandboxedSdk) : this(
-        SdkImplFactory.createSdkImpl(sandboxedSdk)
+        Api34Impl(sandboxedSdk)
     )
 
     /**
@@ -104,7 +102,7 @@
      *
      * @return Platform SandboxedSdk
      */
-    @RequiresExtension(extension = AD_SERVICES, version = 4)
+    @RequiresApi(34)
     @RestrictTo(LIBRARY_GROUP)
     fun toSandboxedSdk() = sdkImpl.toSandboxedSdk()
 
@@ -113,13 +111,13 @@
 
         fun getSdkInfo(): SandboxedSdkInfo?
 
-        @RequiresExtension(extension = AD_SERVICES, version = 4)
+        @RequiresApi(34)
         @DoNotInline
         fun toSandboxedSdk(): SandboxedSdk
     }
 
-    @RequiresExtension(extension = AD_SERVICES, version = 4)
-    private open class ApiAdServicesV4Impl(
+    @RequiresApi(34)
+    private open class Api34Impl(
         protected val sandboxedSdk: SandboxedSdk
     ) : SandboxedSdkImpl {
 
@@ -128,7 +126,14 @@
             return sandboxedSdk.getInterface()
         }
 
-        override fun getSdkInfo(): SandboxedSdkInfo? = null
+        @DoNotInline
+        override fun getSdkInfo(): SandboxedSdkInfo {
+            val sharedLibraryInfo = sandboxedSdk.sharedLibraryInfo
+            return SandboxedSdkInfo(
+                name = sharedLibraryInfo.name,
+                version = sharedLibraryInfo.longVersion,
+            )
+        }
 
         @DoNotInline
         override fun toSandboxedSdk(): SandboxedSdk {
@@ -143,32 +148,6 @@
         }
     }
 
-    @RequiresApi(33)
-    @RequiresExtension(extension = AD_SERVICES, version = 5)
-    private class ApiAdServicesV5Impl(
-        sandboxedSdk: SandboxedSdk
-    ) : ApiAdServicesV4Impl(sandboxedSdk) {
-
-        override fun getSdkInfo(): SandboxedSdkInfo {
-            val sharedLibraryInfo = sandboxedSdk.sharedLibraryInfo
-            return SandboxedSdkInfo(
-                name = sharedLibraryInfo.name,
-                version = sharedLibraryInfo.longVersion,
-            )
-        }
-    }
-
-    @RequiresExtension(extension = AD_SERVICES, version = 4)
-    private object SdkImplFactory {
-        fun createSdkImpl(sandboxedSdk: SandboxedSdk): SandboxedSdkImpl {
-            return if (AdServicesInfo.isAtLeastV5()) {
-                ApiAdServicesV5Impl(sandboxedSdk)
-            } else {
-                ApiAdServicesV4Impl(sandboxedSdk)
-            }
-        }
-    }
-
     private class CompatImpl(
         private val sdkInterface: IBinder,
         private val sdkInfo: SandboxedSdkInfo?
@@ -182,10 +161,10 @@
 
         override fun getSdkInfo(): SandboxedSdkInfo? = sdkInfo
 
-        @RequiresExtension(extension = AD_SERVICES, version = 4)
+        @RequiresApi(34)
         override fun toSandboxedSdk(): SandboxedSdk {
             // avoid class verifications errors
-            return ApiAdServicesV4Impl.createSandboxedSdk(sdkInterface)
+            return Api34Impl.createSandboxedSdk(sdkInterface)
         }
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
index 24771d7..acfb2a5 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
@@ -15,16 +15,13 @@
  */
 package androidx.privacysandbox.sdkruntime.core
 
-import android.annotation.SuppressLint
 import android.app.sdksandbox.LoadSdkException
 import android.app.sdksandbox.SandboxedSdk
 import android.app.sdksandbox.SandboxedSdkProvider
 import android.content.Context
 import android.os.Bundle
-import android.os.ext.SdkExtensions.AD_SERVICES
 import android.view.View
 import androidx.annotation.RequiresApi
-import androidx.annotation.RequiresExtension
 import androidx.annotation.RestrictTo
 
 /**
@@ -32,9 +29,7 @@
  * Gets compat class name from asset "SandboxedSdkProviderCompatClassName.txt"
  *
  */
-@SuppressLint("Override") // b/273473397
 @RequiresApi(34)
-@RequiresExtension(extension = AD_SERVICES, version = 4)
 // TODO(b/301437557) Remove after documentation migration to sdkruntime-provider
 @Deprecated(
     message = "Use SandboxedSdkProviderAdapter from sdkruntime-provider library",
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
index 601d474..4e212c0 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
@@ -16,7 +16,6 @@
 
 package androidx.privacysandbox.sdkruntime.core.controller.impl
 
-import android.annotation.SuppressLint
 import android.app.Activity
 import android.app.Application
 import android.app.sdksandbox.sdkprovider.SdkSandboxActivityHandler
@@ -39,7 +38,6 @@
  * Implementation that delegates to platform [SdkSandboxController] for Android U.
  */
 @RequiresApi(34)
-@SuppressLint("NewApi", "ClassVerificationFailure") // until updating checks to requires api 34
 internal class PlatformUDCImpl(
     private val controller: SdkSandboxController
 ) : SdkSandboxControllerCompat.SandboxControllerImpl {
diff --git a/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt b/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt
index 5c70191..0ec0f13 100644
--- a/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt
@@ -16,7 +16,6 @@
 
 package androidx.privacysandbox.sdkruntime.provider
 
-import android.annotation.SuppressLint
 import android.app.sdksandbox.LoadSdkException
 import android.app.sdksandbox.SandboxedSdk
 import android.app.sdksandbox.SandboxedSdkProvider
@@ -33,7 +32,6 @@
  *
  */
 @RequiresApi(34)
-@SuppressLint("NewApi", "ClassVerificationFailure") // until updating checks to requires api 34
 class SandboxedSdkProviderAdapter internal constructor(
     private val classNameProvider: CompatClassNameProvider
 ) : SandboxedSdkProvider() {
diff --git a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
index 62de81d..c0001de 100644
--- a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
+++ b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
@@ -103,7 +103,13 @@
         @Parameterized.Parameters(name = "sampleSize={0}, mode={1}")
         fun data(): List<Array<Any>> =
             generateAllEnumerations(
-                listOf(100, 1000, 5000, 10000),
+                listOf(
+                    100,
+                    1000,
+                    5000,
+                    // Removed due to due to slow run times, see b/267544445 for details.
+                    // 10000
+                ),
                 listOf(
                     Mode.MEASURE_INSERT,
                     Mode.MEASURE_DELETE,
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 55d6608..d2f0759 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -75,6 +75,9 @@
         }
     }
     compileOptions {
+        // For testing Java records
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
         coreLibraryDesugaringEnabled true
     }
     namespace "androidx.room.integration.testapp"
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
index 4d4e446..79f7b5c 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
@@ -28,6 +28,7 @@
 import androidx.room.integration.testapp.dao.PetDao;
 import androidx.room.integration.testapp.dao.ProductDao;
 import androidx.room.integration.testapp.dao.RawDao;
+import androidx.room.integration.testapp.dao.RecordEntityDao;
 import androidx.room.integration.testapp.dao.RobotsDao;
 import androidx.room.integration.testapp.dao.SchoolDao;
 import androidx.room.integration.testapp.dao.SpecificDogDao;
@@ -46,6 +47,7 @@
 import androidx.room.integration.testapp.vo.PetCouple;
 import androidx.room.integration.testapp.vo.PetWithUser;
 import androidx.room.integration.testapp.vo.Product;
+import androidx.room.integration.testapp.vo.RecordEntity;
 import androidx.room.integration.testapp.vo.Robot;
 import androidx.room.integration.testapp.vo.RoomLibraryPojo;
 import androidx.room.integration.testapp.vo.School;
@@ -60,7 +62,8 @@
 
 @Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
         BlobEntity.class, Product.class, FunnyNamedEntity.class, House.class,
-        FriendsJunction.class, Hivemind.class, Robot.class, RoomLibraryPojo.class},
+        FriendsJunction.class, Hivemind.class, Robot.class, RoomLibraryPojo.class,
+        RecordEntity.class},
         views = {PetWithUser.class},
         version = 1, exportSchema = false)
 @TypeConverters(TestDatabase.Converters.class)
@@ -81,6 +84,7 @@
     public abstract UserHouseDao getUserHouseDao();
     public abstract RobotsDao getRobotsDao();
     public abstract LibraryItemDao getLibraryItemDao();
+    public abstract RecordEntityDao getRecordEntityDao();
 
     @SuppressWarnings("unused")
     public static class Converters {
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RecordEntityDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RecordEntityDao.java
new file mode 100644
index 0000000..6a6cc70
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RecordEntityDao.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.integration.testapp.vo.RecordEntity;
+
+import java.util.List;
+
+@Dao
+public interface RecordEntityDao {
+
+    @Query("SELECT * FROM RecordEntity")
+    List<RecordEntity> getAll();
+
+    @Insert
+    void insert(RecordEntity item);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java
index 873556a..2af75eb 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java
@@ -23,17 +23,18 @@
 
 import androidx.room.Room;
 import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.RecordEntityDao;
 import androidx.room.integration.testapp.dao.UserDao;
 import androidx.room.integration.testapp.database.ProductDao;
 import androidx.room.integration.testapp.database.Review;
 import androidx.room.integration.testapp.database.SampleDatabase;
 import androidx.room.integration.testapp.vo.AvgWeightByAge;
+import androidx.room.integration.testapp.vo.RecordEntity;
 import androidx.room.integration.testapp.vo.User;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -43,17 +44,13 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class PojoTest {
-    private UserDao mUserDao;
-
-    @Before
-    public void createDb() {
-        Context context = ApplicationProvider.getApplicationContext();
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        mUserDao = db.getUserDao();
-    }
 
     @Test
     public void weightsByAge() {
+        Context context = ApplicationProvider.getApplicationContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        UserDao userDao = db.getUserDao();
+
         User[] users = TestUtil.createUsersArray(3, 5, 7, 10);
         users[0].setAge(10);
         users[0].setWeight(20);
@@ -67,8 +64,8 @@
         users[3].setAge(35);
         users[3].setWeight(55);
 
-        mUserDao.insertAll(users);
-        assertThat(mUserDao.weightByAge(), is(
+        userDao.insertAll(users);
+        assertThat(userDao.weightByAge(), is(
                 Arrays.asList(
                         new AvgWeightByAge(35, 55),
                         new AvgWeightByAge(10, 25),
@@ -87,4 +84,15 @@
         assertThat(result.size(), is(1));
         db.close();
     }
+
+    @Test
+    public void recordEntity() {
+        Context context = ApplicationProvider.getApplicationContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        RecordEntityDao recordDao = db.getRecordEntityDao();
+        recordDao.insert(new RecordEntity(1, "I am a RECORD"));
+        List<RecordEntity> result = recordDao.getAll();
+        assertThat(result.size(), is(1));
+        assertThat(result.get(0).data(), is("I am a RECORD"));
+    }
 }
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RecordEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RecordEntity.java
new file mode 100644
index 0000000..3a1f149
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RecordEntity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public record RecordEntity(
+        @PrimaryKey long id,
+        String data
+) {}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index 98883ca..307b910 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -144,6 +144,11 @@
     fun isCompanionObject(): Boolean
 
     /**
+     * Returns `true` if this [XTypeElement] is a Java record class (i.e. [java.lang.Record]).
+     */
+    fun isRecordClass(): Boolean
+
+    /**
      * Fields declared in this type
      *  includes all instance/static fields in this
      */
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index f404202..33e3c00 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -158,6 +158,10 @@
         return kotlinMetadata?.isInterface() ?: (element.kind == ElementKind.INTERFACE)
     }
 
+    override fun isRecordClass(): Boolean {
+        return element.kind == ElementKind.RECORD
+    }
+
     override fun findPrimaryConstructor(): JavacConstructorElement? {
         val primarySignature = kotlinMetadata?.primaryConstructorSignature ?: return null
         return getConstructors().firstOrNull {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 9d4aba5..b2b8de4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -251,6 +251,12 @@
         return !isInterface() && !declaration.isOpen()
     }
 
+    override fun isRecordClass(): Boolean {
+        // Need to also check super type since @JvmRecord is @Retention(SOURCE)
+        return hasAnnotation(JvmRecord::class) ||
+            superClass?.isTypeOf(java.lang.Record::class) == true
+    }
+
     override fun getDeclaredFields(): List<XFieldElement> {
         return _declaredFields
     }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index 5633347..d4d0197 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -32,6 +32,8 @@
 import androidx.room.compiler.processing.util.getDeclaredMethodByJvmName
 import androidx.room.compiler.processing.util.getField
 import androidx.room.compiler.processing.util.getMethodByJvmName
+import androidx.room.compiler.processing.util.runJavaProcessorTest
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import com.squareup.kotlinpoet.INT
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
@@ -2415,6 +2417,61 @@
         }
     }
 
+    @Test
+    fun isRecordClass() {
+        val javaSrc = Source.java(
+            "JavaRecord",
+            """
+            public record JavaRecord(String name) { }
+            """.trimIndent()
+        )
+        val kotlinSrc = Source.kotlin(
+            "KotlinRecord.kt",
+            """
+            @JvmRecord
+            data class KotlinRecord(val name: String)
+            """.trimIndent()
+        )
+        val handler: (XTestInvocation) -> Unit = {
+            val javaElement = it.processingEnv.requireTypeElement("JavaRecord")
+            assertThat(javaElement.isRecordClass())
+            if (it.isKsp) {
+                val kotlinElement = it.processingEnv.requireTypeElement("KotlinRecord")
+                assertThat(kotlinElement.isRecordClass())
+            }
+        }
+        // Run only javac and KSP tests as @JvmRecord is broken with KAPT (KT-44706)
+        if (isPreCompiled) {
+            val compiled = compileFiles(
+                sources = listOf(javaSrc, kotlinSrc),
+                kotlincArguments = listOf("-jvm-target=16")
+            )
+            runJavaProcessorTest(
+                sources = listOf(
+                    Source.java("PlaceholderJava", "public class PlaceholderJava {}")
+                ),
+                classpath = compiled,
+                handler = handler
+            )
+            runKspTest(
+                sources = listOf(Source.kotlin("placeholder.kt", "class PlaceholderKotlin")),
+                kotlincArguments = listOf("-jvm-target=16"),
+                classpath = compiled,
+                handler = handler
+            )
+        } else {
+            runJavaProcessorTest(
+                sources = listOf(javaSrc),
+                handler = handler
+            )
+            runKspTest(
+                sources = listOf(javaSrc, kotlinSrc),
+                kotlincArguments = listOf("-jvm-target=16"),
+                handler = handler
+            )
+        }
+    }
+
     /**
      * it is good to exclude methods coming from Object when testing as they differ between KSP
      * and KAPT but irrelevant for Room.
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
index 05c36d0..824889d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -961,19 +961,25 @@
 
     private class DefaultDelegate(private val context: Context) : Delegate {
         override fun onPreProcess(element: XTypeElement) {
-            // Check that certain Room annotations with @Target(METHOD) are not used in the POJO
-            // since it is not annotated with AutoValue.
-            element.getAllMethods()
-                .filter { it.hasAnyAnnotation(*TARGET_METHOD_ANNOTATIONS) }
-                .forEach { method ->
-                    val annotationName = TARGET_METHOD_ANNOTATIONS
-                        .first { method.hasAnnotation(it) }
-                        .java.simpleName
-                    context.logger.e(
-                        method,
-                        ProcessorErrors.invalidAnnotationTarget(annotationName, method.kindName())
-                    )
-                }
+            // If POJO is not a record then check that certain Room annotations with @Target(METHOD)
+            // are not used since the POJO is not annotated with AutoValue where Room column
+            // annotations are allowed in methods.
+            if (!element.isRecordClass()) {
+                element.getAllMethods()
+                    .filter { it.hasAnyAnnotation(*TARGET_METHOD_ANNOTATIONS) }
+                    .forEach { method ->
+                        val annotationName = TARGET_METHOD_ANNOTATIONS
+                            .first { columnAnnotation -> method.hasAnnotation(columnAnnotation) }
+                            .java.simpleName
+                        context.logger.e(
+                            method,
+                            ProcessorErrors.invalidAnnotationTarget(
+                                annotationName,
+                                method.kindName()
+                            )
+                        )
+                    }
+            }
         }
 
         override fun findConstructors(element: XTypeElement) = element.getConstructors().filterNot {
diff --git a/settings.gradle b/settings.gradle
index 3ba7212..b60f210 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -505,6 +505,7 @@
 includeProject(":compose:material3:benchmark", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive:material3-adaptive-samples", "compose/material3/material3-adaptive/samples", [BuildType.COMPOSE])
+includeProject(":compose:material3:material3-adaptive:material3-adaptive-benchmark", "compose/material3/material3-adaptive/benchmark", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive-navigation-suite", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples", "compose/material3/material3-adaptive-navigation-suite/samples", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-common", [BuildType.COMPOSE])
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
index daf0bef..6108114 100644
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
@@ -160,6 +160,42 @@
             .that(spl[1].width)
             .isEqualTo(30)
     }
+
+    @Test
+    fun splitDividerPositionDoesNotChangePadding() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val spl = createTestSpl(context)
+        spl.setPadding(4, 0, 10, 0)
+        spl.measureAndLayoutForTest()
+        assertWithMessage("left padding")
+            .that(spl.paddingLeft)
+            .isEqualTo(4)
+        assertWithMessage("right padding")
+            .that(spl.paddingRight)
+            .isEqualTo(10)
+
+        fun testPadding(description: String) {
+            assertWithMessage("left edge of first child $description")
+                .that(spl[0].left)
+                .isEqualTo(4)
+            assertWithMessage("right edge of second child $description")
+                .that(spl[1].right)
+                .isEqualTo(spl.width - 10)
+        }
+
+        testPadding("before divider position change")
+        val firstChildExpectedWidth = spl[0].width
+        val secondChildExpectedWidth = spl[1].width
+        spl.splitDividerPosition = spl.visualDividerPosition
+        spl.measureAndLayoutForTest()
+        assertWithMessage("first child width")
+            .that(spl[0].width)
+            .isEqualTo(firstChildExpectedWidth)
+        assertWithMessage("second child width")
+            .that(spl[1].width)
+            .isEqualTo(secondChildExpectedWidth)
+        testPadding("after divider position change")
+    }
 }
 
 private fun View.drawToBitmap(): Bitmap {
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
index c81253c..2082a20 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
@@ -920,8 +920,9 @@
                             if ((index == 0) xor isLayoutRtl) {
                                 dividerPos - lp.horizontalMargin - paddingLeft
                             } else {
-                                // padding accounted for in widthAvailable
-                                widthAvailable - lp.horizontalMargin - dividerPos
+                                // padding accounted for in widthAvailable;
+                                // dividerPos includes left padding
+                                widthAvailable - lp.horizontalMargin - (dividerPos - paddingLeft)
                             }
                         }
                     }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
index 2b3ecda..5bcb602 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
@@ -33,6 +33,27 @@
  * descendants of this class will implement operations of the form "O = LHS [op] RHS", or "O =
  * op(LHS, RHS)".
  *
+ * <p>This node will wait until there's no pending data from either side:
+ *
+ * <ul>
+ *   <li>This node will wait until both sides invoked either {@code onData} or {@code onInvalidated}
+ *       at least once.
+ *   <li>If one side invoked {@code onPreUpdate}, this node will wait until it invoked either {@code
+ *       onData} or {@code onInvalidated}.
+ *   <li>This node expects each side to invoke exactly on {@code onData} or {@code onInvalidated}
+ *       after each {@code onPreUpdate}:
+ *       <ul>
+ *         <li>If {@code onData} or {@code onInvalidated} are invoked without {@code onPreUpdate},
+ *             this node will log a warning and downstream the callback immediately (unless waiting
+ *             for the other side).
+ *         <li>If {@code onPreUpdate} is invoked more than once without {@code onData} or {@code
+ *             onInvalidated}, this node will log a warning and ignore the followup {@code
+ *             onPreUpdate}.
+ *       </ul>
+ *       Both of these scenarios can mean {@link DynamicTypeEvaluator} might publish a partially
+ *       evaluated update.
+ * </ul>
+ *
  * @param <LhsT> The source data type for the left-hand side of the operation.
  * @param <RhsT> The source data type for the right-hand side of the operation.
  * @param <O> The data type that this node emits.
@@ -40,133 +61,95 @@
 class DynamicDataBiTransformNode<LhsT, RhsT, O> implements DynamicDataNode<O> {
     private static final String TAG = "DynamicDataBiTransform";
 
-    private final DynamicTypeValueReceiverWithPreUpdate<LhsT> mLhsIncomingCallback;
-    private final DynamicTypeValueReceiverWithPreUpdate<RhsT> mRhsIncomingCallback;
+    private final UpstreamCallback<LhsT> mLhsUpstreamCallback = new UpstreamCallback<>();
+    private final UpstreamCallback<RhsT> mRhsUpstreamCallback = new UpstreamCallback<>();
 
     final DynamicTypeValueReceiverWithPreUpdate<O> mDownstream;
     private final BiFunction<LhsT, RhsT, O> mTransformer;
 
-    @Nullable LhsT mCachedLhsData;
-    @Nullable RhsT mCachedRhsData;
-
-    int mPendingLhsStateUpdates = 0;
-    int mPendingRhsStateUpdates = 0;
+    boolean mDownstreamPreUpdated = false;
 
     DynamicDataBiTransformNode(
             DynamicTypeValueReceiverWithPreUpdate<O> downstream,
             BiFunction<LhsT, RhsT, O> transformer) {
         this.mDownstream = downstream;
         this.mTransformer = transformer;
+    }
 
-        // These classes refer to handlePreStateUpdate, which is @UnderInitialization when these
-        // initializers run, and hence raise an error. It's invalid to annotate
-        // handle{Pre}StateUpdate as @UnderInitialization (since it refers to initialized fields),
-        // and moving this assignment into the constructor yields the same error (since one of the
-        // fields has to be assigned first, when the class is still under initialization).
-        //
-        // The only path to get these is via get{Lhs,Rhs}IncomingCallback, which can only be called
-        // when the class is initialized (and which also cannot be called from a sub-constructor, as
-        // that will again complain that it's calling something which is @UnderInitialization).
-        // Given that, suppressing the warning in onStateUpdate should be safe.
-        this.mLhsIncomingCallback =
-                new DynamicTypeValueReceiverWithPreUpdate<LhsT>() {
-                    @Override
-                    public void onPreUpdate() {
-                        mPendingLhsStateUpdates++;
+    private class UpstreamCallback<T> implements DynamicTypeValueReceiverWithPreUpdate<T> {
+        private boolean mUpstreamPreUpdated = false;
 
-                        if (mPendingLhsStateUpdates == 1 && mPendingRhsStateUpdates == 0) {
-                            mDownstream.onPreUpdate();
-                        }
-                    }
+        /**
+         * Whether {@link #cache} should be used, i.e. set to true when either {@link #onData} or
+         * {@link #onInvalidated} were invoked at least once, and there's no pending {@link
+         * #onPreUpdate} call.
+         */
+        boolean isCacheReady = false;
 
-                    @SuppressWarnings("method.invocation")
-                    @Override
-                    public void onData(@NonNull LhsT newData) {
-                        onUpdatedImpl(newData);
-                    }
+        /**
+         * Latest value arrived in {@link #onData}, or {@code null} if the last callback was {@link
+         * #onInvalidated}. Should not be used if {@link #isCacheReady} is {@code false}.
+         */
+        @Nullable T cache;
 
-                    private void onUpdatedImpl(@Nullable LhsT newData) {
-                        if (mPendingLhsStateUpdates == 0) {
-                            Log.w(
-                                    TAG,
-                                    "Received a state update, but one or more suppliers did not"
-                                            + " call onPreStateUpdate");
-                        } else {
-                            mPendingLhsStateUpdates--;
-                        }
+        @Override
+        public void onPreUpdate() {
+            if (mUpstreamPreUpdated) {
+                Log.w(TAG, "Received onPreUpdate twice without onData/onInvalidated.");
+            }
+            isCacheReady = false;
+            mUpstreamPreUpdated = true;
+            if (!mDownstreamPreUpdated) {
+                mDownstreamPreUpdated = true;
+                mDownstream.onPreUpdate();
+            }
+        }
 
-                        mCachedLhsData = newData;
-                        handleStateUpdate();
-                    }
+        @Override
+        public void onData(@NonNull T newData) {
+            onUpdate(newData);
+        }
 
-                    @Override
-                    public void onInvalidated() {
-                        // Note: Casts are required here to help out the null checker.
-                        onUpdatedImpl((LhsT) null);
-                    }
-                };
+        @Override
+        public void onInvalidated() {
+            onUpdate(null);
+        }
 
-        this.mRhsIncomingCallback =
-                new DynamicTypeValueReceiverWithPreUpdate<RhsT>() {
-                    @Override
-                    public void onPreUpdate() {
-                        mPendingRhsStateUpdates++;
-
-                        if (mPendingLhsStateUpdates == 0 && mPendingRhsStateUpdates == 1) {
-                            mDownstream.onPreUpdate();
-                        }
-                    }
-
-                    @SuppressWarnings("method.invocation")
-                    @Override
-                    public void onData(@NonNull RhsT newData) {
-                        onUpdatedImpl(newData);
-                    }
-
-                    private void onUpdatedImpl(@Nullable RhsT newData) {
-                        if (mPendingRhsStateUpdates == 0) {
-                            Log.w(
-                                    TAG,
-                                    "Received a state update, but one or more suppliers did not"
-                                            + " call onPreStateUpdate");
-                        } else {
-                            mPendingRhsStateUpdates--;
-                        }
-
-                        mCachedRhsData = newData;
-                        handleStateUpdate();
-                    }
-
-                    @Override
-                    public void onInvalidated() {
-                        onUpdatedImpl((RhsT) null);
-                    }
-                };
+        private void onUpdate(@Nullable T newData) {
+            if (!mUpstreamPreUpdated) {
+                Log.w(TAG, "Received onData/onInvalidated without onPreUpdate.");
+            }
+            mUpstreamPreUpdated = false;
+            isCacheReady = true;
+            cache = newData;
+            handleStateUpdate();
+        }
     }
 
     void handleStateUpdate() {
-        if (mPendingLhsStateUpdates == 0 && mPendingRhsStateUpdates == 0) {
-            LhsT lhs = mCachedLhsData;
-            RhsT rhs = mCachedRhsData;
+        if (!mLhsUpstreamCallback.isCacheReady || !mRhsUpstreamCallback.isCacheReady) {
+            return;
+        }
+        LhsT lhs = mLhsUpstreamCallback.cache;
+        RhsT rhs = mRhsUpstreamCallback.cache;
 
-            if (lhs == null || rhs == null) {
+        if (lhs == null || rhs == null) {
+            mDownstream.onInvalidated();
+        } else {
+            O result = mTransformer.apply(lhs, rhs);
+            if (result == null) {
                 mDownstream.onInvalidated();
             } else {
-                O result = mTransformer.apply(lhs, rhs);
-                if (result == null) {
-                    mDownstream.onInvalidated();
-                } else {
-                    mDownstream.onData(result);
-                }
+                mDownstream.onData(result);
             }
         }
     }
 
-    public DynamicTypeValueReceiverWithPreUpdate<LhsT> getLhsIncomingCallback() {
-        return mLhsIncomingCallback;
+    public DynamicTypeValueReceiverWithPreUpdate<LhsT> getLhsUpstreamCallback() {
+        return mLhsUpstreamCallback;
     }
 
-    public DynamicTypeValueReceiverWithPreUpdate<RhsT> getRhsIncomingCallback() {
-        return mRhsIncomingCallback;
+    public DynamicTypeValueReceiverWithPreUpdate<RhsT> getRhsUpstreamCallback() {
+        return mRhsUpstreamCallback;
     }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
index a3c1cd2..5dc4a67 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
@@ -633,12 +633,12 @@
                     node = concatNode;
                     bindRecursively(
                             stringSource.getConcatOp().getInputLhs(),
-                            concatNode.getLhsIncomingCallback(),
+                            concatNode.getLhsUpstreamCallback(),
                             locale,
                             resultBuilder);
                     bindRecursively(
                             stringSource.getConcatOp().getInputRhs(),
-                            concatNode.getRhsIncomingCallback(),
+                            concatNode.getRhsUpstreamCallback(),
                             locale,
                             resultBuilder);
                     break;
@@ -681,11 +681,11 @@
 
                     bindRecursively(
                             int32Source.getArithmeticOperation().getInputLhs(),
-                            arithmeticNode.getLhsIncomingCallback(),
+                            arithmeticNode.getLhsUpstreamCallback(),
                             resultBuilder);
                     bindRecursively(
                             int32Source.getArithmeticOperation().getInputRhs(),
-                            arithmeticNode.getRhsIncomingCallback(),
+                            arithmeticNode.getRhsUpstreamCallback(),
                             resultBuilder);
 
                     break;
@@ -809,11 +809,11 @@
                 node = betweenInstancesNode;
                 bindRecursively(
                         durationSource.getBetween().getStartInclusive(),
-                        betweenInstancesNode.getLhsIncomingCallback(),
+                        betweenInstancesNode.getLhsUpstreamCallback(),
                         resultBuilder);
                 bindRecursively(
                         durationSource.getBetween().getEndExclusive(),
-                        betweenInstancesNode.getRhsIncomingCallback(),
+                        betweenInstancesNode.getRhsUpstreamCallback(),
                         resultBuilder);
                 break;
             case FIXED:
@@ -984,11 +984,11 @@
 
                     bindRecursively(
                             floatSource.getArithmeticOperation().getInputLhs(),
-                            arithmeticNode.getLhsIncomingCallback(),
+                            arithmeticNode.getLhsUpstreamCallback(),
                             resultBuilder);
                     bindRecursively(
                             floatSource.getArithmeticOperation().getInputRhs(),
-                            arithmeticNode.getRhsIncomingCallback(),
+                            arithmeticNode.getRhsUpstreamCallback(),
                             resultBuilder);
 
                     break;
@@ -1167,11 +1167,11 @@
 
                     bindRecursively(
                             boolSource.getInt32Comparison().getInputLhs(),
-                            compNode.getLhsIncomingCallback(),
+                            compNode.getLhsUpstreamCallback(),
                             resultBuilder);
                     bindRecursively(
                             boolSource.getInt32Comparison().getInputRhs(),
-                            compNode.getRhsIncomingCallback(),
+                            compNode.getRhsUpstreamCallback(),
                             resultBuilder);
 
                     break;
@@ -1184,11 +1184,11 @@
 
                     bindRecursively(
                             boolSource.getLogicalOp().getInputLhs(),
-                            logicalNode.getLhsIncomingCallback(),
+                            logicalNode.getLhsUpstreamCallback(),
                             resultBuilder);
                     bindRecursively(
                             boolSource.getLogicalOp().getInputRhs(),
-                            logicalNode.getRhsIncomingCallback(),
+                            logicalNode.getRhsUpstreamCallback(),
                             resultBuilder);
 
                     break;
@@ -1211,11 +1211,11 @@
 
                     bindRecursively(
                             boolSource.getFloatComparison().getInputLhs(),
-                            compNode.getLhsIncomingCallback(),
+                            compNode.getLhsUpstreamCallback(),
                             resultBuilder);
                     bindRecursively(
                             boolSource.getFloatComparison().getInputRhs(),
-                            compNode.getRhsIncomingCallback(),
+                            compNode.getRhsUpstreamCallback(),
                             resultBuilder);
 
                     break;
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
index 00591df..176d5bc 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
@@ -160,11 +160,11 @@
         new BoolNodes.LogicalBoolOp(protoNode, new AddToListCallback<>(results));
 
     FixedBool lhsProtoNode = FixedBool.newBuilder().setValue(lhs).build();
-    FixedBoolNode lhsNode = new FixedBoolNode(lhsProtoNode, node.getLhsIncomingCallback());
+    FixedBoolNode lhsNode = new FixedBoolNode(lhsProtoNode, node.getLhsUpstreamCallback());
     lhsNode.init();
 
     FixedBool rhsProtoNode = FixedBool.newBuilder().setValue(rhs).build();
-    FixedBoolNode rhsNode = new FixedBoolNode(rhsProtoNode, node.getRhsIncomingCallback());
+    FixedBoolNode rhsNode = new FixedBoolNode(rhsProtoNode, node.getRhsUpstreamCallback());
     rhsNode.init();
 
     return results.get(0);
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java
index 22ef7ad..c5607d0 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java
@@ -62,8 +62,8 @@
         Instant secondInstant = Instant.ofEpochSecond(12345L);
 
         BetweenInstancesNode node = new BetweenInstancesNode(new AddToListCallback<>(results));
-        node.getLhsIncomingCallback().onData(firstInstant);
-        node.getRhsIncomingCallback().onData(secondInstant);
+        node.getLhsUpstreamCallback().onData(firstInstant);
+        node.getRhsUpstreamCallback().onData(secondInstant);
 
         assertThat(results).containsExactly(Duration.between(firstInstant, secondInstant));
     }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java
index 4bd9905..5501a16 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java
@@ -18,11 +18,12 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,68 +37,124 @@
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock private DynamicTypeValueReceiverWithPreUpdate<Integer> mMockCallback;
 
-    private DynamicDataBiTransformNode<Integer, Integer, Integer> mNodeUnderTest;
+    private DynamicDataBiTransformNode<Integer, Integer, Integer> mSumNodeUnderTest;
 
     @Before
     public void setUp() {
-        mNodeUnderTest = new DynamicDataBiTransformNode<>(mMockCallback, Integer::sum);
+        mSumNodeUnderTest = new DynamicDataBiTransformNode<>(mMockCallback, Integer::sum);
+    }
+
+    @After
+    public void assertNoOtherCallbacks() {
+        // DynamicDataBiTransformNode is fragile, we want to make sure an unexpected callback wasn't
+        // used on one of the behaviors tested here.
+        verifyNoMoreInteractions(mMockCallback);
     }
 
     @Test
     public void onPreStateUpdate_propagatesOnce() {
-        mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
+        mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
         verify(mMockCallback).onPreUpdate();
+        reset(mMockCallback);
 
         // Next call should not cause another call to be passed through.
-        mNodeUnderTest.getRhsIncomingCallback().onPreUpdate();
-        verify(mMockCallback).onPreUpdate();
+        mSumNodeUnderTest.getRhsUpstreamCallback().onPreUpdate();
+        verify(mMockCallback, never()).onPreUpdate();
     }
 
     @Test
-    public void onStateUpdate_invokesCallback() {
-        mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
-        mNodeUnderTest.getRhsIncomingCallback().onPreUpdate();
+    public void onPreStateUpdate_twice_ignoresSecondOne() {
+        mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+        verify(mMockCallback).onPreUpdate();
+        reset(mMockCallback);
 
-        mNodeUnderTest.getLhsIncomingCallback().onData(5);
-        mNodeUnderTest.getRhsIncomingCallback().onData(6);
+        mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+        mSumNodeUnderTest.getRhsUpstreamCallback().onPreUpdate();
+        // Doesn't pre-update again.
+        verify(mMockCallback, never()).onPreUpdate();
 
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+        mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
+        // Still propagates data.
         verify(mMockCallback).onData(11);
     }
 
     @Test
-    public void onStateUpdate_onlyOneSide_doesntInvokeCallback() {
-        // Note: RHS has *not* been seeded with any data here...
-        mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
-        mNodeUnderTest.getLhsIncomingCallback().onData(5);
+    public void onStateUpdate_invokesCallback() {
+        mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+        mSumNodeUnderTest.getRhsUpstreamCallback().onPreUpdate();
 
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+        mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
+
+        verify(mMockCallback).onPreUpdate();
+        verify(mMockCallback).onData(11);
+    }
+
+    @Test
+    public void onStateUpdate_onlyOneSide_doesNotInvokeCallback() {
+        // Note: RHS has *not* been seeded with any data here...
+        mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+
+        verify(mMockCallback).onPreUpdate();
         verify(mMockCallback, never()).onData(any());
     }
 
     @Test
     public void onStateUpdate_onPreStateUpdateNotCalled_stillPropagatesData() {
-        mNodeUnderTest.getLhsIncomingCallback().onData(5);
-        mNodeUnderTest.getRhsIncomingCallback().onData(6);
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+        mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
         verify(mMockCallback, never()).onPreUpdate();
         verify(mMockCallback).onData(11);
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void onStateUpdate_onStateUpdate_eachSidePendingUpdateHandledSeparately() {
         // Seed with some real data
-        mNodeUnderTest.getLhsIncomingCallback().onData(5);
-        mNodeUnderTest.getRhsIncomingCallback().onData(6);
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+        mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
         reset(mMockCallback);
 
-        mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
-        mNodeUnderTest.getRhsIncomingCallback().onData(10);
+        mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+        verify(mMockCallback).onPreUpdate();
+        mSumNodeUnderTest.getRhsUpstreamCallback().onData(10);
 
         // The "is update pending" counters should be evaluated separately here, so the incoming
         // update on the RHS should not count towards the state update from the LHS
         verify(mMockCallback, never()).onData(any());
 
         // And now, fulfilling the LHS should still cause an update to be fired.
-        mNodeUnderTest.getLhsIncomingCallback().onData(20);
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(20);
         verify(mMockCallback).onData(30);
     }
+
+    @Test
+    public void onStateUpdate_secondTimeOnlyOneSide_doesNotWaitForOtherSide() {
+        // Seed with some real data
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+        mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
+        reset(mMockCallback);
+
+        // Only on the left.
+        mSumNodeUnderTest.getLhsUpstreamCallback().onData(10);
+
+        // Not waiting for the right.
+        verify(mMockCallback).onData(16);
+    }
+
+    @Test
+    public void onInvalidated_propagatesAfterBothSides() {
+        mSumNodeUnderTest.getLhsUpstreamCallback().onInvalidated();
+        verifyNoMoreInteractions(mMockCallback);
+
+        mSumNodeUnderTest.getRhsUpstreamCallback().onData(5);
+        verify(mMockCallback).onInvalidated();
+    }
+
+    /** Same as {@link org.mockito.Mockito#reset} but suppresses the unchecked warning. */
+    @SuppressWarnings("unchecked")
+    private <T> void reset(T mock) {
+        org.mockito.Mockito.reset(mock);
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
index 27ea07b..c8aea06 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
@@ -312,7 +312,7 @@
 
         float lhsValue = 6.6f;
         FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(lhsValue).build();
-        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
         lhsNode.init();
 
         float oldRhsValue = 6.5f;
@@ -325,7 +325,7 @@
                                         .build()));
         StateFloatSource rhsProtoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
         StateFloatSourceNode rhsNode =
-                new StateFloatSourceNode(oss, rhsProtoNode, node.getRhsIncomingCallback());
+                new StateFloatSourceNode(oss, rhsProtoNode, node.getRhsUpstreamCallback());
 
         rhsNode.preInit();
         rhsNode.init();
@@ -359,11 +359,11 @@
 
         float numerator = 0f;
         FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
-        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
 
         float denominator = 0f;
         FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
-        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsIncomingCallback());
+        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
         lhsNode.preInit();
         rhsNode.preInit();
 
@@ -389,11 +389,11 @@
 
         float numerator = 1f;
         FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
-        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
 
         float denominator = 0;
         FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
-        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsIncomingCallback());
+        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
         lhsNode.preInit();
         rhsNode.preInit();
 
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
index 0adc166..1771223 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
@@ -113,11 +113,11 @@
 
         int numerator = 4;
         FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
-        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsIncomingCallback());
+        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
 
         int denominator = 2;
         FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
-        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsIncomingCallback());
+        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
         lhsNode.preInit();
         rhsNode.preInit();
 
@@ -143,11 +143,11 @@
 
         int numerator = 0;
         FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
-        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsIncomingCallback());
+        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
 
         int denominator = 0;
         FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
-        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsIncomingCallback());
+        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
         lhsNode.preInit();
         rhsNode.preInit();
 
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
index fea4c97..f68ef36 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
@@ -523,8 +523,8 @@
 
             if (mExpectedValue != null) {
                 // Test expects an actual value.
-                assertThat(results).hasSize(1);
                 assertThat(results).containsExactly(mExpectedValue);
+                assertThat(invalidatedCalls.get()).isEqualTo(0);
             } else {
                 // Test expects an invalid value.
                 assertThat(results).isEmpty();
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java
index b3d45da..1e74a05 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java
@@ -188,11 +188,11 @@
         FixedStringNode lhsNode =
                 new FixedStringNode(
                         FixedString.newBuilder().setValue(string300chars).build(),
-                        node.getLhsIncomingCallback());
+                        node.getLhsUpstreamCallback());
         FixedStringNode rhsNode =
                 new FixedStringNode(
                         FixedString.newBuilder().setValue(string300chars).build(),
-                        node.getRhsIncomingCallback());
+                        node.getRhsUpstreamCallback());
         lhsNode.preInit();
         lhsNode.init();
         rhsNode.preInit();
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 9df108f..993dd38 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -2294,6 +2294,16 @@
         // Modifiers cannot be applied to android's Space, so use a plain View if this Spacer has
         // modifiers.
         View view;
+
+        // Init the layout params to 0 in case of linear dimension (so we don't get strange
+        // behaviour before the first data pipeline update).
+        if (spacer.getWidth().hasLinearDimension()) {
+            layoutParams.width = 0;
+        }
+        if (spacer.getHeight().hasLinearDimension()) {
+            layoutParams.height = 0;
+        }
+
         if (spacer.hasModifiers()) {
             view =
                     applyModifiers(
@@ -2311,9 +2321,6 @@
             parentViewWrapper.maybeAddView(view, layoutParams);
 
             if (spacer.getWidth().hasLinearDimension()) {
-                // Init the layout params' width to 0 (so we don't get strange behaviour before the
-                // first data pipeline update).
-                layoutParams.width = 0;
                 handleProp(
                         spacer.getWidth().getLinearDimension(),
                         width -> {
@@ -2331,9 +2338,6 @@
             }
 
             if (spacer.getHeight().hasLinearDimension()) {
-                // Init the layout params' width to 0 (so we don't get strange behaviour before the
-                // first data pipeline update).
-                layoutParams.height = 0;
                 handleProp(
                         spacer.getHeight().getLinearDimension(),
                         height -> {
@@ -2356,6 +2360,11 @@
                 handleProp(
                         spacer.getWidth().getLinearDimension(),
                         width -> {
+                            // Update minimum width first, because LayoutParams could be null.
+                            // This calls requestLayout.
+                            int widthPx = safeDpToPx(width);
+                            view.setMinimumWidth(widthPx);
+
                             // We still need to update layout params in case other dimension is
                             // expand, so 0 could
                             // be miss interpreted.
@@ -2365,9 +2374,7 @@
                                 return;
                             }
 
-                            lp.width = safeDpToPx(width);
-                            // This calls requestLayout.
-                            view.setMinimumWidth(safeDpToPx(width));
+                            lp.width = widthPx;
                         },
                         posId,
                         pipelineMaker);
@@ -2376,6 +2383,11 @@
                 handleProp(
                         spacer.getHeight().getLinearDimension(),
                         height -> {
+                            // Update minimum height first, because LayoutParams could be null.
+                            // This calls requestLayout.
+                            int heightPx = safeDpToPx(height);
+                            view.setMinimumHeight(heightPx);
+
                             // We still need to update layout params in case other dimension is
                             // expand, so 0 could be miss interpreted.
                             LayoutParams lp = view.getLayoutParams();
@@ -2384,9 +2396,7 @@
                                 return;
                             }
 
-                            lp.height = safeDpToPx(height);
-                            // This calls requestLayout.
-                            view.setMinimumHeight(safeDpToPx(height));
+                            lp.height = heightPx;
                         },
                         posId,
                         pipelineMaker);
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
index 0a66ff6..4fa4807 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
@@ -78,6 +78,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -630,14 +631,75 @@
         // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
         expect.that(tv.getMeasuredWidth()).isEqualTo(width);
         expect.that(tv.getMeasuredHeight()).isEqualTo(height);
+    }
 
-        // This tests that layoutParams weren't null in the case when there's no modifiers so that
-        // minimum dimension is correctly set.
+    @Test
+    public void inflate_spacer_noModifiers_hasMinDim() {
+        int width = 10;
+        int height = 20;
+        LayoutElement root =
+                LayoutElement.newBuilder()
+                        .setSpacer(
+                                Spacer.newBuilder()
+                                        .setHeight(
+                                                SpacerDimension.newBuilder()
+                                                        .setLinearDimension(dp(height)))
+                                        .setWidth(
+                                                SpacerDimension.newBuilder()
+                                                        .setLinearDimension(dp(width))))
+                        .build();
+
+        FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
+
+        // Check that there's a single element in the layout...
+        assertThat(rootLayout.getChildCount()).isEqualTo(1);
+        View tv = rootLayout.getChildAt(0);
+
+        // This tests that minimum dimension is correctly set.
+        // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
         expect.that(tv.getMinimumWidth()).isEqualTo(width);
         expect.that(tv.getMeasuredHeight()).isEqualTo(height);
     }
 
     @Test
+    public void inflate_spacer_afterMutation_hasMinDim() {
+        // Confirms that all dimensions are correctly set after mutation.
+        int width = 10;
+        int newWidth = width - 5;
+        int height = 20;
+        int newHeight = height - 6;
+
+        Layout layout1 = layoutBoxWithSpacer(width, height);
+        Layout layout2 = layoutBoxWithSpacer(newWidth, newHeight);
+
+        // Add initial layout.
+        Renderer renderer = renderer(layout1);
+        ViewGroup inflatedViewParent = renderer.inflate();
+
+        // Compute the mutation.
+        ViewGroupMutation mutation =
+                renderer.computeMutation(getRenderedMetadata(inflatedViewParent), layout2);
+        assertThat(mutation).isNotNull();
+        assertThat(mutation.isNoOp()).isFalse();
+
+        // Apply the mutation.
+        boolean mutationResult = renderer.applyMutation(inflatedViewParent, mutation);
+        assertThat(mutationResult).isTrue();
+
+        // This contains layout after the mutation.
+        ViewGroup boxAfterMutation = (ViewGroup) inflatedViewParent.getChildAt(0);
+        View spacerAfterMutation = boxAfterMutation.getChildAt(0);
+
+        // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
+        expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(0);
+        expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(0);
+
+        // This tests that minimum dimension is correctly set.
+        expect.that(spacerAfterMutation.getMinimumWidth()).isEqualTo(newWidth);
+        expect.that(spacerAfterMutation.getMinimumHeight()).isEqualTo(newHeight);
+    }
+
+    @Test
     public void inflate_spacerWithModifiers() {
         int width = 10;
         int height = 20;
@@ -671,6 +733,42 @@
     }
 
     @Test
+    public void inflate_spacer_withModifiers_afterMutation_hasMinDim() {
+        // Confirms that all dimensions are correctly set after mutation.
+        int width = 10;
+        int newWidth = width - 5;
+        int height = 20;
+        int newHeight = height - 6;
+        Modifiers.Builder modifiers =
+                Modifiers.newBuilder().setBorder(Border.newBuilder().setWidth(dp(2)).build());
+
+        Layout layout1 = layoutBoxWithSpacer(width, height, modifiers);
+
+        // Add initial layout.
+        Renderer renderer = renderer(layout1);
+        ViewGroup inflatedViewParent = renderer.inflate();
+
+        Layout layout2 = layoutBoxWithSpacer(newHeight, newWidth, modifiers);
+
+        // Compute the mutation.
+        ViewGroupMutation mutation =
+                renderer.computeMutation(getRenderedMetadata(inflatedViewParent), layout2);
+        assertThat(mutation).isNotNull();
+        assertThat(mutation.isNoOp()).isFalse();
+
+        // Apply the mutation.
+        boolean mutationResult = renderer.applyMutation(inflatedViewParent, mutation);
+        assertThat(mutationResult).isTrue();
+
+        ViewGroup boxAfterMutation = (ViewGroup) inflatedViewParent.getChildAt(0);
+        View spacerAfterMutation = boxAfterMutation.getChildAt(0);
+
+        // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
+        expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(0);
+        expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(0);
+    }
+
+    @Test
     public void inflate_spacerWithDynamicDimension_andModifiers() {
         int width = 100;
         int widthForLayout = 112;
@@ -5749,4 +5847,27 @@
                 .setLayoutWeight(FloatProp.newBuilder().setValue(weight).build())
                 .build();
     }
+
+    /** Builds a wrapper Box that contains Spacer with the given parameters. */
+    private static Layout layoutBoxWithSpacer(int width, int height) {
+        return layoutBoxWithSpacer(width, height, /* modifiers= */ null);
+    }
+
+    /** Builds a wrapper Box that contains Spacer with the given parameters. */
+    private static Layout layoutBoxWithSpacer(
+            int width, int height, @Nullable Modifiers.Builder modifiers) {
+        Spacer.Builder spacer =
+                Spacer.newBuilder()
+                        .setWidth(SpacerDimension.newBuilder().setLinearDimension(dp(width)))
+                        .setHeight(SpacerDimension.newBuilder().setLinearDimension(dp(height)));
+        if (modifiers != null) {
+            spacer.setModifiers(modifiers);
+        }
+        return fingerprintedLayout(
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .addContents(LayoutElement.newBuilder().setSpacer(spacer)))
+                        .build());
+    }
 }
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index a4ba9ae..e3f0976 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -122,12 +122,16 @@
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.ColorStop {
-    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp);
-    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
     method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
     method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOffset();
   }
 
+  public static final class ColorBuilders.ColorStop.Builder {
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp);
+    method public androidx.wear.protolayout.ColorBuilders.ColorStop build();
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.ColorStop.Builder setOffset(androidx.wear.protolayout.TypeBuilders.FloatProp);
+  }
+
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.SweepGradient implements androidx.wear.protolayout.ColorBuilders.Brush {
     method public java.util.List<androidx.wear.protolayout.ColorBuilders.ColorStop!> getColorStops();
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getEndAngle();
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index a4ba9ae..e3f0976 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -122,12 +122,16 @@
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.ColorStop {
-    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp);
-    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
     method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
     method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOffset();
   }
 
+  public static final class ColorBuilders.ColorStop.Builder {
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp);
+    method public androidx.wear.protolayout.ColorBuilders.ColorStop build();
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.ColorStop.Builder setOffset(androidx.wear.protolayout.TypeBuilders.FloatProp);
+  }
+
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.SweepGradient implements androidx.wear.protolayout.ColorBuilders.Brush {
     method public java.util.List<androidx.wear.protolayout.ColorBuilders.ColorStop!> getColorStops();
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getEndAngle();
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
index c8c3b1c..f14f152 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
@@ -217,38 +217,6 @@
             }
         }
 
-        /**
-         * Constructor for {@link ColorStop}.
-         *
-         * <p>When all {@link ColorStop} in a Gradient have no offset, the colors are evenly
-         * distributed in the gradient.
-         *
-         * @param color the color for this stop.
-         *     <p>Note that this parameter only supports static values.
-         */
-        @RequiresSchemaVersion(major = 1, minor = 300)
-        public ColorStop(@NonNull ColorProp color) {
-            ColorStop inst = new Builder().setColor(color).build();
-            this.mImpl = inst.mImpl;
-            this.mFingerprint = inst.mFingerprint;
-        }
-
-        /**
-         * Constructor for {@link ColorStop}.
-         *
-         * <p>Note that all parameters only support static values.
-         *
-         * @param color the color for this stop.
-         * @param offset the relative offset for this color, between 0 and 1. This determines where
-         *     the color is positioned relative to a gradient space.
-         */
-        @RequiresSchemaVersion(major = 1, minor = 300)
-        public ColorStop(@NonNull ColorProp color, @NonNull FloatProp offset) {
-            ColorStop inst = new Builder().setColor(color).setOffset(offset).build();
-            this.mImpl = inst.mImpl;
-            this.mFingerprint = inst.mFingerprint;
-        }
-
         /** Get the fingerprint for this object, or null if unknown. */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @Nullable
@@ -283,13 +251,10 @@
         }
 
         /** Builder for {@link ColorStop} */
-        static final class Builder {
+        public static final class Builder {
             private final ColorProto.ColorStop.Builder mImpl = ColorProto.ColorStop.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(-468737254);
 
-            /** Creates an instance of {@link Builder}. */
-            public Builder() {}
-
             /**
              * Sets the color for this stop. Only opaque colors are supported. Any transparent
              * colors will have their alpha component set to 0xFF (opaque).
@@ -298,7 +263,7 @@
              */
             @RequiresSchemaVersion(major = 1, minor = 300)
             @NonNull
-            Builder setColor(@NonNull ColorProp color) {
+            private Builder setColor(@NonNull ColorProp color) {
                 if (color.getDynamicValue() != null) {
                     throw new IllegalArgumentException(
                             "ColorStop.Builder.setColor doesn't support dynamic values.");
@@ -319,7 +284,7 @@
              */
             @RequiresSchemaVersion(major = 1, minor = 300)
             @NonNull
-            Builder setOffset(@NonNull FloatProp offset) {
+            public Builder setOffset(@NonNull FloatProp offset) {
                 if (offset.getDynamicValue() != null) {
                     throw new IllegalArgumentException(
                             "ColorStop.Builder.setOffset doesn't support dynamic values.");
@@ -335,6 +300,21 @@
                 return this;
             }
 
+            /**
+             * Creates an instance of {@link Builder}.
+             *
+             * <p>If all {@link ColorStop} in a Gradient have no offset, the colors are evenly
+             * distributed in the gradient.
+             *
+             * @param color the color for this stop. Only opaque colors are supported. Any
+             *     transparent colors will have their alpha component set to 0xFF (opaque). Note
+             *     that this parameter only supports static values.
+             */
+            @RequiresSchemaVersion(major = 1, minor = 300)
+            public Builder(@NonNull ColorProp color) {
+                this.setColor(color);
+            }
+
             /** Builds an instance from accumulated values. */
             @NonNull
             public ColorStop build() {
diff --git a/wear/watchface/watchface-complications-data/api/current.txt b/wear/watchface/watchface-complications-data/api/current.txt
index 74a2166..8f499f5 100644
--- a/wear/watchface/watchface-complications-data/api/current.txt
+++ b/wear/watchface/watchface-complications-data/api/current.txt
@@ -93,6 +93,7 @@
   }
 
   @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class DynamicComplicationText implements androidx.wear.watchface.complications.data.ComplicationText {
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue);
     ctor public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue, CharSequence fallbackValue);
     method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString getDynamicValue();
     method public CharSequence getFallbackValue();
@@ -352,6 +353,7 @@
   }
 
   public static final class RangedValueComplicationData.Builder {
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     ctor public RangedValueComplicationData.Builder(float value, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     method public androidx.wear.watchface.complications.data.RangedValueComplicationData build();
diff --git a/wear/watchface/watchface-complications-data/api/restricted_current.txt b/wear/watchface/watchface-complications-data/api/restricted_current.txt
index 74a2166..8f499f5 100644
--- a/wear/watchface/watchface-complications-data/api/restricted_current.txt
+++ b/wear/watchface/watchface-complications-data/api/restricted_current.txt
@@ -93,6 +93,7 @@
   }
 
   @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class DynamicComplicationText implements androidx.wear.watchface.complications.data.ComplicationText {
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue);
     ctor public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue, CharSequence fallbackValue);
     method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString getDynamicValue();
     method public CharSequence getFallbackValue();
@@ -352,6 +353,7 @@
   }
 
   public static final class RangedValueComplicationData.Builder {
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     ctor public RangedValueComplicationData.Builder(float value, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     method public androidx.wear.watchface.complications.data.RangedValueComplicationData build();
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index ea4cc25..29cf91f 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -325,8 +325,8 @@
         placeholder?.tapAction,
         cachedWireComplicationData,
         dataSource = null,
-        persistencePolicy =
-            placeholder?.persistencePolicy ?: ComplicationPersistencePolicies.CACHING_ALLOWED,
+        persistencePolicy = placeholder?.persistencePolicy
+                ?: ComplicationPersistencePolicies.CACHING_ALLOWED,
         displayPolicy = placeholder?.displayPolicy ?: ComplicationDisplayPolicies.ALWAYS_DISPLAY,
         dynamicValueInvalidationFallback = placeholder,
     ) {
@@ -1041,6 +1041,9 @@
          *   don't support [dynamicValue], which should be in the range [[min]] .. [[max]]. The
          *   semantic meaning of value can be specified via [setValueType].
          *
+         *   This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the
+         *   no-fallback constructor if you target an equal or higher API level.
+         *
          *   IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_.
          *   See [setDynamicValueInvalidationFallback] for the situation where [dynamicValue] cannot
          *   be evaluated, e.g. when a data source is not available.
@@ -1060,7 +1063,30 @@
             min: Float,
             max: Float,
             contentDescription: ComplicationText
-        ) : this(fallbackValue, dynamicValue, min, max, contentDescription)
+        ) : this(value = fallbackValue, dynamicValue, min = min, max = max, contentDescription)
+
+        /**
+         * Creates a [Builder] for a [RangedValueComplicationData] with a [DynamicFloat] value, and
+         * no `fallbackValue` for API levels known to support dynamic values.
+         *
+         * @param dynamicValue The [DynamicFloat] of the ranged complication which will be evaluated
+         *   into a value dynamically, and should be in the range [[min]] .. [[max]]. The semantic
+         *   meaning of value can be specified via [setValueType].
+         * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f.
+         * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For
+         *   [TYPE_PERCENTAGE] this must be 0f.
+         * @param contentDescription Defines localized text that briefly describes content of the
+         *   complication. This property is used primarily for accessibility. Since some
+         *   complications do not have textual representation this attribute can be used for
+         *   providing such. Please do not include the word 'complication' in the description.
+         */
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        public constructor(
+            dynamicValue: DynamicFloat,
+            min: Float,
+            max: Float,
+            contentDescription: ComplicationText
+        ) : this(value = min, dynamicValue, min = min, max = max, contentDescription)
 
         private var tapAction: PendingIntent? = null
         private var validTimeRange: TimeRange? = null
@@ -1165,7 +1191,7 @@
     override fun getContentDescription(context: Context): TimeDependentText? =
         _contentDescription?.emptyToNull()?.toWireComplicationText()
             ?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull()
-            ?: WireComplicationText(context.getString(R.string.a11y_template_range, value, max))
+                ?: WireComplicationText(context.getString(R.string.a11y_template_range, value, max))
 
     /** The content description field for accessibility. */
     val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY
@@ -1391,6 +1417,9 @@
          * @param fallbackValue The fallback value of the goal complication which will be used on
          *   systems that don't support [dynamicValue], and should be >= 0.
          *
+         *   This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the
+         *   no-fallback constructor if you target an equal or higher API level.
+         *
          *   IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_.
          *   See [setDynamicValueInvalidationFallback] for the situation where the [dynamicValue]
          *   cannot be evaluated, e.g. when a data source is not available.
@@ -1497,7 +1526,7 @@
     override fun getContentDescription(context: Context): TimeDependentText? =
         _contentDescription?.emptyToNull()?.toWireComplicationText()
             ?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull()
-            ?: WireComplicationText(
+                ?: WireComplicationText(
                 context.getString(R.string.a11y_template_range, value, targetValue)
             )
 
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
index 3dff985..47f00bf 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
@@ -614,6 +614,9 @@
  * @param dynamicValue The [DynamicString] which will be evaluated into a value dynamically.
  * @param fallbackValue Used when the system does not support [dynamicValue].
  *
+ *   This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the no-fallback
+ *   constructor if you target an equal or higher API level.
+ *
  *   IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_. See
  *   [ComplicationData.dynamicValueInvalidationFallback] for the situation where the [dynamicValue]
  *   cannot be evaluated, e.g. when a data source is not available.
@@ -623,6 +626,13 @@
     public val dynamicValue: DynamicString,
     public val fallbackValue: CharSequence,
 ) : ComplicationText {
+    /**
+     * Creates a [DynamicComplicationText] with no [fallbackValue] for API levels that are known to
+     * support dynamic values.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public constructor(dynamicValue: DynamicString) : this(dynamicValue, "")
+
     private val delegate =
         DelegatingComplicationText(WireComplicationText(fallbackValue, dynamicValue))
 
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java
index 13eb584..56e3f0b 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java
@@ -53,6 +53,7 @@
             mUriIdlingResource = uriIdlingResource;
         }
 
+        /** @noinspection RedundantSuppression*/
         @Override
         @SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
         public boolean shouldOverrideUrlLoading(WebView view, String url) {
@@ -70,6 +71,7 @@
             return response;
         }
 
+        /** @noinspection RedundantSuppression*/
         @Override
         @SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
         public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
@@ -117,9 +119,7 @@
 
         WebSettings webViewSettings = mWebView.getSettings();
         webViewSettings.setJavaScriptEnabled(true);
-        // Setting this off for security. Off by default for SDK versions >= 16.
-        webViewSettings.setAllowFileAccessFromFileURLs(false);
-        webViewSettings.setAllowUniversalAccessFromFileURLs(false);
+        setDeprecatedAllowFileAccess(webViewSettings);
         // Keeping these off is less critical but still a good idea, especially
         // if your app is not using file:// or content:// URLs.
         webViewSettings.setAllowFileAccess(false);
@@ -129,6 +129,14 @@
         loadButton.setOnClickListener(v -> loadUrl());
     }
 
+    /** @noinspection RedundantSuppression*/
+    @SuppressWarnings("deprecation") /* b/180503860 */
+    private static void setDeprecatedAllowFileAccess(WebSettings webViewSettings) {
+        // Setting this off for security. Off by default for SDK versions >= 16.
+        webViewSettings.setAllowFileAccessFromFileURLs(false);
+        webViewSettings.setAllowUniversalAccessFromFileURLs(false);
+    }
+
     /**
      * Load the url https://example.com/androidx_webkit/example/assets/www/ajax_requests.html.
      */
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
index 5f1cef3..6ff87a2 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
@@ -54,6 +54,7 @@
             mAssetLoader = assetLoader;
         }
 
+        /** @noinspection RedundantSuppression*/
         @Override
         @SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
         public boolean shouldOverrideUrlLoading(WebView view, String url) {
@@ -67,6 +68,7 @@
             return mAssetLoader.shouldInterceptRequest(Api21Impl.getUrl(request));
         }
 
+        /** @noinspection RedundantSuppression*/
         @Override
         @SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
         public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
@@ -75,7 +77,6 @@
     }
 
     @Override
-    @SuppressWarnings("deprecation") /* AsyncTask */
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -96,6 +97,11 @@
         webView.setWebViewClient(new MyWebViewClient(assetLoader));
 
         // Write the demo file asynchronously and then load the file after it's written.
+        writeFileInBackgroundAndShowInWebView(webView);
+    }
+
+    @SuppressWarnings("deprecation") /* b/180503860 */
+    private void writeFileInBackgroundAndShowInWebView(WebView webView) {
         new WriteFileTask(webView, mDemoFile, DEMO_HTML_CONTENT).execute();
     }
 
@@ -108,6 +114,7 @@
     }
 
     // Writes to file asynchronously in the background thread.
+    @SuppressWarnings("deprecation") /* b/180503860 */
     private static class WriteFileTask extends android.os.AsyncTask<Void, Void, Void> {
         @NonNull private final WebView mWebView;
         @NonNull private final File mFile;
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
index afa726a..5331d1f 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
@@ -37,6 +37,7 @@
     private static final String DESCRIPTION =
             "<html><body><h1>Force Dark Mode is %s </h1></body></html>";
 
+    @SuppressWarnings("deprecation") /* b/180503860 */
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -57,6 +58,7 @@
         }
     }
 
+    @SuppressWarnings("deprecation") /* b/180503860 */
     private void setupWebView(WebView webView, int forceDarkMode) {
         webView.setWebViewClient(new WebViewClient());
         String formattedDescription;
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
index f251d13..e6d53d4 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
@@ -101,6 +101,7 @@
                     + "</html>"
     ).getBytes(), Base64.NO_PADDING);
 
+    @SuppressWarnings("deprecation") /* b/180503860 */
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java
index fce79c1..837ab2f 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java
@@ -66,24 +66,36 @@
             // At this point, the WebView is no longer drawing the content. We should cover it up
             // with the new View.
             mFullScreenView = view;
-            mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            addDeprecatedFullScreenFlag();
             mWindow.addContentView(mFullScreenView,
                     new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                             ViewGroup.LayoutParams.MATCH_PARENT));
             mCustomViewCallback = callback;
         }
 
+        /** @noinspection RedundantSuppression*/
+        @SuppressWarnings("deprecation") /* b/180503860 */
+        private void addDeprecatedFullScreenFlag() {
+            mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
+
         @Override
         public void onHideCustomView() {
             // At this point, mFullScreenView is no longer drawing content. Remove this from the
             // layout to show the underlying WebView, and remove the reference to the View so it can
             // be GC'ed.
-            mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            clearDeprecatedFullScreenFlag();
             ((ViewGroup) mFullScreenView.getParent()).removeView(mFullScreenView);
             mFullScreenView = null;
             mCustomViewCallback = null;
         }
 
+        /** @noinspection RedundantSuppression*/
+        @SuppressWarnings("deprecation") /* b/180503860 */
+        private void clearDeprecatedFullScreenFlag() {
+            mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
+
         /* package */ void exitFullScreen() {
             if (mCustomViewCallback == null) return;
             mCustomViewCallback.onCustomViewHidden();