Merge "Expose convertVisibilityConfigFromProto to public" into androidx-main
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
index 93530a91..5778397 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
+import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
@@ -40,8 +41,11 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.UiThread;
 import androidx.appcompat.R;
+import androidx.collection.LruCache;
 import androidx.core.content.res.ResourcesCompat;
+import androidx.core.util.Pair;
 import androidx.core.util.TypedValueCompat;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.inputmethod.EditorInfoCompat;
@@ -86,6 +90,14 @@
         mAutoSizeTextHelper = new AppCompatTextViewAutoSizeHelper(mView);
     }
 
+    @RequiresApi(26)
+    @UiThread
+    @NonNull
+    static Typeface createVariationInstance(@NonNull Typeface baseTypeface,
+            @NonNull String fontVariationSettings) {
+        return Api26Impl.createVariationInstance(baseTypeface, fontVariationSettings);
+    }
+
     @SuppressLint("NewApi")
     void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
         final Context context = mView.getContext();
@@ -772,6 +784,17 @@
 
     @RequiresApi(26)
     static class Api26Impl {
+        /**
+         * Cache for variation instances created based on an existing Typeface
+         */
+        private static final LruCache<Pair<Typeface, String>, Typeface> sVariationsCache =
+                new LruCache<>(30);
+
+        /**
+         * Used to create variation instances; initialized lazily
+         */
+        private static @Nullable Paint sPaint;
+
         private Api26Impl() {
             // This class is not instantiable.
         }
@@ -788,6 +811,54 @@
             return textView.setFontVariationSettings(fontVariationSettings);
         }
 
+        /**
+         * Create a new Typeface based on {@code baseTypeFace} with the specified variation
+         * settings.  Uses a cache to avoid memory scaling with the number of AppCompatTextViews.
+         *
+         * @param baseTypeface the original typeface, preferably without variations applied
+         *                     (used both to create the new instance, and as a cache key).
+         *                     Note: this method will correctly handle instances with variations
+         *                     applied, as we have no way of detecting that.  However, cache hit
+         *                     rates may be decreased.
+         * @param fontVariationSettings the new font variation settings.
+         *                              This is used as a cache key without sorting, to avoid
+         *                              additional per-TextView allocations to parse and sort the
+         *                              variation settings.  App developers should strive to provide
+         *                              the settings in the same order every time within their app,
+         *                              in order to get the best cache performance.
+         * @return the new instance, or {@code null} if
+         *         {@link Paint#setFontVariationSettings(String)} would return null for this
+         *         Typeface and font variation settings string.
+         */
+        @Nullable
+        @UiThread
+        static Typeface createVariationInstance(@Nullable Typeface baseTypeface,
+                @Nullable String fontVariationSettings) {
+            Pair<Typeface, String> cacheKey = new Pair<>(baseTypeface, fontVariationSettings);
+
+            Typeface result = sVariationsCache.get(cacheKey);
+            if (result != null) {
+                return result;
+            }
+            Paint paint = sPaint != null ? sPaint : (sPaint = new Paint());
+
+            // Work around b/353609778
+            if (Objects.equals(paint.getFontVariationSettings(), fontVariationSettings)) {
+                paint.setFontVariationSettings(null);
+            }
+
+            // Use Paint to create a new Typeface based on an existing one
+            paint.setTypeface(baseTypeface);
+            boolean effective = paint.setFontVariationSettings(fontVariationSettings);
+            if (effective) {
+                result = paint.getTypeface();
+                sVariationsCache.put(cacheKey, result);
+                return result;
+            } else {
+                return null;
+            }
+        }
+
         static int getAutoSizeStepGranularity(TextView textView) {
             return textView.getAutoSizeStepGranularity();
         }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
index 0b7c10b..42f25b5 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
@@ -96,6 +96,20 @@
 
     private boolean mIsSetTypefaceProcessing = false;
 
+    /**
+     * Equivalent to Typeface.mOriginalTypeface.
+     * Used to correctly emulate the behavior of getTypeface(), because we need to call setTypeface
+     * directly in order to implement caching of variation instances of typefaces.
+     */
+    private Typeface mOriginalTypeface;
+
+    /**
+     * The currently applied font variation settings.
+     * Used to make getFontVariationSettings somewhat more accurate with Typeface instance caching,
+     * as we don't call super.setFontVariationSettings.
+     */
+    private String mFontVariationSettings;
+
     @Nullable
     private SuperCaller mSuperCaller = null;
 
@@ -160,7 +174,6 @@
     /**
      * This should be accessed via
      * {@link androidx.core.view.ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)}
-     *
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
@@ -173,7 +186,6 @@
     /**
      * This should be accessed via
      * {@link androidx.core.view.ViewCompat#getBackgroundTintList(android.view.View)}
-     *
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
@@ -186,7 +198,6 @@
     /**
      * This should be accessed via
      * {@link androidx.core.view.ViewCompat#setBackgroundTintMode(android.view.View, PorterDuff.Mode)}
-     *
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
@@ -199,7 +210,6 @@
     /**
      * This should be accessed via
      * {@link androidx.core.view.ViewCompat#getBackgroundTintMode(android.view.View)}
-     *
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
@@ -217,6 +227,38 @@
         }
     }
 
+    /**
+     * Set font variation settings.
+     * See {@link TextView#setFontVariationSettings(String)} for details.
+     * <p>
+     * <em>Note:</em> Due to performance optimizations,
+     * {@code getPaint().getFontVariationSettings()} will be less reliable than if not using
+     * AppCompatTextView.  You should prefer {@link #getFontVariationSettings()}, which will be more
+     * accurate. However, neither approach will work correctly if using Typeface objects with
+     * embedded font variation settings.
+     */
+    @RequiresApi(26)
+    @Override
+    public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
+        Typeface variationTypefaceInstance = AppCompatTextHelper.Api26Impl.createVariationInstance(
+                mOriginalTypeface, fontVariationSettings);
+        if (variationTypefaceInstance != null) {
+            // Call superclass method directly to bypass overwriting mOriginalTypeface
+            super.setTypeface(variationTypefaceInstance);
+            mFontVariationSettings = fontVariationSettings;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Nullable
+    @RequiresApi(26)
+    @Override
+    public String getFontVariationSettings() {
+        return mFontVariationSettings;
+    }
+
     @Override
     public void setFilters(@SuppressWarnings("ArrayReturn") @NonNull InputFilter[] filters) {
         super.setFilters(getEmojiTextViewHelper().getFilters(filters));
@@ -755,6 +797,20 @@
     }
 
     @Override
+    public void setTypeface(@Nullable Typeface tf) {
+        mOriginalTypeface = tf;
+        super.setTypeface(tf);
+    }
+
+    @Override
+    @Nullable
+    // Code inspection reveals that the superclass method can return null.
+    @SuppressWarnings("InvalidNullabilityOverride")
+    public Typeface getTypeface() {
+        return mOriginalTypeface;
+    }
+
+    @Override
     public void setTypeface(@Nullable Typeface tf, int style) {
         if (mIsSetTypefaceProcessing) {
             // b/151782655
diff --git a/appsearch/appsearch-builtin-types/api/current.txt b/appsearch/appsearch-builtin-types/api/current.txt
index 3255d7d..7f4b2f3 100644
--- a/appsearch/appsearch-builtin-types/api/current.txt
+++ b/appsearch/appsearch-builtin-types/api/current.txt
@@ -15,18 +15,18 @@
   @androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
     method public String? getBlackoutPeriodEndDate();
     method public String? getBlackoutPeriodStartDate();
-    method public int getComputingDevice();
     method public int[]? getDaysOfWeek();
     method @IntRange(from=0, to=23) public int getHour();
     method @IntRange(from=0, to=59) public int getMinute();
     method public androidx.appsearch.builtintypes.AlarmInstance? getNextInstance();
+    method public int getOriginatingDevice();
     method public androidx.appsearch.builtintypes.AlarmInstance? getPreviousInstance();
     method public String? getRingtone();
     method public boolean isEnabled();
     method public boolean shouldVibrate();
-    field public static final int COMPUTING_DEVICE_SMART_PHONE = 1; // 0x1
-    field public static final int COMPUTING_DEVICE_SMART_WATCH = 2; // 0x2
-    field public static final int COMPUTING_DEVICE_UNKNOWN = 0; // 0x0
+    field public static final int ORIGINATING_DEVICE_SMART_PHONE = 1; // 0x1
+    field public static final int ORIGINATING_DEVICE_SMART_WATCH = 2; // 0x2
+    field public static final int ORIGINATING_DEVICE_UNKNOWN = 0; // 0x0
   }
 
   @androidx.appsearch.annotation.Document.BuilderProducer public static final class Alarm.Builder {
@@ -40,7 +40,6 @@
     method public androidx.appsearch.builtintypes.Alarm.Builder setAlternateNames(java.util.List<java.lang.String!>?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
-    method public androidx.appsearch.builtintypes.Alarm.Builder setComputingDevice(int);
     method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
     method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
@@ -52,6 +51,7 @@
     method public androidx.appsearch.builtintypes.Alarm.Builder setMinute(@IntRange(from=0, to=59) int);
     method public androidx.appsearch.builtintypes.Alarm.Builder setName(String?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setNextInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+    method public androidx.appsearch.builtintypes.Alarm.Builder setOriginatingDevice(int);
     method public androidx.appsearch.builtintypes.Alarm.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setPreviousInstance(androidx.appsearch.builtintypes.AlarmInstance?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setRingtone(String?);
diff --git a/appsearch/appsearch-builtin-types/api/restricted_current.txt b/appsearch/appsearch-builtin-types/api/restricted_current.txt
index 1fdbab2..32acfbf 100644
--- a/appsearch/appsearch-builtin-types/api/restricted_current.txt
+++ b/appsearch/appsearch-builtin-types/api/restricted_current.txt
@@ -17,18 +17,18 @@
   @androidx.appsearch.annotation.Document(name="builtin:Alarm") public class Alarm extends androidx.appsearch.builtintypes.Thing {
     method public String? getBlackoutPeriodEndDate();
     method public String? getBlackoutPeriodStartDate();
-    method public int getComputingDevice();
     method public int[]? getDaysOfWeek();
     method @IntRange(from=0, to=23) public int getHour();
     method @IntRange(from=0, to=59) public int getMinute();
     method public androidx.appsearch.builtintypes.AlarmInstance? getNextInstance();
+    method public int getOriginatingDevice();
     method public androidx.appsearch.builtintypes.AlarmInstance? getPreviousInstance();
     method public String? getRingtone();
     method public boolean isEnabled();
     method public boolean shouldVibrate();
-    field public static final int COMPUTING_DEVICE_SMART_PHONE = 1; // 0x1
-    field public static final int COMPUTING_DEVICE_SMART_WATCH = 2; // 0x2
-    field public static final int COMPUTING_DEVICE_UNKNOWN = 0; // 0x0
+    field public static final int ORIGINATING_DEVICE_SMART_PHONE = 1; // 0x1
+    field public static final int ORIGINATING_DEVICE_SMART_WATCH = 2; // 0x2
+    field public static final int ORIGINATING_DEVICE_UNKNOWN = 0; // 0x0
   }
 
   @androidx.appsearch.annotation.Document.BuilderProducer public static final class Alarm.Builder {
@@ -42,7 +42,6 @@
     method public androidx.appsearch.builtintypes.Alarm.Builder setAlternateNames(java.util.List<java.lang.String!>?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodEndDate(String?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setBlackoutPeriodStartDate(String?);
-    method public androidx.appsearch.builtintypes.Alarm.Builder setComputingDevice(int);
     method public androidx.appsearch.builtintypes.Alarm.Builder setCreationTimestampMillis(long);
     method public androidx.appsearch.builtintypes.Alarm.Builder setDaysOfWeek(@IntRange(from=java.util.Calendar.SUNDAY, to=java.util.Calendar.SATURDAY) int...?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setDescription(String?);
@@ -54,6 +53,7 @@
     method public androidx.appsearch.builtintypes.Alarm.Builder setMinute(@IntRange(from=0, to=59) int);
     method public androidx.appsearch.builtintypes.Alarm.Builder setName(String?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setNextInstance(androidx.appsearch.builtintypes.AlarmInstance?);
+    method public androidx.appsearch.builtintypes.Alarm.Builder setOriginatingDevice(int);
     method public androidx.appsearch.builtintypes.Alarm.Builder setPotentialActions(java.util.List<androidx.appsearch.builtintypes.PotentialAction!>?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setPreviousInstance(androidx.appsearch.builtintypes.AlarmInstance?);
     method public androidx.appsearch.builtintypes.Alarm.Builder setRingtone(String?);
diff --git a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java b/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
index 23a2776..975c794 100644
--- a/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
+++ b/appsearch/appsearch-builtin-types/src/androidTest/java/androidx/appsearch/builtintypes/AlarmTest.java
@@ -57,7 +57,7 @@
                 .setShouldVibrate(true)
                 .setPreviousInstance(alarmInstance1)
                 .setNextInstance(alarmInstance2)
-                .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
+                .setOriginatingDevice(Alarm.ORIGINATING_DEVICE_SMART_WATCH)
                 .build();
 
         assertThat(alarm.getNamespace()).isEqualTo("namespace");
@@ -83,7 +83,7 @@
         assertThat(alarm.shouldVibrate()).isTrue();
         assertThat(alarm.getPreviousInstance()).isEqualTo(alarmInstance1);
         assertThat(alarm.getNextInstance()).isEqualTo(alarmInstance2);
-        assertThat(alarm.getComputingDevice()).isEqualTo(Alarm.COMPUTING_DEVICE_SMART_WATCH);
+        assertThat(alarm.getOriginatingDevice()).isEqualTo(Alarm.ORIGINATING_DEVICE_SMART_WATCH);
     }
 
     @Test
@@ -115,7 +115,7 @@
                 .setShouldVibrate(true)
                 .setPreviousInstance(alarmInstance1)
                 .setNextInstance(alarmInstance2)
-                .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
+                .setOriginatingDevice(Alarm.ORIGINATING_DEVICE_SMART_WATCH)
                 .build();
 
         Alarm alarm2 = new Alarm.Builder(alarm1).build();
@@ -143,7 +143,7 @@
         assertThat(alarm1.shouldVibrate()).isEqualTo(alarm2.shouldVibrate());
         assertThat(alarm1.getPreviousInstance()).isEqualTo(alarm2.getPreviousInstance());
         assertThat(alarm1.getNextInstance()).isEqualTo(alarm2.getNextInstance());
-        assertThat(alarm1.getComputingDevice()).isEqualTo(alarm2.getComputingDevice());
+        assertThat(alarm1.getOriginatingDevice()).isEqualTo(alarm2.getOriginatingDevice());
     }
 
     @Test
@@ -177,7 +177,7 @@
                 .setShouldVibrate(true)
                 .setPreviousInstance(alarmInstance1)
                 .setNextInstance(alarmInstance2)
-                .setComputingDevice(Alarm.COMPUTING_DEVICE_SMART_WATCH)
+                .setOriginatingDevice(Alarm.ORIGINATING_DEVICE_SMART_WATCH)
                 .build();
 
         GenericDocument genericDocument = GenericDocument.fromDocumentClass(alarm);
@@ -213,7 +213,7 @@
         assertThat(genericDocument.getPropertyDocument("nextInstance"))
                 .isEqualTo(GenericDocument.fromDocumentClass(alarmInstance2));
         assertThat(genericDocument.getPropertyLong("computingDevice"))
-                .isEqualTo(Alarm.COMPUTING_DEVICE_SMART_WATCH);
+                .isEqualTo(Alarm.ORIGINATING_DEVICE_SMART_WATCH);
 
         // Test that toDocumentClass doesn't lose information.
         GenericDocument newGenericDocument = GenericDocument.fromDocumentClass(
@@ -256,4 +256,14 @@
                 alarmGenericDocument.toDocumentClass(Alarm.class));
         assertThat(newGenericDocument).isEqualTo(alarmGenericDocument);
     }
+
+    @Test
+    public void testRenameComputingDevice_rename() throws Exception {
+        GenericDocument genericAlarm =
+                new GenericDocument.Builder<>("namespace1", "id1", "builtin:Alarm")
+                        .setPropertyLong("computingDevice", 42)
+                        .build();
+        Alarm alarm = genericAlarm.toDocumentClass(Alarm.class);
+        assertThat(alarm.getOriginatingDevice()).isEqualTo(42);
+    }
 }
diff --git a/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java b/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
index 43fb786..b488d66 100644
--- a/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
+++ b/appsearch/appsearch-builtin-types/src/main/java/androidx/appsearch/builtintypes/Alarm.java
@@ -37,15 +37,18 @@
 public class Alarm extends Thing {
     /** The device that this {@link Alarm} belongs to. */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    @IntDef({COMPUTING_DEVICE_UNKNOWN, COMPUTING_DEVICE_SMART_PHONE, COMPUTING_DEVICE_SMART_WATCH})
+    @IntDef({
+            ORIGINATING_DEVICE_UNKNOWN,
+            ORIGINATING_DEVICE_SMART_PHONE,
+            ORIGINATING_DEVICE_SMART_WATCH})
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ComputingDevice {}
+    public @interface OriginatingDevice {}
     /** The {@link Alarm} belongs to an unknown device. */
-    public static final int COMPUTING_DEVICE_UNKNOWN = 0;
+    public static final int ORIGINATING_DEVICE_UNKNOWN = 0;
     /** The {@link Alarm} belongs to a smart phone device. */
-    public static final int COMPUTING_DEVICE_SMART_PHONE = 1;
+    public static final int ORIGINATING_DEVICE_SMART_PHONE = 1;
     /** The {@link Alarm} belongs to a smart watch device. */
-    public static final int COMPUTING_DEVICE_SMART_WATCH = 2;
+    public static final int ORIGINATING_DEVICE_SMART_WATCH = 2;
 
     @Document.BooleanProperty
     private final boolean mEnabled;
@@ -77,8 +80,13 @@
     @Document.DocumentProperty
     private final AlarmInstance mNextInstance;
 
-    @Document.LongProperty
-    private final int mComputingDevice;
+    // This property was originally released as computingDevice, and the old name is maintained for
+    // compatibility with existing documents. Since the field is not indexed, the impact is limited
+    // to use of this field as a property path for projections and inheritance.
+    // If this limitation causes problems, we should add the mComputingDevice field back, mark it
+    // deprecated, leave it in the API surface, and provide a migrator for convenience of upgrading.
+    @Document.LongProperty(name = "computingDevice")
+    private final int mOriginatingDevice;
 
     Alarm(@NonNull String namespace, @NonNull String id, int documentScore,
             long creationTimestampMillis, long documentTtlMillis, @Nullable String name,
@@ -89,7 +97,7 @@
             @Nullable String blackoutPeriodStartDate, @Nullable String blackoutPeriodEndDate,
             @Nullable String ringtone, boolean shouldVibrate,
             @Nullable AlarmInstance previousInstance, @Nullable AlarmInstance nextInstance,
-            int computingDevice) {
+            int originatingDevice) {
         super(namespace, id, documentScore, creationTimestampMillis, documentTtlMillis, name,
                 alternateNames, description, image, url, potentialActions);
         mEnabled = enabled;
@@ -102,7 +110,7 @@
         mShouldVibrate = shouldVibrate;
         mPreviousInstance = previousInstance;
         mNextInstance = nextInstance;
-        mComputingDevice = computingDevice;
+        mOriginatingDevice = originatingDevice;
     }
 
     /** Returns whether or not the {@link Alarm} is active. */
@@ -217,10 +225,10 @@
         return mNextInstance;
     }
 
-    /** Returns the {@link ComputingDevice} this alarm belongs to. */
-    @ComputingDevice
-    public int getComputingDevice() {
-        return mComputingDevice;
+    /** Returns the {@link OriginatingDevice} this alarm belongs to. */
+    @OriginatingDevice
+    public int getOriginatingDevice() {
+        return mOriginatingDevice;
     }
 
     /** Builder for {@link Alarm}. */
@@ -257,7 +265,7 @@
         protected boolean mShouldVibrate;
         protected AlarmInstance mPreviousInstance;
         protected AlarmInstance mNextInstance;
-        protected int mComputingDevice;
+        protected int mOriginatingDevice;
 
         BuilderImpl(@NonNull String namespace, @NonNull String id) {
             super(namespace, id);
@@ -275,7 +283,7 @@
             mShouldVibrate = alarm.shouldVibrate();
             mPreviousInstance = alarm.getPreviousInstance();
             mNextInstance = alarm.getNextInstance();
-            mComputingDevice = alarm.getComputingDevice();
+            mOriginatingDevice = alarm.getOriginatingDevice();
         }
 
         /** Sets whether or not the {@link Alarm} is active. */
@@ -421,10 +429,10 @@
             return (T) this;
         }
 
-        /** Sets the {@link ComputingDevice} this alarm belongs to. */
+        /** Sets the {@link OriginatingDevice} this alarm belongs to. */
         @NonNull
-        public T setComputingDevice(@ComputingDevice int computingDevice) {
-            mComputingDevice = computingDevice;
+        public T setOriginatingDevice(@OriginatingDevice int originatingDevice) {
+            mOriginatingDevice = originatingDevice;
             return (T) this;
         }
 
@@ -437,7 +445,7 @@
                     mPotentialActions,
                     mEnabled, mDaysOfWeek, mHour, mMinute, mBlackoutPeriodStartDate,
                     mBlackoutPeriodEndDate, mRingtone, mShouldVibrate, mPreviousInstance,
-                    mNextInstance, mComputingDevice);
+                    mNextInstance, mOriginatingDevice);
         }
     }
 }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
index 5cecaa7..35e4404 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
@@ -50,7 +50,7 @@
                 // fall through
             case Features.LIST_FILTER_HAS_PROPERTY_FUNCTION:
                 // fall through
-            case Features.LIST_FILTER_TOKENIZE_FUNCTION:
+            case Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS:
                 // fall through
             case Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG:
                 // fall through
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
index ad5be47..b7ee775 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
@@ -289,9 +289,10 @@
                 .setQuery(mQueryExpression)
                 .addAllNamespaceFilters(mTargetPrefixedNamespaceFilters)
                 .addAllSchemaTypeFilters(mTargetPrefixedSchemaFilters)
-                .setUseReadOnlySearch(mIcingOptionsConfig.getUseReadOnlySearch());
+                .setUseReadOnlySearch(mIcingOptionsConfig.getUseReadOnlySearch())
+                .addAllQueryParameterStrings(mSearchSpec.getSearchStringParameters());
 
-        List<EmbeddingVector> searchEmbeddings = mSearchSpec.getSearchEmbeddings();
+        List<EmbeddingVector> searchEmbeddings = mSearchSpec.getEmbeddingParameters();
         for (int i = 0; i < searchEmbeddings.size(); i++) {
             protoBuilder.addEmbeddingQueryVectors(
                     GenericDocumentToProtoConverter.embeddingVectorToVectorProto(
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
index 7c99bb2..dfcea5f 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
@@ -107,7 +107,8 @@
                 .setPrefix(mSuggestionQueryExpression)
                 .addAllNamespaceFilters(mTargetPrefixedNamespaceFilters)
                 .addAllSchemaTypeFilters(mTargetPrefixedSchemaFilters)
-                .setNumToReturn(mSearchSuggestionSpec.getMaximumResultCount());
+                .setNumToReturn(mSearchSuggestionSpec.getMaximumResultCount())
+                .addAllQueryParameterStrings(mSearchSuggestionSpec.getSearchStringParameters());
 
         // Convert type property filter map into type property mask proto.
         for (Map.Entry<String, List<String>> entry :
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index aa0b554..cf8406b 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -97,7 +97,7 @@
             case Features.SCHEMA_SET_DESCRIPTION:
                 // TODO(b/326987971) : Update when feature is ready in service-appsearch.
                 // fall through
-            case Features.LIST_FILTER_TOKENIZE_FUNCTION:
+            case Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS:
                 // TODO(b/332620561) : Update when feature is ready in service-appsearch.
                 // fall through
             case Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS:
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
index 125da54..5239d56 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
@@ -125,18 +125,16 @@
                 }
                 ApiHelperForV.copyEnabledFeatures(platformBuilder, jetpackSearchSpec);
             }
-            // Copy beyond-V features
-            if (jetpackSearchSpec.isEmbeddingSearchEnabled()
-                    || !jetpackSearchSpec.getSearchEmbeddings().isEmpty()) {
-                // TODO(b/326656531): Remove this once embedding search APIs are available.
-                throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
-                        + " is not available on this AppSearch implementation.");
-            }
-            if (jetpackSearchSpec.isListFilterTokenizeFunctionEnabled()) {
-                // TODO(b/332620561): Remove this once 'tokenize' is supported.
-                throw new UnsupportedOperationException(Features.LIST_FILTER_TOKENIZE_FUNCTION
-                        + " is not available on this AppSearch implementation.");
-            }
+        }
+        if (!jetpackSearchSpec.getEmbeddingParameters().isEmpty()) {
+            // TODO(b/326656531): Remove this once embedding search APIs are available.
+            throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
+                    + " is not available on this AppSearch implementation.");
+        }
+        if (!jetpackSearchSpec.getSearchStringParameters().isEmpty()) {
+            // TODO(b/332620561): Remove this once search parameter strings APIs is supported.
+            throw new UnsupportedOperationException(Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+                    + " is not available on this AppSearch implementation.");
         }
 
         if (jetpackSearchSpec.getJoinSpec() != null) {
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java
index d354fe5..e16ce71 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSuggestionSpecToPlatformConverter.java
@@ -78,6 +78,12 @@
                         platformBuilder, entry.getKey(), entry.getValue());
             }
         }
+        if (!jetpackSearchSuggestionSpec.getSearchStringParameters().isEmpty()) {
+            // TODO(b/332620561): Remove this once search parameter strings APIs is supported.
+            throw new UnsupportedOperationException(
+                    Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+                            + " is not available on this AppSearch implementation.");
+        }
         return platformBuilder.build();
     }
 
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
index 6393a15..3e93d3e 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
@@ -80,7 +80,7 @@
                 // fall through
             case Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS:
                 // fall through
-            case Features.LIST_FILTER_TOKENIZE_FUNCTION:
+            case Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS:
                 // fall through
             default:
                 return false; // AppSearch features absent in GMSCore AppSearch.
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
index 0d687c0..4e92e8a 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
@@ -87,18 +87,16 @@
             if (jetpackSearchSpec.isListFilterHasPropertyFunctionEnabled()) {
                 gmsBuilder.setListFilterHasPropertyFunctionEnabled(true);
             }
-            // Copy beyond-V features
-            if (jetpackSearchSpec.isEmbeddingSearchEnabled()
-                    || !jetpackSearchSpec.getSearchEmbeddings().isEmpty()) {
-                // TODO(b/326656531): Remove this once embedding search APIs are available.
-                throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
-                        + " is not available on this AppSearch implementation.");
-            }
-            if (jetpackSearchSpec.isListFilterTokenizeFunctionEnabled()) {
-                // TODO(b/332620561): Remove this once 'tokenize' is supported.
-                throw new UnsupportedOperationException(Features.LIST_FILTER_TOKENIZE_FUNCTION
-                        + " is not available on this AppSearch implementation.");
-            }
+        }
+        if (!jetpackSearchSpec.getEmbeddingParameters().isEmpty()) {
+            // TODO(b/326656531): Remove this once embedding search APIs are available.
+            throw new UnsupportedOperationException(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
+                    + " is not available on this AppSearch implementation.");
+        }
+        if (!jetpackSearchSpec.getSearchStringParameters().isEmpty()) {
+            // TODO(b/332620561): Remove this once search parameter strings are supported.
+            throw new UnsupportedOperationException(Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+                    + " is not available on this AppSearch implementation.");
         }
 
         if (jetpackSearchSpec.getJoinSpec() != null) {
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java
index 18556a7..119758a 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSuggestionSpecToGmsConverter.java
@@ -20,6 +20,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.Features;
 import androidx.appsearch.app.SearchSuggestionSpec;
 import androidx.core.util.Preconditions;
 
@@ -65,6 +66,13 @@
                 gmsBuilder.addFilterProperties(entry.getKey(), entry.getValue());
             }
         }
+
+        if (!jetpackSearchSuggestionSpec.getSearchStringParameters().isEmpty()) {
+            // TODO(b/332620561): Remove this once search parameter strings are supported.
+            throw new UnsupportedOperationException(
+                    Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
+                            + " is not available on this AppSearch implementation.");
+        }
         return gmsBuilder.build();
     }
 }
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 9ed73ae..efdb86e 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -302,7 +302,6 @@
     field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
     field public static final String LIST_FILTER_HAS_PROPERTY_FUNCTION = "LIST_FILTER_HAS_PROPERTY_FUNCTION";
     field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
-    field public static final String LIST_FILTER_TOKENIZE_FUNCTION = "LIST_FILTER_TOKENIZE_FUNCTION";
     field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
     field public static final String SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES = "SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES";
     field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
@@ -314,6 +313,7 @@
     field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
     field public static final String SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA = "SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA";
     field public static final String SEARCH_SPEC_PROPERTY_WEIGHTS = "SEARCH_SPEC_PROPERTY_WEIGHTS";
+    field public static final String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
     field public static final String SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG = "SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG";
     field public static final String SEARCH_SUGGESTION = "SEARCH_SUGGESTION";
     field public static final String SET_SCHEMA_CIRCULAR_REFERENCES = "SET_SCHEMA_CIRCULAR_REFERENCES";
@@ -608,6 +608,7 @@
   public final class SearchSpec {
     method public String getAdvancedRankingExpression();
     method public int getDefaultEmbeddingSearchMetricType();
+    method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getEmbeddingParameters();
     method public java.util.List<java.lang.String!> getFilterNamespaces();
     method public java.util.List<java.lang.String!> getFilterPackageNames();
     method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
@@ -624,15 +625,15 @@
     method public int getResultCountPerPage();
     method public int getResultGroupingLimit();
     method public int getResultGroupingTypeFlags();
-    method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
+    method @Deprecated public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
     method public String? getSearchSourceLogTag();
+    method public java.util.List<java.lang.String!> getSearchStringParameters();
     method public int getSnippetCount();
     method public int getSnippetCountPerProperty();
     method public int getTermMatch();
-    method public boolean isEmbeddingSearchEnabled();
+    method @Deprecated public boolean isEmbeddingSearchEnabled();
     method public boolean isListFilterHasPropertyFunctionEnabled();
     method public boolean isListFilterQueryLanguageEnabled();
-    method public boolean isListFilterTokenizeFunctionEnabled();
     method public boolean isNumericSearchEnabled();
     method public boolean isVerbatimSearchEnabled();
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
@@ -661,6 +662,8 @@
 
   public static final class SearchSpec.Builder {
     ctor public SearchSpec.Builder();
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
     method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.lang.String!...);
@@ -679,15 +682,16 @@
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
+    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
     method public androidx.appsearch.app.SearchSpec build();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
+    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_QUERY_LANGUAGE) public androidx.appsearch.app.SearchSpec.Builder setListFilterQueryLanguageEnabled(boolean);
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_TOKENIZE_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterTokenizeFunctionEnabled(boolean);
     method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setNumericSearchEnabled(boolean);
     method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
@@ -723,6 +727,7 @@
     method public java.util.List<java.lang.String!> getFilterSchemas();
     method public int getMaximumResultCount();
     method public int getRankingStrategy();
+    method public java.util.List<java.lang.String!> getSearchStringParameters();
     field public static final int SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT = 0; // 0x0
     field public static final int SUGGESTION_RANKING_STRATEGY_NONE = 2; // 0x2
     field public static final int SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY = 1; // 0x1
@@ -742,6 +747,8 @@
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
     method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.lang.String!...);
     method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.lang.String!...);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
     method public androidx.appsearch.app.SearchSuggestionSpec build();
     method public androidx.appsearch.app.SearchSuggestionSpec.Builder setRankingStrategy(int);
   }
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 9ed73ae..efdb86e 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -302,7 +302,6 @@
     field public static final String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
     field public static final String LIST_FILTER_HAS_PROPERTY_FUNCTION = "LIST_FILTER_HAS_PROPERTY_FUNCTION";
     field public static final String LIST_FILTER_QUERY_LANGUAGE = "LIST_FILTER_QUERY_LANGUAGE";
-    field public static final String LIST_FILTER_TOKENIZE_FUNCTION = "LIST_FILTER_TOKENIZE_FUNCTION";
     field public static final String NUMERIC_SEARCH = "NUMERIC_SEARCH";
     field public static final String SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES = "SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES";
     field public static final String SCHEMA_ADD_PARENT_TYPE = "SCHEMA_ADD_PARENT_TYPE";
@@ -314,6 +313,7 @@
     field public static final String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
     field public static final String SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA = "SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA";
     field public static final String SEARCH_SPEC_PROPERTY_WEIGHTS = "SEARCH_SPEC_PROPERTY_WEIGHTS";
+    field public static final String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
     field public static final String SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG = "SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG";
     field public static final String SEARCH_SUGGESTION = "SEARCH_SUGGESTION";
     field public static final String SET_SCHEMA_CIRCULAR_REFERENCES = "SET_SCHEMA_CIRCULAR_REFERENCES";
@@ -608,6 +608,7 @@
   public final class SearchSpec {
     method public String getAdvancedRankingExpression();
     method public int getDefaultEmbeddingSearchMetricType();
+    method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getEmbeddingParameters();
     method public java.util.List<java.lang.String!> getFilterNamespaces();
     method public java.util.List<java.lang.String!> getFilterPackageNames();
     method public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getFilterProperties();
@@ -624,15 +625,15 @@
     method public int getResultCountPerPage();
     method public int getResultGroupingLimit();
     method public int getResultGroupingTypeFlags();
-    method public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
+    method @Deprecated public java.util.List<androidx.appsearch.app.EmbeddingVector!> getSearchEmbeddings();
     method public String? getSearchSourceLogTag();
+    method public java.util.List<java.lang.String!> getSearchStringParameters();
     method public int getSnippetCount();
     method public int getSnippetCountPerProperty();
     method public int getTermMatch();
-    method public boolean isEmbeddingSearchEnabled();
+    method @Deprecated public boolean isEmbeddingSearchEnabled();
     method public boolean isListFilterHasPropertyFunctionEnabled();
     method public boolean isListFilterQueryLanguageEnabled();
-    method public boolean isListFilterTokenizeFunctionEnabled();
     method public boolean isNumericSearchEnabled();
     method public boolean isVerbatimSearchEnabled();
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
@@ -661,6 +662,8 @@
 
   public static final class SearchSpec.Builder {
     ctor public SearchSpec.Builder();
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(androidx.appsearch.app.EmbeddingVector!...);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addEmbeddingParameters(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
     method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(Class<? extends java.lang.Object!>!...) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addFilterDocumentClasses(java.util.Collection<? extends java.lang.Class<? extends java.lang.Object!>!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addFilterNamespaces(java.lang.String!...);
@@ -679,15 +682,16 @@
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<? extends java.lang.Object!>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(androidx.appsearch.app.EmbeddingVector!...);
+    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder addSearchEmbeddings(java.util.Collection<androidx.appsearch.app.EmbeddingVector!>);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.lang.String!...);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
     method public androidx.appsearch.app.SearchSpec build();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setDefaultEmbeddingSearchMetricType(int);
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
+    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public androidx.appsearch.app.SearchSpec.Builder setEmbeddingSearchEnabled(boolean);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.JOIN_SPEC_AND_QUALIFIED_ID) public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_HAS_PROPERTY_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterHasPropertyFunctionEnabled(boolean);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_QUERY_LANGUAGE) public androidx.appsearch.app.SearchSpec.Builder setListFilterQueryLanguageEnabled(boolean);
-    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.LIST_FILTER_TOKENIZE_FUNCTION) public androidx.appsearch.app.SearchSpec.Builder setListFilterTokenizeFunctionEnabled(boolean);
     method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.NUMERIC_SEARCH) public androidx.appsearch.app.SearchSpec.Builder setNumericSearchEnabled(boolean);
     method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
@@ -723,6 +727,7 @@
     method public java.util.List<java.lang.String!> getFilterSchemas();
     method public int getMaximumResultCount();
     method public int getRankingStrategy();
+    method public java.util.List<java.lang.String!> getSearchStringParameters();
     field public static final int SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT = 0; // 0x0
     field public static final int SUGGESTION_RANKING_STRATEGY_NONE = 2; // 0x2
     field public static final int SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY = 1; // 0x1
@@ -742,6 +747,8 @@
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterPropertyPaths(String, java.util.Collection<androidx.appsearch.app.PropertyPath!>);
     method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.lang.String!...);
     method public androidx.appsearch.app.SearchSuggestionSpec.Builder addFilterSchemas(java.util.Collection<java.lang.String!>);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.lang.String!...);
+    method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) public androidx.appsearch.app.SearchSuggestionSpec.Builder addSearchStringParameters(java.util.List<java.lang.String!>);
     method public androidx.appsearch.app.SearchSuggestionSpec build();
     method public androidx.appsearch.app.SearchSuggestionSpec.Builder setRankingStrategy(int);
   }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
index 7d18f74..23cdf04 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -2867,30 +2867,28 @@
         assertThat(outputDocument).isEqualTo(email);
 
         // senderEmbedding is non-indexable, so querying for it will return nothing.
-        searchResults = mSession.search("semanticSearch(getSearchSpecEmbedding(0), 0.9, 1)",
+        searchResults = mSession.search("semanticSearch(getEmbeddingParameter(0), 0.9, 1)",
                 new SearchSpec.Builder()
                         .setDefaultEmbeddingSearchMetricType(
                                 SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
-                        .addSearchEmbeddings(email.mSenderEmbedding)
+                        .addEmbeddingParameters(email.mSenderEmbedding)
                         .setRankingStrategy(
-                                "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                                "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                         .setListFilterQueryLanguageEnabled(true)
-                        .setEmbeddingSearchEnabled(true)
                         .build());
         results = retrieveAllSearchResults(searchResults);
         assertThat(results).isEmpty();
 
         // titleEmbedding is indexable, and querying for it using itself will return a cosine
         // similarity score of 1.
-        searchResults = mSession.search("semanticSearch(getSearchSpecEmbedding(0), 0.9, 1)",
+        searchResults = mSession.search("semanticSearch(getEmbeddingParameter(0), 0.9, 1)",
                 new SearchSpec.Builder()
                         .setDefaultEmbeddingSearchMetricType(
                                 SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
-                        .addSearchEmbeddings(email.mTitleEmbedding)
+                        .addEmbeddingParameters(email.mTitleEmbedding)
                         .setRankingStrategy(
-                                "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                                "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                         .setListFilterQueryLanguageEnabled(true)
-                        .setEmbeddingSearchEnabled(true)
                         .build());
         results = retrieveAllSearchResults(searchResults);
         assertThat(results).hasSize(1);
@@ -2901,18 +2899,17 @@
 
         // Both receiverEmbeddings and bodyEmbeddings are indexable, and in this specific
         // document, they together hold three embedding vectors with the same signature.
-        searchResults = mSession.search("semanticSearch(getSearchSpecEmbedding(0), -1, 1)",
+        searchResults = mSession.search("semanticSearch(getEmbeddingParameter(0), -1, 1)",
                 new SearchSpec.Builder()
                         .setDefaultEmbeddingSearchMetricType(
                                 SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
                         // Using one of the three vectors to query
-                        .addSearchEmbeddings(email.mBodyEmbeddings[0])
+                        .addEmbeddingParameters(email.mBodyEmbeddings[0])
                         .setRankingStrategy(
                                 // We should get a score of 3 for "len", since there are three
                                 // embedding vectors matched.
-                                "len(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                                "len(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                         .setListFilterQueryLanguageEnabled(true)
-                        .setEmbeddingSearchEnabled(true)
                         .build());
         results = retrieveAllSearchResults(searchResults);
         assertThat(results).hasSize(1);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
index e733fc3..5f0fbb6 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
@@ -127,10 +127,9 @@
                 new float[]{4.4f, 5.5f, 6.6f, 7.7f}, "my_model_v2");
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(embedding1, embedding2)
+                .addEmbeddingParameters(embedding1, embedding2)
                 .build();
 
         // Check that copy constructor works.
@@ -139,8 +138,8 @@
                 searchSpec.getEnabledFeatures());
         assertThat(searchSpecCopy.getDefaultEmbeddingSearchMetricType()).isEqualTo(
                 searchSpec.getDefaultEmbeddingSearchMetricType());
-        assertThat(searchSpecCopy.getSearchEmbeddings()).containsExactlyElementsIn(
-                searchSpec.getSearchEmbeddings());
+        assertThat(searchSpecCopy.getEmbeddingParameters()).containsExactlyElementsIn(
+                searchSpec.getEmbeddingParameters());
     }
 
     @Test
@@ -191,47 +190,4 @@
         assertThat(searchSpec3.getEnabledFeatures()).containsExactly(
                 Features.VERBATIM_SEARCH, Features.LIST_FILTER_QUERY_LANGUAGE);
     }
-
-    @Test
-    public void testGetEnabledFeatures_embeddingSearch() {
-        SearchSpec searchSpec = new SearchSpec.Builder()
-                .setNumericSearchEnabled(true)
-                .setVerbatimSearchEnabled(true)
-                .setListFilterQueryLanguageEnabled(true)
-                .setListFilterHasPropertyFunctionEnabled(true)
-                .setEmbeddingSearchEnabled(true)
-                .build();
-        assertThat(searchSpec.getEnabledFeatures()).containsExactly(
-                Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
-                Features.LIST_FILTER_QUERY_LANGUAGE, Features.LIST_FILTER_HAS_PROPERTY_FUNCTION,
-                FeatureConstants.EMBEDDING_SEARCH);
-
-        // Check that copy constructor works.
-        SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
-        assertThat(searchSpecCopy.getEnabledFeatures()).containsExactly(
-                Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
-                Features.LIST_FILTER_QUERY_LANGUAGE, Features.LIST_FILTER_HAS_PROPERTY_FUNCTION,
-                FeatureConstants.EMBEDDING_SEARCH);
-    }
-
-    @Test
-    public void testGetEnabledFeatures_tokenize() {
-        SearchSpec searchSpec = new SearchSpec.Builder()
-                .setNumericSearchEnabled(true)
-                .setVerbatimSearchEnabled(true)
-                .setListFilterQueryLanguageEnabled(true)
-                .setListFilterTokenizeFunctionEnabled(true)
-                .build();
-        assertThat(searchSpec.getEnabledFeatures()).containsExactly(
-                Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
-                Features.LIST_FILTER_QUERY_LANGUAGE,
-                FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION);
-
-        // Check that copy constructor works.
-        SearchSpec searchSpecCopy = new SearchSpec.Builder(searchSpec).build();
-        assertThat(searchSpecCopy.getEnabledFeatures()).containsExactly(
-                Features.NUMERIC_SEARCH, Features.VERBATIM_SEARCH,
-                Features.LIST_FILTER_QUERY_LANGUAGE,
-                FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION);
-    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
index 97698a7..6260cef 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
@@ -8713,14 +8713,13 @@
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(searchEmbedding)
+                .addEmbeddingParameters(searchEmbedding)
                 .setRankingStrategy(
-                        "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                        "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .build();
         SearchResults searchResults = mDb1.search(
-                "semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec);
+                "semanticSearch(getEmbeddingParameter(0), -1, 1)", searchSpec);
         List<SearchResult> results = retrieveAllSearchResults(searchResults);
         assertThat(results).hasSize(2);
         assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8798,13 +8797,12 @@
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(searchEmbedding)
-                .setRankingStrategy("sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                .addEmbeddingParameters(searchEmbedding)
+                .setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .build();
         SearchResults searchResults = mDb1.search(
-                "embedding1:semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec);
+                "embedding1:semanticSearch(getEmbeddingParameter(0), -1, 1)", searchSpec);
         List<SearchResult> results = retrieveAllSearchResults(searchResults);
         assertThat(results).hasSize(2);
         assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8886,15 +8884,14 @@
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(searchEmbedding1, searchEmbedding2)
-                .setRankingStrategy("sum(this.matchedSemanticScores(getSearchSpecEmbedding(0))) + "
-                        + "sum(this.matchedSemanticScores(getSearchSpecEmbedding(1)))")
+                .addEmbeddingParameters(searchEmbedding1, searchEmbedding2)
+                .setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0))) + "
+                        + "sum(this.matchedSemanticScores(getEmbeddingParameter(1)))")
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .build();
         SearchResults searchResults = mDb1.search(
-                "semanticSearch(getSearchSpecEmbedding(0)) OR "
-                        + "semanticSearch(getSearchSpecEmbedding(1))", searchSpec);
+                "semanticSearch(getEmbeddingParameter(0)) OR "
+                        + "semanticSearch(getEmbeddingParameter(1))", searchSpec);
         List<SearchResult> results = retrieveAllSearchResults(searchResults);
         assertThat(results).hasSize(2);
         assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8971,13 +8968,12 @@
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(searchEmbedding)
-                .setRankingStrategy("sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                .addEmbeddingParameters(searchEmbedding)
+                .setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .build();
         SearchResults searchResults = mDb1.search(
-                "foo OR semanticSearch(getSearchSpecEmbedding(0), -10, -1)", searchSpec);
+                "foo OR semanticSearch(getEmbeddingParameter(0), -10, -1)", searchSpec);
         List<SearchResult> results = retrieveAllSearchResults(searchResults);
         assertThat(results).hasSize(2);
         assertThat(results.get(0).getGenericDocument()).isEqualTo(doc0);
@@ -8988,89 +8984,33 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
-    public void testEmbeddingSearchWithoutEnablingFeatureFails() throws Exception {
-        assumeTrue(
-                mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG));
-
-        // Schema registration
-        AppSearchSchema schema = new AppSearchSchema.Builder("Email")
-                .addProperty(new StringPropertyConfig.Builder("body")
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .build())
-                .addProperty(new AppSearchSchema.EmbeddingPropertyConfig.Builder("embedding1")
-                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
-                        .build())
-                .addProperty(new AppSearchSchema.EmbeddingPropertyConfig.Builder("embedding2")
-                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
-                        .build())
-                .build();
-        mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(schema).build()).get();
-
-        // Index documents
-        GenericDocument doc =
-                new GenericDocument.Builder<>("namespace", "id0", "Email")
-                        .setPropertyString("body", "foo")
-                        .setCreationTimestampMillis(1000)
-                        .setPropertyEmbedding("embedding1", new EmbeddingVector(
-                                new float[]{0.1f, 0.2f, 0.3f, 0.4f, 0.5f}, "my_model_v1"))
-                        .setPropertyEmbedding("embedding2", new EmbeddingVector(
-                                        new float[]{-0.1f, -0.2f, -0.3f, 0.4f, 0.5f},
-                                        "my_model_v1"),
-                                new EmbeddingVector(
-                                        new float[]{0.6f, 0.7f, 0.8f}, "my_model_v2"))
-                        .build();
-        checkIsBatchResultSuccess(mDb1.putAsync(
-                new PutDocumentsRequest.Builder().addGenericDocuments(doc).build()));
-
-        EmbeddingVector searchEmbedding = new EmbeddingVector(
-                new float[]{1, -1, -1, 1, -1}, "my_model_v1");
-        SearchSpec searchSpec = new SearchSpec.Builder()
-                .setDefaultEmbeddingSearchMetricType(
-                        SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(searchEmbedding)
-                .setRankingStrategy(
-                        "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
-                .setListFilterQueryLanguageEnabled(true)
-                .build();
-        SearchResults searchResults = mDb1.search(
-                "semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec);
-        ExecutionException executionException = assertThrows(ExecutionException.class,
-                () -> searchResults.getNextPageAsync().get());
-        assertThat(executionException).hasCauseThat().isInstanceOf(AppSearchException.class);
-        AppSearchException exception = (AppSearchException) executionException.getCause();
-        assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT);
-        assertThat(exception).hasMessageThat().contains("Attempted use of unenabled feature");
-        assertThat(exception).hasMessageThat().contains("EMBEDDING_SEARCH");
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
     public void testEmbeddingSearch_notSupported() throws Exception {
         assumeTrue(
                 mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_QUERY_LANGUAGE));
         assumeFalse(
                 mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG));
 
+        EmbeddingVector searchEmbedding = new EmbeddingVector(
+                new float[]{-1, -1, 1}, "my_model_v2");
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
+                .addEmbeddingParameters(searchEmbedding)
                 .build();
         UnsupportedOperationException exception = assertThrows(
                 UnsupportedOperationException.class,
-                () -> mDb1.search("semanticSearch(getSearchSpecEmbedding(0), -1, 1)", searchSpec));
+                () -> mDb1.search("semanticSearch(getEmbeddingParameter(0), -1, 1)", searchSpec));
         assertThat(exception).hasMessageThat().contains(Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG
                 + " is not available on this AppSearch implementation.");
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
-    public void testTokenizeSearch_simple() throws Exception {
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+    public void testSearchSpecStrings_simple() throws Exception {
         assumeTrue(
                 mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_QUERY_LANGUAGE));
         assumeTrue(
-                mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_TOKENIZE_FUNCTION));
+                mDb1.getFeatures().isFeatureSupported(
+                        Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS));
 
         // Schema registration
         AppSearchSchema schema = new AppSearchSchema.Builder("Email")
@@ -9104,45 +9044,67 @@
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                 .setListFilterQueryLanguageEnabled(true)
-                .setListFilterTokenizeFunctionEnabled(true)
+                .addSearchStringParameters("foo.")
                 .build();
-        SearchResults searchResults = mDb1.search("tokenize(\"foo.\")", searchSpec);
+        SearchResults searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
         List<GenericDocument> results = convertSearchResultsToDocuments(searchResults);
         assertThat(results).containsExactly(doc2, doc0);
 
-        searchResults = mDb1.search("tokenize(\"bar, foo\")", searchSpec);
+        searchSpec = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .setListFilterQueryLanguageEnabled(true)
+                .addSearchStringParameters("bar, foo")
+                .build();
+        searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
         results = convertSearchResultsToDocuments(searchResults);
         assertThat(results).containsExactly(doc0);
 
-        searchResults = mDb1.search("tokenize(\"\\\"bar, \\\"foo\\\"\")", searchSpec);
+        searchSpec = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .setListFilterQueryLanguageEnabled(true)
+                .addSearchStringParameters("\\\"bar, \\\"foo\\\"")
+                .build();
+        searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
         results = convertSearchResultsToDocuments(searchResults);
         assertThat(results).containsExactly(doc0);
 
-        searchResults = mDb1.search("tokenize(\"bar ) foo\")", searchSpec);
+        searchSpec = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .setListFilterQueryLanguageEnabled(true)
+                .addSearchStringParameters("bar ) foo")
+                .build();
+        searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
         results = convertSearchResultsToDocuments(searchResults);
         assertThat(results).containsExactly(doc0);
 
-        searchResults = mDb1.search("tokenize(\"bar foo(\")", searchSpec);
+        searchSpec = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .setListFilterQueryLanguageEnabled(true)
+                .addSearchStringParameters("bar foo(")
+                .build();
+        searchResults = mDb1.search("getSearchStringParameter(0)", searchSpec);
         results = convertSearchResultsToDocuments(searchResults);
         assertThat(results).containsExactly(doc0);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
-    public void testTokenizeSearch_notSupported() throws Exception {
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+    public void testSearchSpecString_notSupported() throws Exception {
         assumeTrue(
                 mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_QUERY_LANGUAGE));
         assumeFalse(
-                mDb1.getFeatures().isFeatureSupported(Features.LIST_FILTER_TOKENIZE_FUNCTION));
+                mDb1.getFeatures().isFeatureSupported(
+                        Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS));
 
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setListFilterQueryLanguageEnabled(true)
-                .setListFilterTokenizeFunctionEnabled(true)
+                .addSearchStringParameters("bar foo(")
                 .build();
         UnsupportedOperationException exception = assertThrows(
                 UnsupportedOperationException.class,
-                () -> mDb1.search("tokenize(\"foo.\")", searchSpec));
-        assertThat(exception).hasMessageThat().contains(Features.LIST_FILTER_TOKENIZE_FUNCTION
+                () -> mDb1.search("getSearchStringParameter(0)", searchSpec));
+        assertThat(exception).hasMessageThat().contains(
+                Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS
                 + " is not available on this AppSearch implementation.");
     }
 
@@ -9198,17 +9160,16 @@
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(searchEmbedding)
+                .addEmbeddingParameters(searchEmbedding)
                 .setRankingStrategy(
-                        "sum(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                        "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                 .addInformationalRankingExpressions(
-                        "len(this.matchedSemanticScores(getSearchSpecEmbedding(0)))")
+                        "len(this.matchedSemanticScores(getEmbeddingParameter(0)))")
                 .addInformationalRankingExpressions("this.documentScore()")
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .build();
         SearchResults searchResults = mDb1.search(
-                "semanticSearch(getSearchSpecEmbedding(0))", searchSpec);
+                "semanticSearch(getEmbeddingParameter(0))", searchSpec);
         List<SearchResult> results = retrieveAllSearchResults(searchResults);
         assertThat(results).hasSize(2);
         // doc0:
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
index a6820fb..5b5ac39 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSpecCtsTest.java
@@ -40,6 +40,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -686,16 +687,14 @@
                 new float[]{4.4f, 5.5f, 6.6f, 7.7f}, "my_model_v2");
         SearchSpec searchSpec = new SearchSpec.Builder()
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(embedding1, embedding2)
+                .addEmbeddingParameters(embedding1, embedding2)
                 .build();
         assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
-        assertThat(searchSpec.isEmbeddingSearchEnabled()).isTrue();
         assertThat(searchSpec.getDefaultEmbeddingSearchMetricType()).isEqualTo(
                 SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
-        assertThat(searchSpec.getSearchEmbeddings()).containsExactly(embedding1, embedding2);
+        assertThat(searchSpec.getEmbeddingParameters()).containsExactly(embedding1, embedding2);
     }
 
     @Test
@@ -709,28 +708,25 @@
         // Create a builder
         SearchSpec.Builder searchSpecBuilder = new SearchSpec.Builder()
                 .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .setDefaultEmbeddingSearchMetricType(
                         SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT)
-                .addSearchEmbeddings(embedding1);
+                .addEmbeddingParameters(embedding1);
         SearchSpec searchSpec1 = searchSpecBuilder.build();
 
         // Add a new embedding to the builder and rebuild. We should see that the new embedding
         // is only added to searchSpec2.
-        searchSpecBuilder.addSearchEmbeddings(embedding2);
+        searchSpecBuilder.addEmbeddingParameters(embedding2);
         SearchSpec searchSpec2 = searchSpecBuilder.build();
 
         assertThat(searchSpec1.isListFilterQueryLanguageEnabled()).isTrue();
-        assertThat(searchSpec1.isEmbeddingSearchEnabled()).isTrue();
         assertThat(searchSpec1.getDefaultEmbeddingSearchMetricType()).isEqualTo(
                 SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
-        assertThat(searchSpec1.getSearchEmbeddings()).containsExactly(embedding1);
+        assertThat(searchSpec1.getEmbeddingParameters()).containsExactly(embedding1);
 
         assertThat(searchSpec2.isListFilterQueryLanguageEnabled()).isTrue();
-        assertThat(searchSpec2.isEmbeddingSearchEnabled()).isTrue();
         assertThat(searchSpec2.getDefaultEmbeddingSearchMetricType()).isEqualTo(
                 SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
-        assertThat(searchSpec2.getSearchEmbeddings()).containsExactly(embedding1, embedding2);
+        assertThat(searchSpec2.getEmbeddingParameters()).containsExactly(embedding1, embedding2);
     }
 
     @Test
@@ -741,62 +737,12 @@
                 .setVerbatimSearchEnabled(true)
                 .setListFilterQueryLanguageEnabled(true)
                 .setListFilterHasPropertyFunctionEnabled(true)
-                .setEmbeddingSearchEnabled(true)
                 .build();
 
         assertThat(searchSpec.isNumericSearchEnabled()).isTrue();
         assertThat(searchSpec.isVerbatimSearchEnabled()).isTrue();
         assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
         assertThat(searchSpec.isListFilterHasPropertyFunctionEnabled()).isTrue();
-        assertThat(searchSpec.isEmbeddingSearchEnabled()).isTrue();
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
-    public void testSetFeatureEnabledToFalse_embeddingSearch() {
-        SearchSpec.Builder builder = new SearchSpec.Builder();
-        SearchSpec searchSpec = builder
-                .setListFilterQueryLanguageEnabled(true)
-                .setEmbeddingSearchEnabled(true)
-                .build();
-        assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
-        assertThat(searchSpec.isEmbeddingSearchEnabled()).isTrue();
-
-        searchSpec = builder
-                .setListFilterQueryLanguageEnabled(false)
-                .setEmbeddingSearchEnabled(false)
-                .build();
-        assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isFalse();
-        assertThat(searchSpec.isEmbeddingSearchEnabled()).isFalse();
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
-    public void testListFilterTokenizeFunction() {
-        SearchSpec searchSpec = new SearchSpec.Builder()
-                .setListFilterQueryLanguageEnabled(true)
-                .setListFilterTokenizeFunctionEnabled(true)
-                .build();
-        assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
-        assertThat(searchSpec.isListFilterTokenizeFunctionEnabled()).isTrue();
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
-    public void testSetFeatureEnabledToFalse_tokenizeFunction() {
-        SearchSpec.Builder builder = new SearchSpec.Builder();
-        SearchSpec searchSpec = builder
-                .setListFilterQueryLanguageEnabled(true)
-                .setListFilterTokenizeFunctionEnabled(true)
-                .build();
-        assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
-        assertThat(searchSpec.isListFilterTokenizeFunctionEnabled()).isTrue();
-
-        searchSpec = builder
-                .setListFilterTokenizeFunctionEnabled(false)
-                .build();
-        assertThat(searchSpec.isListFilterQueryLanguageEnabled()).isTrue();
-        assertThat(searchSpec.isListFilterTokenizeFunctionEnabled()).isFalse();
     }
 
     @Test
@@ -835,4 +781,45 @@
         assertThat(rebuild.getInformationalRankingExpressions())
                 .containsExactly("this.relevanceScore()", "this.documentScore()").inOrder();
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+    public void testSearchSpecStrings_default_isEmpty() {
+        SearchSpec searchSpec = new SearchSpec.Builder().build();
+        assertThat(searchSpec.getSearchStringParameters()).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+    public void testSearchSpecStrings_addValues_areCumulative() {
+        SearchSpec.Builder searchSpecBuilder =
+                new SearchSpec.Builder().addSearchStringParameters("A", "b");
+        SearchSpec spec = searchSpecBuilder.build();
+        assertThat(spec.getSearchStringParameters()).containsExactly("A", "b").inOrder();
+
+        searchSpecBuilder.addSearchStringParameters(Arrays.asList("C", "d"));
+        spec = searchSpecBuilder.build();
+        assertThat(spec.getSearchStringParameters()).containsExactly("A", "b", "C", "d").inOrder();
+
+        searchSpecBuilder.addSearchStringParameters("e");
+        spec = searchSpecBuilder.build();
+        assertThat(spec.getSearchStringParameters())
+                .containsExactly("A", "b", "C", "d", "e").inOrder();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+    public void testSearchSpecStrings_rebuild_doesntAffectOriginal() {
+        SearchSpec.Builder searchSpecBuilder =
+                new SearchSpec.Builder().addSearchStringParameters("A", "b");
+
+        SearchSpec original = searchSpecBuilder.build();
+        SearchSpec rebuild =
+                searchSpecBuilder.addSearchStringParameters(Arrays.asList("C", "d")).build();
+
+        // Rebuild won't effect the original object
+        assertThat(original.getSearchStringParameters()).containsExactly("A", "b").inOrder();
+        assertThat(rebuild.getSearchStringParameters())
+                .containsExactly("A", "b", "C", "d").inOrder();
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
index ee67901..06caa38 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
@@ -110,9 +110,10 @@
     }
 
     @Test
-    public void testFlagValue_enableListFilterTokenizeFunction() {
-        assertThat(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
-                .isEqualTo("com.android.appsearch.flags.enable_list_filter_tokenize_function");
+    public void testFlagValue_enableSearchSpecSearchStringParameters() {
+        assertThat(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+                .isEqualTo(
+                        "com.android.appsearch.flags.enable_search_spec_search_spec_strings");
     }
 
     @Test
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
index 58821b7..936a417 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
@@ -224,16 +224,16 @@
      * documentB, regardless of whether a value is actually set.
      *
      * <p>{@link Features#SCHEMA_EMBEDDING_PROPERTY_CONFIG}: This feature covers the
-     * "semanticSearch" and "getSearchSpecEmbedding" functions in query expressions, which are
+     * "semanticSearch" and "getEmbeddingParameter" functions in query expressions, which are
      * used for semantic search.
      *
-     * <p>Usage: semanticSearch(getSearchSpecEmbedding({embedding_index}), {low}, {high}, {metric})
+     * <p>Usage: semanticSearch(getEmbeddingParameter({embedding_index}), {low}, {high}, {metric})
      * <ul>
      *     <li>semanticSearch matches all documents that have at least one embedding vector with
      *     a matching model signature (see {@link EmbeddingVector#getModelSignature()}) and a
      *     similarity score within the range specified based on the provided metric.</li>
-     *     <li>getSearchSpecEmbedding({embedding_index}) retrieves the embedding search passed in
-     *     {@link SearchSpec.Builder#addSearchEmbeddings} based on the index specified, which
+     *     <li>getEmbeddingParameter({embedding_index}) retrieves the embedding search passed in
+     *     {@link SearchSpec.Builder#addEmbeddingParameters} based on the index specified, which
      *     starts from 0.</li>
      *     <li>"low" and "high" are floating point numbers that specify the similarity score
      *     range. If omitted, they default to negative and positive infinity, respectively.</li>
@@ -250,27 +250,27 @@
      *
      * <p>Examples:
      * <ul>
-     *     <li>Basic: semanticSearch(getSearchSpecEmbedding(0), 0.5, 1, "COSINE")</li>
+     *     <li>Basic: semanticSearch(getEmbeddingParameter(0), 0.5, 1, "COSINE")</li>
      *     <li>With a property restriction:
-     *     property1:semanticSearch(getSearchSpecEmbedding(0), 0.5, 1)</li>
-     *     <li>Hybrid: foo OR semanticSearch(getSearchSpecEmbedding(0), 0.5, 1)</li>
-     *     <li>Complex: (foo OR semanticSearch(getSearchSpecEmbedding(0), 0.5, 1)) AND bar</li>
+     *     property1:semanticSearch(getEmbeddingParameter(0), 0.5, 1)</li>
+     *     <li>Hybrid: foo OR semanticSearch(getEmbeddingParameter(0), 0.5, 1)</li>
+     *     <li>Complex: (foo OR semanticSearch(getEmbeddingParameter(0), 0.5, 1)) AND bar</li>
      * </ul>
      *
-     * <p>{@link Features#LIST_FILTER_TOKENIZE_FUNCTION}: This feature covers the
-     * "tokenize" function in query expressions, which takes a string and treats the entire string
-     * as plain text. This string is then segmented, normalized and stripped of punctuation-only
-     * segments. The remaining tokens are then AND'd together. This function is useful for callers
-     * who wish to provide user input, but want to ensure that that user input does not invoke any
-     * query operators.
+     * <p>{@link Features#SEARCH_SPEC_SEARCH_STRING_PARAMETERS}: This feature covers the
+     * "getSearchStringParameter" function in query expressions, which substitutes the string
+     * provided at the same index in {@link SearchSpec.Builder#addSearchStringParameters} into the
+     * query as plain text. This string is then segmented, normalized and stripped of
+     * punctuation-only segments. The remaining tokens are then AND'd together. This function is
+     * useful for callers who wish to provide user input, but want to ensure that that user input
+     * does not invoke any query operators.
      *
-     * <p>Ex. `foo OR tokenize("bar OR baz.")`. The string "bar OR baz." will be segmented into
-     * "bar", "OR", "baz", ".". Punctuation is removed and the segments are normalized to "bar",
-     * "or", "baz". This query will be equivalent to `foo OR (bar AND or AND baz)`.
+     * <p>Usage: getSearchStringParameter({search_parameter_strings_index})
      *
-     * <p>Ex. `tokenize("\"bar\" OR \\baz")`. Quotation marks and escape characters must be escaped.
-     * This query will be segmented into "\"", "bar", "\"", "OR", "\", "baz". Once stripped of
-     * punctuation and normalized, this will be equivalent to the query `bar AND or AND baz`.
+     * <p>Ex. `foo OR getSearchStringParameter(0)` with {@link SearchSpec#getSearchStringParameters}
+     * returning {"bar OR baz."}. The string "bar OR baz." will be segmented into "bar", "OR",
+     * "baz", ".". Punctuation is removed and the segments are normalized to "bar", "or", "baz".
+     * This query will be equivalent to `foo OR (bar AND or AND baz)`.
      *
      * <p>The availability of each of these features can be checked by calling
      * {@link Features#isFeatureSupported} with the desired feature.
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java
index 1addca7..a691027 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/EmbeddingVector.java
@@ -43,7 +43,7 @@
  * <p>For more details on how embedding search works, check {@link AppSearchSession#search} and
  * {@link SearchSpec.Builder#setRankingStrategy(String)}.
  *
- * @see SearchSpec.Builder#addSearchEmbeddings
+ * @see SearchSpec.Builder#addEmbeddingParameters
  * @see GenericDocument.Builder#setPropertyEmbedding
  */
 @RequiresFeature(
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java
index c89fb1e..5a54e55f 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/FeatureConstants.java
@@ -44,8 +44,5 @@
     /** A feature constant for the "semanticSearch" function in {@link AppSearchSession#search}. */
     public static final String EMBEDDING_SEARCH = "EMBEDDING_SEARCH";
 
-    /** A feature constant for the "tokenize" function in {@link AppSearchSession#search}. */
-    public static final String LIST_FILTER_TOKENIZE_FUNCTION = "TOKENIZE";
-
     private FeatureConstants() {}
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
index e89dc99..ac1de38 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
@@ -120,15 +120,6 @@
     String LIST_FILTER_HAS_PROPERTY_FUNCTION = FeatureConstants.LIST_FILTER_HAS_PROPERTY_FUNCTION;
 
     /**
-     * Feature for {@link #isFeatureSupported(String)}. This feature covers the use of the
-     * "tokenize" function in query expressions.
-     *
-     * <p>For details on the "tokenize" function in the query language, see
-     * {@link AppSearchSession#search}.
-     */
-    String LIST_FILTER_TOKENIZE_FUNCTION = "LIST_FILTER_TOKENIZE_FUNCTION";
-
-    /**
      * Feature for {@link #isFeatureSupported(String)}. This feature covers whether or not the
      * AppSearch backend can store the descriptions returned by
      * {@link AppSearchSchema#getDescription} and
@@ -172,6 +163,13 @@
     String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
 
     /**
+     * Feature for {@link #isFeatureSupported(String)}. This feature covers the support of the
+     * {@link SearchSpec.Builder#addSearchStringParameters} and
+     * {@link SearchSuggestionSpec.Builder#addSearchStringParameters} apis.
+     */
+    String SEARCH_SPEC_SEARCH_STRING_PARAMETERS = "SEARCH_SPEC_SEARCH_STRING_PARAMETERS";
+
+    /**
      * Feature for {@link #isFeatureSupported(String)}. This feature covers
      * {@link AppSearchSchema.StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID},
      * {@link SearchSpec.Builder#setJoinSpec}, and all other join features.
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index 1b43e15..5128307d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -146,8 +146,8 @@
     @Nullable private final String mSearchSourceLogTag;
 
     @NonNull
-    @Field(id = 20, getter = "getSearchEmbeddings")
-    private final List<EmbeddingVector> mSearchEmbeddings;
+    @Field(id = 20, getter = "getEmbeddingParameters")
+    private final List<EmbeddingVector> mEmbeddingParameters;
 
     @Field(id = 21, getter = "getDefaultEmbeddingSearchMetricType")
     private final int mDefaultEmbeddingSearchMetricType;
@@ -156,6 +156,10 @@
     @Field(id = 22, getter = "getInformationalRankingExpressions")
     private final List<String> mInformationalRankingExpressions;
 
+    @NonNull
+    @Field(id = 23, getter = "getSearchStringParameters")
+    private final List<String> mSearchStringParameters;
+
     /**
      * Default number of documents per page.
      *
@@ -364,9 +368,10 @@
             @Param(id = 17) @NonNull String advancedRankingExpression,
             @Param(id = 18) @NonNull List<String> enabledFeatures,
             @Param(id = 19) @Nullable String searchSourceLogTag,
-            @Param(id = 20) @Nullable List<EmbeddingVector> searchEmbeddings,
+            @Param(id = 20) @Nullable List<EmbeddingVector> embeddingParameters,
             @Param(id = 21) int defaultEmbeddingSearchMetricType,
-            @Param(id = 22) @Nullable List<String> informationalRankingExpressions
+            @Param(id = 22) @Nullable List<String> informationalRankingExpressions,
+            @Param(id = 23) @Nullable List<String> searchStringParameters
     ) {
         mTermMatchType = termMatchType;
         mSchemas = Collections.unmodifiableList(Preconditions.checkNotNull(schemas));
@@ -388,10 +393,10 @@
         mEnabledFeatures = Collections.unmodifiableList(
                 Preconditions.checkNotNull(enabledFeatures));
         mSearchSourceLogTag = searchSourceLogTag;
-        if (searchEmbeddings != null) {
-            mSearchEmbeddings = Collections.unmodifiableList(searchEmbeddings);
+        if (embeddingParameters != null) {
+            mEmbeddingParameters = Collections.unmodifiableList(embeddingParameters);
         } else {
-            mSearchEmbeddings = Collections.emptyList();
+            mEmbeddingParameters = Collections.emptyList();
         }
         mDefaultEmbeddingSearchMetricType = defaultEmbeddingSearchMetricType;
         if (informationalRankingExpressions != null) {
@@ -400,6 +405,10 @@
         } else {
             mInformationalRankingExpressions = Collections.emptyList();
         }
+        mSearchStringParameters =
+                (searchStringParameters != null)
+                        ? Collections.unmodifiableList(searchStringParameters)
+                        : Collections.emptyList();
     }
 
 
@@ -673,12 +682,30 @@
     }
 
     /**
-     * Returns the list of {@link EmbeddingVector} for embedding search.
+     * Returns the list of {@link EmbeddingVector} that can be referenced in the query through the
+     * "getEmbeddingParameter({index})" function.
+     *
+     * @see AppSearchSession#search
+     *
+     * @deprecated Use {@link #getEmbeddingParameters}.
      */
     @NonNull
     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+    @Deprecated
     public List<EmbeddingVector> getSearchEmbeddings() {
-        return mSearchEmbeddings;
+        return getEmbeddingParameters();
+    }
+
+    /**
+     * Returns the list of {@link EmbeddingVector} that can be referenced in the query through the
+     * "getEmbeddingParameter({index})" function.
+     *
+     * @see AppSearchSession#search
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+    public List<EmbeddingVector> getEmbeddingParameters() {
+        return mEmbeddingParameters;
     }
 
     /**
@@ -704,6 +731,18 @@
     }
 
     /**
+     * Returns the list of String parameters that can be referenced in the query through the
+     * "getSearchStringParameter({index})" function.
+     *
+     * @see AppSearchSession#search
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+    public List<String> getSearchStringParameters() {
+        return mSearchStringParameters;
+    }
+
+    /**
      * Returns whether the NUMERIC_SEARCH feature is enabled.
      */
     public boolean isNumericSearchEnabled() {
@@ -734,21 +773,18 @@
 
     /**
      * Returns whether the embedding search feature is enabled.
+     *
+     * @deprecated AppSearch no longer requires clients to explicitly enable embedding search
+     * because {@link Builder#addEmbeddingParameters} is already guarded by {@link
+     * androidx.appsearch.app.Features#isFeatureSupported}.
      */
+    @Deprecated
     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
     public boolean isEmbeddingSearchEnabled() {
         return mEnabledFeatures.contains(FeatureConstants.EMBEDDING_SEARCH);
     }
 
     /**
-     * Returns whether the LIST_FILTER_TOKENIZE_FUNCTION feature is enabled.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
-    public boolean isListFilterTokenizeFunctionEnabled() {
-        return mEnabledFeatures.contains(FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION);
-    }
-
-    /**
      * Get the list of enabled features that the caller is intending to use in this search call.
      *
      * @return the set of {@link Features} enabled in this {@link SearchSpec} Entry.
@@ -776,7 +812,8 @@
         private ArraySet<String> mEnabledFeatures = new ArraySet<>();
         private Bundle mProjectionTypePropertyMasks = new Bundle();
         private Bundle mTypePropertyWeights = new Bundle();
-        private List<EmbeddingVector> mSearchEmbeddings = new ArrayList<>();
+        private List<EmbeddingVector> mEmbeddingParameters = new ArrayList<>();
+        private List<String> mSearchStringParameters = new ArrayList<>();
 
         private int mResultCountPerPage = DEFAULT_NUM_PER_PAGE;
         @TermMatch private int mTermMatchType = TERM_MATCH_PREFIX;
@@ -818,7 +855,8 @@
                     searchSpec.getPropertyWeights().entrySet()) {
                 setPropertyWeights(entry.getKey(), entry.getValue());
             }
-            mSearchEmbeddings = new ArrayList<>(searchSpec.getSearchEmbeddings());
+            mEmbeddingParameters = new ArrayList<>(searchSpec.getEmbeddingParameters());
+            mSearchStringParameters = new ArrayList<>(searchSpec.getSearchStringParameters());
             mResultCountPerPage = searchSpec.getResultCountPerPage();
             mTermMatchType = searchSpec.getTermMatch();
             mDefaultEmbeddingSearchMetricType = searchSpec.getDefaultEmbeddingSearchMetricType();
@@ -1232,7 +1270,7 @@
          *     current document being scored. Property weights come from what's specified in
          *     {@link SearchSpec}. After normalizing, each provided weight will be divided by the
          *     maximum weight, so that each of them will be <= 1.
-         *     <li>this.matchedSemanticScores(getSearchSpecEmbedding({embedding_index}), {metric})
+         *     <li>this.matchedSemanticScores(getEmbeddingParameter({embedding_index}), {metric})
          *     <p>Returns a list of the matched similarity scores from "semanticSearch" in the query
          *     expression (see also {@link AppSearchSession#search}) based on embedding_index and
          *     metric. If metric is omitted, it defaults to the metric specified in
@@ -1241,9 +1279,9 @@
          *     function will return an empty list. If multiple "semanticSearch"s are called for
          *     the same embedding_index and metric, this function will return a list of their
          *     merged scores.
-         *     <p>Example: `this.matchedSemanticScores(getSearchSpecEmbedding(0), "COSINE")` will
+         *     <p>Example: `this.matchedSemanticScores(getEmbeddingParameter(0), "COSINE")` will
          *     return a list of matched scores within the range of [0.5, 1], if
-         *     `semanticSearch(getSearchSpecEmbedding(0), 0.5, 1, "COSINE")` is called in the
+         *     `semanticSearch(getEmbeddingParameter(0), 0.5, 1, "COSINE")` is called in the
          *     query expression.
          * </ul>
          *
@@ -1870,13 +1908,14 @@
             return setPropertyWeightPaths(factory.getSchemaName(), propertyPathWeights);
         }
 // @exportToFramework:endStrip()
-
         /**
          * Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
          * query expression and the ranking expression for embedding search.
          *
          * @see AppSearchSession#search
          * @see SearchSpec.Builder#setRankingStrategy(String)
+         *
+         * @deprecated Use {@link #addEmbeddingParameters}
          */
         @CanIgnoreReturnValue
         @NonNull
@@ -1884,10 +1923,30 @@
                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
                 name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+        @Deprecated
         public Builder addSearchEmbeddings(@NonNull EmbeddingVector... searchEmbeddings) {
-            Preconditions.checkNotNull(searchEmbeddings);
-            resetIfBuilt();
-            return addSearchEmbeddings(Arrays.asList(searchEmbeddings));
+            return addEmbeddingParameters(searchEmbeddings);
+        }
+
+        /**
+         * Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
+         * query expression and the ranking expression for embedding search.
+         *
+         * @see AppSearchSession#search
+         * @see SearchSpec.Builder#setRankingStrategy(String)
+         *
+         * @deprecated Use {@link #addEmbeddingParameters}
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+        @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+        @Deprecated
+        public Builder addSearchEmbeddings(
+                @NonNull Collection<EmbeddingVector> searchEmbeddings) {
+            return addEmbeddingParameters(searchEmbeddings);
         }
 
         /**
@@ -1903,11 +1962,30 @@
                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
                 name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
-        public Builder addSearchEmbeddings(
+        public Builder addEmbeddingParameters(@NonNull EmbeddingVector... searchEmbeddings) {
+            Preconditions.checkNotNull(searchEmbeddings);
+            resetIfBuilt();
+            return addEmbeddingParameters(Arrays.asList(searchEmbeddings));
+        }
+
+        /**
+         * Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
+         * query expression and the ranking expression for embedding search.
+         *
+         * @see AppSearchSession#search
+         * @see SearchSpec.Builder#setRankingStrategy(String)
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+        @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+        public Builder addEmbeddingParameters(
                 @NonNull Collection<EmbeddingVector> searchEmbeddings) {
             Preconditions.checkNotNull(searchEmbeddings);
             resetIfBuilt();
-            mSearchEmbeddings.addAll(searchEmbeddings);
+            mEmbeddingParameters.addAll(searchEmbeddings);
             return this;
         }
 
@@ -1938,6 +2016,43 @@
         }
 
         /**
+         * Adds Strings to the list of String parameters that can be referenced in the query through
+         * the "getSearchStringParameter({index})" function.
+         *
+         * @see AppSearchSession#search
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        public Builder addSearchStringParameters(@NonNull String... searchStringParameters) {
+            Preconditions.checkNotNull(searchStringParameters);
+            resetIfBuilt();
+            return addSearchStringParameters(Arrays.asList(searchStringParameters));
+        }
+
+        /**
+         * Adds Strings to the list of String parameters that can be referenced in the query through
+         * the "getSearchStringParameter({index})" function.
+         *
+         * @see AppSearchSession#search
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        public Builder addSearchStringParameters(@NonNull List<String> searchStringParameters) {
+            Preconditions.checkNotNull(searchStringParameters);
+            resetIfBuilt();
+            mSearchStringParameters.addAll(searchStringParameters);
+            return this;
+        }
+
+        /**
          * Sets the NUMERIC_SEARCH feature as enabled/disabled according to the enabled parameter.
          *
          * @param enabled Enables the feature if true, otherwise disables it.
@@ -2045,11 +2160,16 @@
          * {@link AppSearchSession#search} for more details about the function.
          *
          * @param enabled Enables the feature if true, otherwise disables it
+         *
+         * @deprecated AppSearch no longer requires clients to explicitly enable embedding search
+         * because {@link #addEmbeddingParameters} is already guarded by {@link
+         * androidx.appsearch.app.Features#isFeatureSupported}.
          */
         @CanIgnoreReturnValue
         @RequiresFeature(
                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
                 name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+        @Deprecated
         @NonNull
         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
         public Builder setEmbeddingSearchEnabled(boolean enabled) {
@@ -2058,26 +2178,6 @@
         }
 
         /**
-         * Sets the LIST_FILTER_TOKENIZE_FUNCTION feature as enabled/disabled according to
-         * the enabled parameter.
-         *
-         * @param enabled Enables the feature if true, otherwise disables it
-         *
-         * <p>If disabled, disallows the use of the "tokenize" function. See
-         * {@link AppSearchSession#search} for more details about the function.
-         */
-        @CanIgnoreReturnValue
-        @RequiresFeature(
-                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
-                name = Features.LIST_FILTER_TOKENIZE_FUNCTION)
-        @NonNull
-        @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION)
-        public Builder setListFilterTokenizeFunctionEnabled(boolean enabled) {
-            modifyEnabledFeature(FeatureConstants.LIST_FILTER_TOKENIZE_FUNCTION, enabled);
-            return this;
-        }
-
-        /**
          * Constructs a new {@link SearchSpec} from the contents of this builder.
          *
          * @throws IllegalArgumentException if property weights are provided with a
@@ -2119,8 +2219,9 @@
                     mRankingStrategy, mOrder, mSnippetCount, mSnippetCountPerProperty,
                     mMaxSnippetSize, mProjectionTypePropertyMasks, mGroupingTypeFlags,
                     mGroupingLimit, mTypePropertyWeights, mJoinSpec, mAdvancedRankingExpression,
-                    new ArrayList<>(mEnabledFeatures), mSearchSourceLogTag, mSearchEmbeddings,
-                    mDefaultEmbeddingSearchMetricType, mInformationalRankingExpressions);
+                    new ArrayList<>(mEnabledFeatures), mSearchSourceLogTag, mEmbeddingParameters,
+                    mDefaultEmbeddingSearchMetricType, mInformationalRankingExpressions,
+                    mSearchStringParameters);
         }
 
         private void resetIfBuilt() {
@@ -2131,9 +2232,10 @@
                 mPackageNames = new ArrayList<>(mPackageNames);
                 mProjectionTypePropertyMasks = BundleUtil.deepCopy(mProjectionTypePropertyMasks);
                 mTypePropertyWeights = BundleUtil.deepCopy(mTypePropertyWeights);
-                mSearchEmbeddings = new ArrayList<>(mSearchEmbeddings);
+                mEmbeddingParameters = new ArrayList<>(mEmbeddingParameters);
                 mInformationalRankingExpressions = new ArrayList<>(
                         mInformationalRankingExpressions);
+                mSearchStringParameters = new ArrayList<>(mSearchStringParameters);
                 mBuilt = false;
             }
         }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
index f2db304..7a7bfbb 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
@@ -24,6 +24,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresFeature;
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.annotation.CanIgnoreReturnValue;
@@ -62,23 +63,37 @@
     @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
     @NonNull public static final Parcelable.Creator<SearchSuggestionSpec> CREATOR =
             new SearchSuggestionSpecCreator();
+
+    @NonNull
     @Field(id = 1, getter = "getFilterNamespaces")
     private final List<String> mFilterNamespaces;
+
+    @NonNull
     @Field(id = 2, getter = "getFilterSchemas")
     private final List<String> mFilterSchemas;
+
     // Maps are not supported by SafeParcelable fields, using Bundle instead. Here the key is
     // schema type and value is a list of target property paths in that schema to search over.
+    @NonNull
     @Field(id = 3)
     final Bundle mFilterProperties;
+
     // Maps are not supported by SafeParcelable fields, using Bundle instead. Here the key is
     // namespace and value is a list of target document ids in that namespace to search over.
+    @NonNull
     @Field(id = 4)
     final Bundle mFilterDocumentIds;
+
     @Field(id = 5, getter = "getRankingStrategy")
     private final int mRankingStrategy;
+
     @Field(id = 6, getter = "getMaximumResultCount")
     private final int mMaximumResultCount;
 
+    @NonNull
+    @Field(id = 7, getter = "getSearchStringParameters")
+    private final List<String> mSearchStringParameters;
+
     /** @exportToFramework:hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @Constructor
@@ -88,7 +103,8 @@
             @Param(id = 3) @NonNull Bundle filterProperties,
             @Param(id = 4) @NonNull Bundle filterDocumentIds,
             @Param(id = 5) @SuggestionRankingStrategy int rankingStrategy,
-            @Param(id = 6) int maximumResultCount) {
+            @Param(id = 6) int maximumResultCount,
+            @Param(id = 7) @Nullable List<String> searchStringParameters) {
         Preconditions.checkArgument(maximumResultCount >= 1,
                 "MaximumResultCount must be positive.");
         mFilterNamespaces = Preconditions.checkNotNull(filterNamespaces);
@@ -97,6 +113,10 @@
         mFilterDocumentIds = Preconditions.checkNotNull(filterDocumentIds);
         mRankingStrategy = rankingStrategy;
         mMaximumResultCount = maximumResultCount;
+        mSearchStringParameters =
+                (searchStringParameters != null)
+                        ? Collections.unmodifiableList(searchStringParameters)
+                        : Collections.emptyList();
     }
 
     /**
@@ -228,6 +248,18 @@
         return documentIdsMap;
     }
 
+    /**
+     * Returns the list of String parameters that can be referenced in the query through the
+     * "getSearchStringParameter({index})" function.
+     *
+     * @see AppSearchSession#search
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+    public List<String> getSearchStringParameters() {
+        return mSearchStringParameters;
+    }
+
     /** Builder for {@link SearchSuggestionSpec objects}. */
     public static final class Builder {
         private ArrayList<String> mNamespaces = new ArrayList<>();
@@ -237,6 +269,7 @@
         private final int mTotalResultCount;
         @SuggestionRankingStrategy private int mRankingStrategy =
                 SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT;
+        private List<String> mSearchStringParameters = new ArrayList<>();
         private boolean mBuilt = false;
 
         /**
@@ -557,6 +590,43 @@
             return this;
         }
 
+        /**
+         * Adds Strings to the list of String parameters that can be referenced in the query through
+         * the "getSearchStringParameter({index})" function.
+         *
+         * @see AppSearchSession#search
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        public Builder addSearchStringParameters(@NonNull String... searchStringParameters) {
+            Preconditions.checkNotNull(searchStringParameters);
+            resetIfBuilt();
+            return addSearchStringParameters(Arrays.asList(searchStringParameters));
+        }
+
+        /**
+         * Adds Strings to the list of String parameters that can be referenced in the query through
+         * the "getSearchStringParameter({index})" function.
+         *
+         * @see AppSearchSession#search
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
+        public Builder addSearchStringParameters(@NonNull List<String> searchStringParameters) {
+            Preconditions.checkNotNull(searchStringParameters);
+            resetIfBuilt();
+            mSearchStringParameters.addAll(searchStringParameters);
+            return this;
+        }
+
         /** Constructs a new {@link SearchSpec} from the contents of this builder. */
         @NonNull
         public SearchSuggestionSpec build() {
@@ -587,7 +657,8 @@
                     mTypePropertyFilters,
                     mDocumentIds,
                     mRankingStrategy,
-                    mTotalResultCount);
+                    mTotalResultCount,
+                    mSearchStringParameters);
         }
 
         private void resetIfBuilt() {
@@ -596,6 +667,7 @@
                 mSchemas = new ArrayList<>(mSchemas);
                 mTypePropertyFilters = BundleUtil.deepCopy(mTypePropertyFilters);
                 mDocumentIds = BundleUtil.deepCopy(mDocumentIds);
+                mSearchStringParameters = new ArrayList<>(mSearchStringParameters);
                 mBuilt = false;
             }
         }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
index c6cc193..eaf90b6 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
@@ -54,11 +54,6 @@
     public static final String FLAG_ENABLE_LIST_FILTER_HAS_PROPERTY_FUNCTION =
             FLAG_PREFIX + "enable_list_filter_has_property_function";
 
-    /** Enable the "tokenize" function in list filter query expressions. */
-    public static final String FLAG_ENABLE_LIST_FILTER_TOKENIZE_FUNCTION =
-            FLAG_PREFIX + "enable_list_filter_tokenize_function";
-
-
     /** Enable Schema Type Grouping related features. */
     public static final String FLAG_ENABLE_GROUPING_TYPE_PER_SCHEMA =
             FLAG_PREFIX + "enable_grouping_type_per_schema";
@@ -79,6 +74,14 @@
     public static final String FLAG_ENABLE_SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG =
             FLAG_PREFIX + "enable_search_spec_set_search_source_log_tag";
 
+    /**
+     * Enable {@link androidx.appsearch.app.SearchSpec.Builder#addSearchStringParameters} and
+     * {@link androidx.appsearch.app.SearchSuggestionSpec.Builder#addSearchStringParameters}
+     * methods.
+     */
+    public static final String FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS =
+            FLAG_PREFIX + "enable_search_spec_search_spec_strings";
+
     /** Enable addTakenActions API in PutDocumentsRequest. */
     public static final String FLAG_ENABLE_PUT_DOCUMENTS_REQUEST_ADD_TAKEN_ACTIONS =
             FLAG_PREFIX + "enable_put_documents_request_add_taken_actions";
@@ -234,8 +237,8 @@
         return true;
     }
 
-    /** Whether the "tokenize" function in list filter query expressions should be enabled. */
-    public static boolean enableListFilterTokenizeFunction() {
+    /** Whether the search parameter APIs should be enabled. */
+    public static boolean enableSearchSpecSearchStringParameters() {
         return true;
     }
 
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt
index 542f2ba..a1176a6 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/MetricCaptureTest.kt
@@ -44,9 +44,22 @@
     }
 
     @Test
+    fun cpuEventCounterCapture_outputName() {
+        CpuEventCounter().use {
+            assertEquals(
+                listOf("instructions", "cpuCycles"),
+                CpuEventCounterCapture(
+                        it,
+                        listOf(CpuEventCounter.Event.Instructions, CpuEventCounter.Event.CpuCycles)
+                    )
+                    .names
+            )
+        }
+    }
+
+    @Test
     fun cpuEventCounterCapture_multi() {
         try {
-
             // skip test if need root, or event fails to enable
             CpuEventCounter.forceEnable()?.let { errorMessage -> assumeTrue(errorMessage, false) }
 
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
index 8b29a81..725a125 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/CpuEventCounter.kt
@@ -20,6 +20,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import java.io.Closeable
+import java.util.Locale
 
 /**
  * Exposes CPU counters from perf_event_open based on libs/utils/src/Profiler.cpp from
@@ -87,6 +88,8 @@
 
         val flag: Int
             inline get() = 1 shl id
+
+        val outputName = name.replaceFirstChar { it.lowercase(Locale.US) }
     }
 
     /**
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
index 1432196..417495f 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
@@ -146,7 +146,7 @@
 internal class CpuEventCounterCapture(
     private val cpuEventCounter: CpuEventCounter,
     private val events: List<CpuEventCounter.Event>
-) : MetricCapture(events.map { it.name }) {
+) : MetricCapture(events.map { it.outputName }) {
     constructor(
         cpuEventCounter: CpuEventCounter,
         mask: Int
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt
index f4a8ef4..2a26600 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricNameUtils.kt
@@ -32,7 +32,7 @@
  * This functionality is a stopgap as we migrate to actually using JSON in CI.
  */
 internal fun String.toOutputMetricName() =
-    this.replaceFirstChar { it.lowercase(Locale.getDefault()) }
+    this.replaceFirstChar { it.lowercase(Locale.US) }
         .toSnakeCase()
         .replace(Regex("_ns$"), "_nanos")
         .replace(Regex("_ms$"), "_millis")
diff --git a/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt b/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt
index 8dc3df7..2de82d0 100644
--- a/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/LibraryVersionsServiceTest.kt
@@ -29,155 +29,136 @@
 
 @RunWith(JUnit4::class)
 class LibraryVersionsServiceTest {
-    @get:Rule
-    val tempDir = TemporaryFolder()
+    @get:Rule val tempDir = TemporaryFolder()
 
     @Test
     fun basic() {
-        val service = createLibraryVersionsService(
-            """
+        val service =
+            createLibraryVersionsService(
+                """
             [versions]
             V1 = "1.2.3"
             [groups]
             G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
             G2 = { group = "g.g2"}
-        """.trimIndent()
-        )
-        assertThat(
-            service.libraryGroups["G1"]
-        ).isEqualTo(
-            LibraryGroup(
-                group = "g.g1", atomicGroupVersion = Version("1.2.3")
+        """
+                    .trimIndent()
             )
-        )
-        assertThat(
-            service.libraryGroups["G2"]
-        ).isEqualTo(
-            LibraryGroup(
-                group = "g.g2", atomicGroupVersion = null
-            )
-        )
+        assertThat(service.libraryGroups["G1"])
+            .isEqualTo(LibraryGroup(group = "g.g1", atomicGroupVersion = Version("1.2.3")))
+        assertThat(service.libraryGroups["G2"])
+            .isEqualTo(LibraryGroup(group = "g.g2", atomicGroupVersion = null))
     }
 
     @Test
     fun invalidToml() {
-        val service = createLibraryVersionsService(
-            """
+        val service =
+            createLibraryVersionsService(
+                """
             [versions]
             V1 = "1.2.3"
             [groups]
             G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
             G1 = { group = "g.g1"}
-        """.trimIndent()
-        )
-        assertThrows<Exception> {
-            service.libraryGroups["G1"]
-        }.hasMessageThat().contains(
-            "libraryversions.toml:line 5, column 1: G1 previously defined at line 4, column 1"
-        )
+        """
+                    .trimIndent()
+            )
+        assertThrows<Exception> { service.libraryGroups["G1"] }
+            .hasMessageThat()
+            .contains(
+                "libraryversions.toml:line 5, column 1: G1 previously defined at line 4, column 1"
+            )
     }
 
     @Test
     fun missingVersionReference() {
-        val service = createLibraryVersionsService(
-            """
+        val service =
+            createLibraryVersionsService(
+                """
             [versions]
             V1 = "1.2.3"
             [groups]
             G1 = { group = "g.g1", atomicGroupVersion = "versions.doesNotExist" }
-        """.trimIndent()
-        )
-        val result = runCatching {
-            service.libraryGroups["G1"]
-        }
-        assertThat(
-            result.exceptionOrNull()
-        ).hasMessageThat().contains(
-            "Group entry g.g1 specifies doesNotExist, but such version doesn't exist"
-        )
+        """
+                    .trimIndent()
+            )
+        val result = runCatching { service.libraryGroups["G1"] }
+        assertThat(result.exceptionOrNull())
+            .hasMessageThat()
+            .contains("Group entry g.g1 specifies doesNotExist, but such version doesn't exist")
     }
 
     @Test
     fun malformedVersionReference() {
-        val service = createLibraryVersionsService(
-            """
+        val service =
+            createLibraryVersionsService(
+                """
             [versions]
             V1 = "1.2.3"
             [groups]
             G1 = { group = "g.g1", atomicGroupVersion = "v1" }
-        """.trimIndent()
-        )
-        val result = runCatching {
-            service.libraryGroups["G1"]
-        }
-        assertThat(
-            result.exceptionOrNull()
-        ).hasMessageThat().contains(
-            "Group entry atomicGroupVersion is expected to start with versions"
-        )
+        """
+                    .trimIndent()
+            )
+        val result = runCatching { service.libraryGroups["G1"] }
+        assertThat(result.exceptionOrNull())
+            .hasMessageThat()
+            .contains("Group entry atomicGroupVersion is expected to start with versions")
     }
 
     @Test
     fun overrideInclude() {
-        val service = createLibraryVersionsService(
-            """
+        val service =
+            createLibraryVersionsService(
+                """
             [versions]
             V1 = "1.2.3"
             [groups]
             G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", overrideInclude = [ ":otherGroup:subproject" ]}
             """
-        )
-        assertThat(
-            service.overrideLibraryGroupsByProjectPath.get(":otherGroup:subproject")
-        ).isEqualTo(
-            LibraryGroup(
-                group = "g.g1", atomicGroupVersion = Version("1.2.3")
             )
-        )
-        assertThat(
-            service.overrideLibraryGroupsByProjectPath.get(":normalGroup:subproject")
-        ).isEqualTo(null)
+        assertThat(service.overrideLibraryGroupsByProjectPath.get(":otherGroup:subproject"))
+            .isEqualTo(LibraryGroup(group = "g.g1", atomicGroupVersion = Version("1.2.3")))
+        assertThat(service.overrideLibraryGroupsByProjectPath.get(":normalGroup:subproject"))
+            .isEqualTo(null)
     }
 
     @Test
     fun duplicateGroupIdsWithoutOverrideInclude() {
-        val service = createLibraryVersionsService(
-            """
+        val service =
+            createLibraryVersionsService(
+                """
             [versions]
             V1 = "1.2.3"
             [groups]
             G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
             G2 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
             """
-        )
+            )
 
-        assertThrows<Exception> {
-            service.libraryGroupsByGroupId["g.g1"]
-        }.hasMessageThat().contains(
-            "Duplicate library group g.g1 defined in G2 does not set overrideInclude. " +
-                "Declarations beyond the first can only have an effect if they set overrideInclude"
-        )
+        assertThrows<Exception> { service.libraryGroupsByGroupId["g.g1"] }
+            .hasMessageThat()
+            .contains(
+                "Duplicate library group g.g1 defined in G2 does not set overrideInclude. " +
+                    "Declarations beyond the first can only have an effect if they set overrideInclude"
+            )
     }
 
     @Test
     fun duplicateGroupIdsWithOverrideInclude() {
-        val service = createLibraryVersionsService(
-            """
+        val service =
+            createLibraryVersionsService(
+                """
             [versions]
             V1 = "1.2.3"
             [groups]
             G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
             G2 = { group = "g.g1", atomicGroupVersion = "versions.V1", overrideInclude = ["sample"] }
             """
-        )
-
-        assertThat(
-            service.libraryGroupsByGroupId["g.g1"]
-        ).isEqualTo(
-            LibraryGroup(
-                group = "g.g1", atomicGroupVersion = Version("1.2.3")
             )
-        )
+
+        assertThat(service.libraryGroupsByGroupId["g.g1"])
+            .isEqualTo(LibraryGroup(group = "g.g1", atomicGroupVersion = Version("1.2.3")))
     }
 
     private fun runAndroidExtensionTest(
@@ -188,13 +169,9 @@
     ) {
         listOf(false, true).forEach { useKmpVersions ->
             val rootProjectDir = tempDir.newFolder()
-            val rootProject = ProjectBuilder.builder().withProjectDir(
-                rootProjectDir
-            ).build()
-            val subject = ProjectBuilder.builder()
-                .withParent(rootProject)
-                .withName(projectPath)
-                .build()
+            val rootProject = ProjectBuilder.builder().withProjectDir(rootProjectDir).build()
+            val subject =
+                ProjectBuilder.builder().withParent(rootProject).withName(projectPath).build()
             // create the service before extensions are created so that they'll use the test service
             // we've created.
             createLibraryVersionsService(
@@ -204,8 +181,8 @@
             // needed for AndroidXExtension initialization
             rootProject.setSupportRootFolder(rootProjectDir)
             // create androidx extensions
-            val extension = subject.extensions
-                .create<AndroidXExtension>(AndroidXImplPlugin.EXTENSION_NAME)
+            val extension =
+                subject.extensions.create<AndroidXExtension>(AndroidXImplPlugin.EXTENSION_NAME)
             if (useKmpVersions) {
                 validateWithKmp(extension)
             } else {
@@ -217,24 +194,16 @@
     private fun createLibraryVersionsService(
         tomlFileContents: String,
         tomlFileName: String = "libraryversions.toml",
-        composeCustomVersion: String? = null,
-        composeCustomGroup: String? = null,
         project: Project = ProjectBuilder.builder().withProjectDir(tempDir.newFolder()).build()
     ): LibraryVersionsService {
-        val serviceProvider = project.gradle.sharedServices.registerIfAbsent(
-            "libraryVersionsService", LibraryVersionsService::class.java
-        ) { spec ->
-            spec.parameters.tomlFileContents = project.provider {
-                tomlFileContents
+        val serviceProvider =
+            project.gradle.sharedServices.registerIfAbsent(
+                "libraryVersionsService",
+                LibraryVersionsService::class.java
+            ) { spec ->
+                spec.parameters.tomlFileContents = project.provider { tomlFileContents }
+                spec.parameters.tomlFileName = tomlFileName
             }
-            spec.parameters.tomlFileName = tomlFileName
-            spec.parameters.composeCustomVersion = project.provider {
-                composeCustomVersion
-            }
-            spec.parameters.composeCustomGroup = project.provider {
-                composeCustomGroup
-            }
-        }
         return serviceProvider.get()
     }
 }
diff --git a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
index bf7a859..9781b2e 100644
--- a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
+++ b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
@@ -45,7 +45,6 @@
     public String kotlinVersion;
     public String path;
     public String sha;
-    public String groupZipPath;
     public String projectZipPath;
     public Boolean groupIdRequiresSameVersion;
     public ArrayList<Dependency> dependencies;
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 93fa12e..57fc5cc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -17,7 +17,6 @@
 package androidx.build
 
 import androidx.build.dependencies.KOTLIN_NATIVE_VERSION
-import com.android.build.api.dsl.CommonExtension
 import com.android.build.api.variant.AndroidComponentsExtension
 import com.android.build.gradle.AppPlugin
 import com.android.build.gradle.LibraryPlugin
@@ -46,10 +45,6 @@
             when (plugin) {
                 is AppPlugin,
                 is LibraryPlugin -> {
-                    val commonExtension =
-                        project.extensions.findByType(CommonExtension::class.java)
-                            ?: throw Exception("Failed to find Android extension")
-                    commonExtension.defaultConfig.minSdk = 21
                     project.configureAndroidCommonOptions()
                 }
                 is KotlinBasePluginWrapper -> {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 6b043c7..8a6dbd1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -36,13 +36,12 @@
 
     @JvmField val AllLibraryGroups: List<LibraryGroup>
 
-    val libraryGroupsByGroupId: Map<String, LibraryGroup>
-    val overrideLibraryGroupsByProjectPath: Map<String, LibraryGroup>
+    private val libraryGroupsByGroupId: Map<String, LibraryGroup>
+    private val overrideLibraryGroupsByProjectPath: Map<String, LibraryGroup>
 
     val mavenGroup: LibraryGroup?
 
-    val listProjectsService: Provider<ListProjectsService>
-
+    private val listProjectsService: Provider<ListProjectsService>
     private val versionService: LibraryVersionsService
 
     val deviceTests = DeviceTests.register(project.extensions)
@@ -83,6 +82,24 @@
         kotlinTestTarget.set(kotlinTarget)
     }
 
+    /**
+     * Map of maven coordinates (e.g. "androidx.core:core") to a Gradle project path (e.g.
+     * ":core:core")
+     */
+    val mavenCoordinatesToProjectPathMap: Map<String, String> by lazy {
+        val newProjectMap: MutableMap<String, String> = mutableMapOf()
+        listProjectsService.get().allPossibleProjects.forEach {
+            val group =
+                overrideLibraryGroupsByProjectPath[it.gradlePath]
+                    ?: getLibraryGroupFromProjectPath(it.gradlePath, null)
+            if (group != null) {
+                newProjectMap["${group.group}:${substringAfterLastColon(it.gradlePath)}"] =
+                    it.gradlePath
+            }
+        }
+        newProjectMap
+    }
+
     var name: Property<String?> = project.objects.property(String::class.java)
 
     fun setName(newName: String) {
@@ -139,6 +156,11 @@
         return projectPath.substring(0, lastColonIndex)
     }
 
+    private fun substringAfterLastColon(projectPath: String): String {
+        val lastColonIndex = projectPath.lastIndexOf(":")
+        return projectPath.substring(lastColonIndex + 1)
+    }
+
     // gets the library group from the project path, including special cases
     private fun getLibraryGroupFromProjectPath(
         projectPath: String,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 31fcd62..eb2b57c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -66,7 +66,6 @@
 import java.io.File
 import java.time.Duration
 import java.util.Locale
-import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
@@ -127,7 +126,7 @@
 abstract class AndroidXImplPlugin
 @Inject
 constructor(private val componentFactory: SoftwareComponentFactory) : Plugin<Project> {
-    @get:javax.inject.Inject abstract val registry: BuildEventsListenerRegistry
+    @get:Inject abstract val registry: BuildEventsListenerRegistry
 
     override fun apply(project: Project) {
         if (project.isRoot)
@@ -226,6 +225,7 @@
 
         project.workaroundPrebuiltTakingPrecedenceOverProject()
         project.configureSamplesProject()
+        project.configureMaxDepVersions(androidXExtension)
     }
 
     private fun Project.registerProjectOrArtifact() {
@@ -625,8 +625,6 @@
         }
 
         project.configureJavaCompilationWarnings(androidXExtension)
-
-        project.addToProjectMap(androidXExtension)
     }
 
     private fun configureWithKotlinMultiplatformAndroidPlugin(
@@ -663,7 +661,6 @@
             }
         }
 
-        project.addToProjectMap(androidXExtension)
         project.afterEvaluate {
             project.addToBuildOnServer("assembleAndroidMain")
             project.addToBuildOnServer("lint")
@@ -899,8 +896,6 @@
 
         project.setUpCheckDocsTask(androidXExtension)
 
-        project.addToProjectMap(androidXExtension)
-
         project.buildOnServerDependsOnAssembleRelease()
         project.buildOnServerDependsOnLint()
     }
@@ -991,8 +986,6 @@
                 }
             }
         }
-
-        project.addToProjectMap(androidXExtension)
     }
 
     private fun Project.configureProjectStructureValidation(androidXExtension: AndroidXExtension) {
@@ -1404,9 +1397,6 @@
     }
 }
 
-private const val PROJECTS_MAP_KEY = "projects"
-private const val ACCESSED_PROJECTS_MAP_KEY = "accessedProjectsMap"
-
 /** Returns whether the configuration is used for testing. */
 private fun Configuration.isTest(): Boolean = name.lowercase().contains("test")
 
@@ -1425,30 +1415,6 @@
     }
 }
 
-private fun Project.addToProjectMap(androidXExtension: AndroidXExtension) {
-    // TODO(alanv): Move this out of afterEvaluate
-    afterEvaluate {
-        if (androidXExtension.shouldRelease()) {
-            val group = androidXExtension.mavenGroup?.group
-            if (group != null) {
-                val module = "$group:$name"
-
-                if (project.rootProject.extra.has(ACCESSED_PROJECTS_MAP_KEY)) {
-                    throw GradleException(
-                        "Attempted to add $project to project map after " +
-                            "the contents of the map were accessed"
-                    )
-                }
-                @Suppress("UNCHECKED_CAST")
-                val projectModules =
-                    project.rootProject.extra.get(PROJECTS_MAP_KEY)
-                        as ConcurrentHashMap<String, String>
-                projectModules[module] = path
-            }
-        }
-    }
-}
-
 val Project.androidExtension: AndroidComponentsExtension<*, *, *>
     get() =
         extensions.findByType<LibraryAndroidComponentsExtension>()
@@ -1464,12 +1430,6 @@
 val Project.androidXExtension: AndroidXExtension
     get() = extensions.getByType()
 
-@Suppress("UNCHECKED_CAST")
-fun Project.getProjectsMap(): ConcurrentHashMap<String, String> {
-    project.rootProject.extra.set(ACCESSED_PROJECTS_MAP_KEY, true)
-    return rootProject.extra.get(PROJECTS_MAP_KEY) as ConcurrentHashMap<String, String>
-}
-
 /**
  * Configures all non-Studio tasks in a project (see b/153193718 for background) to time out after
  * [TASK_TIMEOUT_MINUTES].
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 1d6f63f..ed28b5d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -35,7 +35,6 @@
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.artifacts.component.ModuleComponentSelector
 import org.gradle.api.file.RelativePath
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Zip
@@ -125,47 +124,6 @@
         AffectedModuleDetector.configure(gradle, this)
 
         registerOwnersServiceTasks()
-
-        // If useMaxDepVersions is set, iterate through all the project and substitute any androidx
-        // artifact dependency with the local tip of tree version of the library.
-        if (project.usingMaxDepVersions()) {
-            // This requires evaluating all sub-projects to create the module:project map
-            // and project dependencies.
-            allprojects { project2 ->
-                // evaluationDependsOnChildren isn't transitive so we must call it on each project
-                project2.evaluationDependsOnChildren()
-            }
-            val projectModules = getProjectsMap()
-            subprojects { subproject ->
-                // TODO(153485458) remove most of these exceptions
-                if (
-                    !subproject.name.contains("hilt") &&
-                        subproject.name != "docs-public" &&
-                        subproject.name != "docs-tip-of-tree" &&
-                        subproject.name != "camera-testapp-timing" &&
-                        subproject.name != "room-testapp"
-                ) {
-                    subproject.configurations.configureEach { configuration ->
-                        configuration.resolutionStrategy.dependencySubstitution.apply {
-                            all { dep ->
-                                val requested = dep.requested
-                                if (requested is ModuleComponentSelector) {
-                                    val module = requested.group + ":" + requested.module
-                                    if (
-                                        // todo(b/331800231): remove compiler exception.
-                                        requested.group != "androidx.compose.compiler" &&
-                                            projectModules.containsKey(module)
-                                    ) {
-                                        dep.useTarget(project(projectModules[module]!!))
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
         registerStudioTask()
 
         project.tasks.register("listTaskOutputs", ListTaskOutputsTask::class.java) { task ->
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
index 4def22c..a35febf 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
@@ -29,8 +29,6 @@
     interface Parameters : BuildServiceParameters {
         var tomlFileName: String
         var tomlFileContents: Provider<String>
-        var composeCustomVersion: Provider<String>
-        var composeCustomGroup: Provider<String>
     }
 
     private val parsedTomlFile: TomlParseResult by lazy {
@@ -147,7 +145,7 @@
 }
 
 // a LibraryGroupSpec knows how to associate a LibraryGroup with the appropriate projects
-data class LibraryGroupAssociation(
+private data class LibraryGroupAssociation(
     // the name of the variable to which it is assigned in the toml file
     val declarationName: String,
     // the group
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index ad554b8..c215eea 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -142,7 +142,7 @@
     val androidLibrariesSetProvider: Provider<Set<String>> = provider {
         val androidxAndroidProjects = mutableSetOf<String>()
         // Check every project is the project map to see if they are an Android Library
-        val projectModules = project.getProjectsMap()
+        val projectModules = extension.mavenCoordinatesToProjectPathMap
         for ((mavenCoordinates, projectPath) in projectModules) {
             project.findProject(projectPath)?.plugins?.let { plugins ->
                 if (plugins.hasPlugin(LibraryPlugin::class.java)) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MaxDepVersions.kt b/buildSrc/private/src/main/kotlin/androidx/build/MaxDepVersions.kt
new file mode 100644
index 0000000..81a9cfc
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MaxDepVersions.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+
+/**
+ * If useMaxDepVersions is set, iterate through all the dependencies and substitute any androidx
+ * artifact dependency with the local tip of tree version of the library.
+ */
+internal fun Project.configureMaxDepVersions(extension: AndroidXExtension) {
+    if (!usingMaxDepVersions()) return
+    val projectModules = extension.mavenCoordinatesToProjectPathMap
+    configurations.configureEach { configuration ->
+        configuration.resolutionStrategy.dependencySubstitution.apply {
+            all { dep ->
+                val requested = dep.requested
+                if (requested is ModuleComponentSelector) {
+                    val module = requested.group + ":" + requested.module
+                    if (
+                        // todo(b/331800231): remove compiler exception.
+                        requested.group != "androidx.compose.compiler" &&
+                            projectModules.containsKey(module)
+                    ) {
+                        dep.useTarget(project(projectModules[module]!!))
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
index 5a347ab..791d72d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
@@ -18,7 +18,6 @@
 import androidx.build.uptodatedness.cacheEvenIfNoOutputs
 import java.io.File
 import java.io.FileNotFoundException
-import java.util.Locale
 import org.gradle.api.Action
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
@@ -121,14 +120,11 @@
 object Release {
     @Suppress("MemberVisibilityCanBePrivate")
     const val PROJECT_ARCHIVE_ZIP_TASK_NAME = "createProjectZip"
-    const val DIFF_TASK_PREFIX = "createDiffArchive"
-    const val FULL_ARCHIVE_TASK_NAME = "createArchive"
-    const val ALL_ARCHIVES_TASK_NAME = "createAllArchives"
+    private const val FULL_ARCHIVE_TASK_NAME = "createArchive"
+    private const val ALL_ARCHIVES_TASK_NAME = "createAllArchives"
     const val DEFAULT_PUBLISH_CONFIG = "release"
-    const val GROUP_ZIPS_FOLDER = "per-group-zips"
     const val PROJECT_ZIPS_FOLDER = "per-project-zips"
-    const val GROUP_ZIP_PREFIX = "gmaven"
-    const val GLOBAL_ZIP_PREFIX = "top-of-tree-m2repository"
+    private const val GLOBAL_ZIP_PREFIX = "top-of-tree-m2repository"
 
     // lazily created config action params so that we don't keep re-creating them
     private var configActionParams: GMavenZipTask.ConfigAction.Params? = null
@@ -158,12 +154,6 @@
             )
             return
         }
-
-        val mavenGroup =
-            androidXExtension.mavenGroup?.group
-                ?: throw IllegalArgumentException(
-                    "Cannot register a project to release if it does not have a mavenGroup set up"
-                )
         if (!androidXExtension.isVersionSet()) {
             throw IllegalArgumentException(
                 "Cannot register a project to release if it does not have a mavenVersion set up"
@@ -172,12 +162,7 @@
         val version = project.version
 
         val projectZipTask = getProjectZipTask(project)
-        val zipTasks =
-            listOf(
-                projectZipTask,
-                getGroupReleaseZipTask(project, mavenGroup),
-                getGlobalFullZipTask(project)
-            )
+        val zipTasks = listOf(projectZipTask, getGlobalFullZipTask(project))
 
         val artifacts = androidXExtension.publishedArtifacts
         val publishTask = project.tasks.named("publish")
@@ -295,28 +280,6 @@
         )
     }
 
-    /** Creates and returns the zip task that includes artifacts only in the given maven group. */
-    private fun getGroupReleaseZipTask(
-        project: Project,
-        group: String
-    ): TaskProvider<GMavenZipTask> {
-        return project.rootProject.maybeRegister(
-            name = "${DIFF_TASK_PREFIX}For${groupToTaskNameSuffix(group)}",
-            onConfigure = { task: GMavenZipTask ->
-                GMavenZipTask.ConfigAction(
-                        getParams(
-                            project = project,
-                            distDir = File(project.getDistributionDirectory(), GROUP_ZIPS_FOLDER),
-                            fileNamePrefix = GROUP_ZIP_PREFIX,
-                            group = group
-                        )
-                    )
-                    .execute(task)
-            },
-            onRegister = { taskProvider -> project.addToAnchorTask(taskProvider) }
-        )
-    }
-
     private fun getProjectZipTask(project: Project): TaskProvider<GMavenZipTask> {
         val taskProvider =
             project.tasks.register(PROJECT_ARCHIVE_ZIP_TASK_NAME, GMavenZipTask::class.java) {
@@ -403,7 +366,7 @@
         }
     }
 
-    fun verifyFiles() {
+    private fun verifyFiles() {
         val missingFiles = mutableListOf<String>()
         val emptyDirs = mutableListOf<String>()
         filesToVerify.forEach { file ->
@@ -478,15 +441,6 @@
         return declaredTargets.toList()
     }
 
-/** Converts the maven group into a readable task name. */
-private fun groupToTaskNameSuffix(group: String): String {
-    return group.split('.').joinToString("") {
-        it.replaceFirstChar { char ->
-            if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else char.toString()
-        }
-    }
-}
-
 private fun Project.projectZipPrefix(): String {
     return "${project.group}-${project.name}"
 }
@@ -509,17 +463,3 @@
         getZipName(projectZipPrefix(), "") +
         "-${project.version}.zip"
 }
-
-fun Project.getGroupZipPath(): String {
-    return Release.GROUP_ZIPS_FOLDER +
-        "/" +
-        getZipName(Release.GROUP_ZIP_PREFIX, project.group.toString()) +
-        ".zip"
-}
-
-fun Project.getGlobalZipFile(): File {
-    return File(
-        project.getDistributionDirectory(),
-        getZipName(Release.GLOBAL_ZIP_PREFIX, "") + ".zip"
-    )
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
index efe9eee..07f4db2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
@@ -21,7 +21,6 @@
 import androidx.build.LibraryGroup
 import androidx.build.docs.CheckTipOfTreeDocsTask.Companion.requiresDocs
 import androidx.build.getBuildInfoDirectory
-import androidx.build.getGroupZipPath
 import androidx.build.getProjectZipPath
 import androidx.build.getSupportRootFolder
 import androidx.build.gitclient.getHeadShaProvider
@@ -92,8 +91,6 @@
 
     @get:Input abstract val groupIdRequiresSameVersion: Property<Boolean>
 
-    @get:Input abstract val groupZipPath: Property<String>
-
     @get:Input abstract val projectZipPath: Property<String>
 
     @get:[Input Optional]
@@ -139,7 +136,6 @@
         libraryBuildInfoFile.path = projectDir.get()
         libraryBuildInfoFile.sha = commit.get()
         libraryBuildInfoFile.groupIdRequiresSameVersion = groupIdRequiresSameVersion.get()
-        libraryBuildInfoFile.groupZipPath = groupZipPath.get()
         libraryBuildInfoFile.projectZipPath = projectZipPath.get()
         libraryBuildInfoFile.kotlinVersion = kotlinVersion.orNull
         libraryBuildInfoFile.checks = ArrayList()
@@ -196,7 +192,6 @@
                 )
                 task.commit.set(shaProvider)
                 task.groupIdRequiresSameVersion.set(mavenGroup?.requireSameVersion ?: false)
-                task.groupZipPath.set(project.getGroupZipPath())
                 task.projectZipPath.set(project.getProjectZipPath())
 
                 // Note:
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
index 59be856b..8106d92 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/AndroidXConfig.kt
@@ -25,7 +25,7 @@
 
 /** AndroidX configuration backed by Gradle properties. */
 abstract class AndroidConfigImpl(private val project: Project) : AndroidConfig {
-    override val buildToolsVersion: String = "35.0.0-rc1"
+    override val buildToolsVersion: String = "35.0.0"
 
     override val compileSdk: Int by lazy {
         val sdkString = project.extraPropertyOrNull(COMPILE_SDK)?.toString()
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
index 6bd2e52..6fe08bc 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
@@ -18,6 +18,7 @@
 
 import android.hardware.camera2.CameraDevice
 import android.media.MediaCodec
+import android.util.Range
 import androidx.annotation.VisibleForTesting
 import androidx.camera.camera2.pipe.OutputStream
 import androidx.camera.camera2.pipe.core.Log
@@ -32,6 +33,7 @@
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.StreamSpec
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.streamsharing.StreamSharing
 import kotlinx.coroutines.CoroutineScope
@@ -118,6 +120,15 @@
         }
     }
 
+    fun getExpectedFrameRateRange(): Range<Int>? {
+        return if (
+            isSessionConfigValid() &&
+                sessionConfig.expectedFrameRateRange != StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+        )
+            sessionConfig.expectedFrameRateRange
+        else null
+    }
+
     /**
      * Populates the mapping between surfaces of a capture session and the Stream Use Case of their
      * associated stream.
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index b832f7b..0b760d2 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -874,6 +874,8 @@
                 }
             }
 
+            // Set fps range to capture request
+            val targetFpsRange = sessionConfigAdapter.getExpectedFrameRateRange()
             val defaultParameters =
                 buildMap<Any, Any?> {
                     if (isExtensions) {
@@ -884,7 +886,13 @@
                         CameraPipeKeys.camera2CaptureRequestTag,
                         "android.hardware.camera2.CaptureRequest.setTag.CX"
                     )
+                    targetFpsRange?.let {
+                        set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetFpsRange)
+                    }
                 }
+            targetFpsRange?.let {
+                sessionParameters[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] = targetFpsRange
+            }
 
             // TODO: b/327517884 - Add a quirk to not abort captures on stop for certain OEMs during
             //   extension sessions.
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
index a11268a..7bce113 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
@@ -18,7 +18,9 @@
 
 import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
 import android.os.Build
+import android.util.Range
 import android.view.Surface
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
@@ -121,6 +123,34 @@
     }
 
     @Test
+    fun getExpectedFrameRateRange() {
+        // Arrange
+        val testDeferrableSurface = createTestDeferrableSurface()
+
+        // Create an invalid SessionConfig which doesn't set the template
+        val fakeTestUseCase = createFakeTestUseCase {
+            it.setupSessionConfig(
+                SessionConfig.Builder().also { sessionConfigBuilder ->
+                    sessionConfigBuilder.addSurface(testDeferrableSurface)
+                    sessionConfigBuilder.setTemplateType(TEMPLATE_PREVIEW)
+                    sessionConfigBuilder.setExpectedFrameRateRange(Range(15, 24))
+                }
+            )
+        }
+
+        // Act
+        val sessionConfigAdapter = SessionConfigAdapter(useCases = listOf(fakeTestUseCase))
+
+        // Assert
+        assertThat(sessionConfigAdapter.isSessionConfigValid()).isTrue()
+        assertThat(sessionConfigAdapter.getValidSessionConfigOrNull()).isNotNull()
+        assertThat(sessionConfigAdapter.getExpectedFrameRateRange()).isEqualTo(Range(15, 24))
+
+        // Clean up
+        testDeferrableSurface.close()
+    }
+
+    @Test
     fun populateSurfaceToStreamUseCaseMappingEmptyUseCase() {
         val mapping = sessionConfigAdapter.getSurfaceToStreamUseCaseMapping(listOf(), listOf())
         TestCase.assertTrue(mapping.isEmpty())
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index 89ed085..0e24668 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -19,11 +19,14 @@
 import android.content.Context
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
 import android.hardware.camera2.CameraDevice.TEMPLATE_RECORD
 import android.hardware.camera2.CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE
 import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
 import android.hardware.camera2.params.SessionConfiguration.SESSION_HIGH_SPEED
 import android.os.Build
+import android.util.Range
 import android.util.Size
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraGraph.OperatingMode.Companion.HIGH_SPEED
@@ -523,6 +526,40 @@
     }
 
     @Test
+    fun createCameraGraphConfig_setTargetFpsRange() = runTest {
+        // Arrange
+        initializeUseCaseThreads(this)
+        val useCaseManager = createUseCaseManager()
+        val fakeUseCase =
+            FakeUseCase().apply {
+                updateSessionConfigForTesting(
+                    SessionConfig.Builder()
+                        .setTemplateType(TEMPLATE_PREVIEW)
+                        .setExpectedFrameRateRange(Range(15, 24))
+                        .build()
+                )
+            }
+        val sessionConfigAdapter = SessionConfigAdapter(setOf(fakeUseCase))
+        val streamConfigMap = mutableMapOf<CameraStream.Config, DeferrableSurface>()
+
+        // Act
+        val graphConfig =
+            useCaseManager.createCameraGraphConfig(
+                sessionConfigAdapter,
+                streamConfigMap,
+            )
+
+        // Assert
+        assertThat(graphConfig.sessionTemplate).isEqualTo(RequestTemplate(TEMPLATE_PREVIEW))
+        assertThat(graphConfig.sessionParameters).containsKey(CONTROL_AE_TARGET_FPS_RANGE)
+        assertThat(graphConfig.sessionParameters[CONTROL_AE_TARGET_FPS_RANGE])
+            .isEqualTo(Range(15, 24))
+        assertThat(graphConfig.defaultParameters).containsKey(CONTROL_AE_TARGET_FPS_RANGE)
+        assertThat(graphConfig.defaultParameters[CONTROL_AE_TARGET_FPS_RANGE])
+            .isEqualTo(Range(15, 24))
+    }
+
+    @Test
     fun overrideTemplateParams() = runTest {
         // Arrange
         initializeUseCaseThreads(this)
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt
index 8055baf..7ecd100 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/StreamUseCaseTest.kt
@@ -26,10 +26,10 @@
 import androidx.camera.camera2.pipe.integration.adapter.SupportedSurfaceCombination
 import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.CompositionSettings
 import androidx.camera.core.DynamicRange
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCapture.CaptureMode
-import androidx.camera.core.LayoutSettings
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.AttachedSurfaceInfo
 import androidx.camera.core.impl.CameraMode
@@ -644,8 +644,8 @@
             StreamSharing(
                 FakeCamera(),
                 null,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 children,
                 useCaseConfigFactory
             )
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
index 76d5559..e94c4cb 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
@@ -71,8 +71,8 @@
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraControl;
 import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.Preview;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.impl.CameraCaptureCallback;
@@ -985,8 +985,8 @@
 
         StreamSharing streamSharing =
                 new StreamSharing(mCamera2CameraImpl, null,
-                        LayoutSettings.DEFAULT,
-                        LayoutSettings.DEFAULT,
+                        CompositionSettings.DEFAULT,
+                        CompositionSettings.DEFAULT,
                         children, useCaseConfigFactory);
 
         FakeUseCaseConfig.Builder configBuilder =
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
index 70decc2..08ccc13 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
@@ -237,6 +237,8 @@
         applyTemplateParamsOverrideWorkaround(builder, captureConfig.getTemplateType(),
                 templateParamsOverride);
 
+        applyAeFpsRange(captureConfig, builder);
+
         applyImplementationOptionToCaptureBuilder(builder,
                 captureConfig.getImplementationOptions());
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java
index 6129f59..03633e9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/FocusMeteringControl.java
@@ -458,6 +458,8 @@
      */
     @ExecutedBy("mExecutor")
     void triggerAePrecapture(@Nullable Completer<Void> completer) {
+        Logger.d(TAG, "triggerAePrecapture");
+
         if (!mIsActive) {
             if (completer != null) {
                 completer.setException(
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
index 301336d..9984444 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
@@ -40,9 +40,9 @@
 import androidx.camera.camera2.impl.Camera2ImplConfig;
 import androidx.camera.camera2.internal.SupportedSurfaceCombination.FeatureSettings;
 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.impl.AttachedSurfaceInfo;
 import androidx.camera.core.impl.CameraMode;
@@ -483,7 +483,7 @@
         children.add(new FakeUseCase(new FakeUseCaseConfig.Builder().getUseCaseConfig(),
                 UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE));
         StreamSharing streamSharing = new StreamSharing(new FakeCamera(), null,
-                LayoutSettings.DEFAULT, LayoutSettings.DEFAULT, children,
+                CompositionSettings.DEFAULT, CompositionSettings.DEFAULT, children,
                 useCaseConfigFactory);
         Map<Integer, AttachedSurfaceInfo> surfaceConfigAttachedSurfaceInfoMap =
                 new HashMap<>();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CompositionSettings.java b/camera/camera-core/src/main/java/androidx/camera/core/CompositionSettings.java
new file mode 100644
index 0000000..0151734
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CompositionSettings.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import androidx.annotation.FloatRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Pair;
+
+/**
+ * Composition settings for dual concurrent camera. It includes alpha value for blending,
+ * offset in x, y coordinates, scale of width and height. The offset, width and height are specified
+ * in normalized device coordinates.
+ *
+ * @see <a href="https://learnopengl.com/Getting-started/Coordinate-Systems">Normalized Device Coordinates</a>
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class CompositionSettings {
+
+    public static final CompositionSettings DEFAULT = new Builder()
+            .setAlpha(1.0f)
+            .setOffset(0.0f, 0.0f)
+            .setScale(1.0f, 1.0f)
+            .build();
+
+    private final float mAlpha;
+    private final Pair<Float, Float> mOffset;
+    private final Pair<Float, Float> mScale;
+
+    private CompositionSettings(
+            float alpha,
+            Pair<Float, Float> offset,
+            Pair<Float, Float> scale) {
+        mAlpha = alpha;
+        mOffset = offset;
+        mScale = scale;
+    }
+
+    /**
+     * Gets the alpha.
+     *
+     * @return alpha value.
+     */
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    /**
+     * Gets the offset.
+     *
+     * @return offset value.
+     */
+    @NonNull
+    public Pair<Float, Float> getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Gets the scale. Negative value means mirroring in X or Y direction.
+     *
+     * @return scale value.
+     */
+    @NonNull
+    public Pair<Float, Float> getScale() {
+        return mScale;
+    }
+
+    /** A builder for {@link CompositionSettings} instances. */
+    public static final class Builder {
+        private float mAlpha;
+        private Pair<Float, Float> mOffset;
+        private Pair<Float, Float> mScale;
+
+        /** Creates a new {@link Builder}. */
+        public Builder() {
+            mAlpha = 1.0f;
+            mOffset = Pair.create(0.0f, 0.0f);
+            mScale = Pair.create(1.0f, 1.0f);
+        }
+
+        /**
+         * Sets the alpha.
+         *
+         * @param alpha alpha value.
+         * @return Builder instance.
+         */
+        @NonNull
+        public Builder setAlpha(@FloatRange(from = 0, to = 1) float alpha) {
+            mAlpha = alpha;
+            return this;
+        }
+
+        /**
+         * Sets the offset.
+         *
+         * @param offsetX offset X value.
+         * @param offsetY offset Y value.
+         * @return Builder instance.
+         */
+        @NonNull
+        public Builder setOffset(
+                @FloatRange(from = -1, to = 1) float offsetX,
+                @FloatRange(from = -1, to = 1) float offsetY) {
+            mOffset = Pair.create(offsetX, offsetY);
+            return this;
+        }
+
+        /**
+         * Sets the scale.
+         *
+         * @param scaleX scale X value.
+         * @param scaleY scale Y value.
+         * @return Builder instance.
+         */
+        @NonNull
+        public Builder setScale(
+                @FloatRange(from = -1, to = 1) float scaleX,
+                @FloatRange(from = -1, to = 1) float scaleY) {
+            mScale = Pair.create(scaleX, scaleY);
+            return this;
+        }
+
+        /**
+         * Builds the {@link CompositionSettings}.
+         *
+         * @return {@link CompositionSettings}.
+         */
+        @NonNull
+        public CompositionSettings build() {
+            return new CompositionSettings(
+                    mAlpha,
+                    mOffset,
+                    mScale);
+        }
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java b/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java
index 6507aff..e8d2997 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ConcurrentCamera.java
@@ -79,7 +79,7 @@
         @NonNull
         private UseCaseGroup mUseCaseGroup;
         @NonNull
-        private LayoutSettings mLayoutSettings;
+        private CompositionSettings mCompositionSettings;
 
         /**
          * Constructor of a {@link SingleCameraConfig} for concurrent cameras.
@@ -92,7 +92,7 @@
                 @NonNull CameraSelector cameraSelector,
                 @NonNull UseCaseGroup useCaseGroup,
                 @NonNull LifecycleOwner lifecycleOwner) {
-            this(cameraSelector, useCaseGroup, LayoutSettings.DEFAULT, lifecycleOwner);
+            this(cameraSelector, useCaseGroup, CompositionSettings.DEFAULT, lifecycleOwner);
         }
 
         /**
@@ -100,18 +100,18 @@
          *
          * @param cameraSelector {@link CameraSelector}.
          * @param useCaseGroup {@link UseCaseGroup}.
-         * @param layoutSettings {@link LayoutSettings}.
+         * @param compositionSettings {@link CompositionSettings}.
          * @param lifecycleOwner {@link LifecycleOwner}.
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public SingleCameraConfig(
                 @NonNull CameraSelector cameraSelector,
                 @NonNull UseCaseGroup useCaseGroup,
-                @NonNull LayoutSettings layoutSettings,
+                @NonNull CompositionSettings compositionSettings,
                 @NonNull LifecycleOwner lifecycleOwner) {
             this.mCameraSelector = cameraSelector;
             this.mUseCaseGroup = useCaseGroup;
-            this.mLayoutSettings = layoutSettings;
+            this.mCompositionSettings = compositionSettings;
             this.mLifecycleOwner = lifecycleOwner;
         }
 
@@ -143,13 +143,13 @@
         }
 
         /**
-         * Returns {@link LayoutSettings}.
-         * @return {@link LayoutSettings} instance.
+         * Returns {@link CompositionSettings}.
+         * @return {@link CompositionSettings} instance.
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @NonNull
-        public LayoutSettings getLayoutSettings() {
-            return mLayoutSettings;
+        public CompositionSettings getCompositionSettings() {
+            return mCompositionSettings;
         }
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/LayoutSettings.java b/camera/camera-core/src/main/java/androidx/camera/core/LayoutSettings.java
deleted file mode 100644
index 68e098c..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/LayoutSettings.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.core;
-
-import androidx.annotation.FloatRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-/**
- * Layout settings for dual concurrent camera. It includes alpha value for blending,
- * offset in x, y coordinates, width and height. The offset, width and height are specified
- * in normalized device coordinates.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class LayoutSettings {
-
-    public static final LayoutSettings DEFAULT = new Builder()
-            .setAlpha(1.0f)
-            .setOffsetX(0.0f)
-            .setOffsetY(0.0f)
-            .setWidth(1.0f)
-            .setHeight(1.0f)
-            .build();
-
-    private final float mAlpha;
-    private final float mOffsetX;
-    private final float mOffsetY;
-    private final float mWidth;
-    private final float mHeight;
-
-    private LayoutSettings(
-            float alpha,
-            float offsetX,
-            float offsetY,
-            float width,
-            float height) {
-        this.mAlpha = alpha;
-        this.mOffsetX = offsetX;
-        this.mOffsetY = offsetY;
-        this.mWidth = width;
-        this.mHeight = height;
-    }
-
-    /**
-     * Gets the alpha.
-     *
-     * @return alpha value.
-     */
-    public float getAlpha() {
-        return mAlpha;
-    }
-
-    /**
-     * Gets the offset X.
-     *
-     * @return offset X value.
-     */
-    public float getOffsetX() {
-        return mOffsetX;
-    }
-
-    /**
-     * Gets the offset Y.
-     *
-     * @return offset Y value.
-     */
-    public float getOffsetY() {
-        return mOffsetY;
-    }
-
-    /**
-     * Gets the width.
-     *
-     * @return width.
-     */
-    public float getWidth() {
-        return mWidth;
-    }
-
-    /**
-     * Gets the height.
-     *
-     * @return height.
-     */
-    public float getHeight() {
-        return mHeight;
-    }
-
-    /** A builder for {@link LayoutSettings} instances. */
-    public static final class Builder {
-        private float mAlpha;
-        private float mOffsetX;
-        private float mOffsetY;
-        private float mWidth;
-        private float mHeight;
-
-        /** Creates a new {@link Builder}. */
-        public Builder() {
-            mAlpha = 1.0f;
-            mOffsetX = 0.0f;
-            mOffsetY = 0.0f;
-            mWidth = 0.0f;
-            mHeight = 0.0f;
-        }
-
-        /**
-         * Sets the alpha.
-         *
-         * @param alpha alpha value.
-         * @return Builder instance.
-         */
-        @NonNull
-        public Builder setAlpha(@FloatRange(from = 0, to = 1) float alpha) {
-            this.mAlpha = alpha;
-            return this;
-        }
-
-        /**
-         * Sets the offset X.
-         *
-         * @param offsetX offset X value.
-         * @return Builder instance.
-         */
-        @NonNull
-        public Builder setOffsetX(@FloatRange(from = -1, to = 1) float offsetX) {
-            this.mOffsetX = offsetX;
-            return this;
-        }
-
-        /**
-         * Sets the offset Y.
-         *
-         * @param offsetY offset Y value.
-         * @return Builder instance.
-         */
-        @NonNull
-        public Builder setOffsetY(@FloatRange(from = -1, to = 1) float offsetY) {
-            this.mOffsetY = offsetY;
-            return this;
-        }
-
-        /**
-         * Sets the width.
-         *
-         * @param width width value.
-         * @return Builder instance.
-         */
-        @NonNull
-        public Builder setWidth(@FloatRange(from = -1, to = 1) float width) {
-            this.mWidth = width;
-            return this;
-        }
-
-        /**
-         * Sets the height.
-         *
-         * @param height height value.
-         * @return Builder instance.
-         */
-        @NonNull
-        public Builder setHeight(@FloatRange(from = -1, to = 1) float height) {
-            this.mHeight = height;
-            return this;
-        }
-
-        /**
-         * Builds the {@link LayoutSettings}.
-         *
-         * @return {@link LayoutSettings}.
-         */
-        @NonNull
-        public LayoutSettings build() {
-            return new LayoutSettings(
-                    mAlpha,
-                    mOffsetX,
-                    mOffsetY,
-                    mWidth,
-                    mHeight);
-        }
-    }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index b233749..1725729 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -55,9 +55,9 @@
 import androidx.camera.core.CameraEffect;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.Logger;
 import androidx.camera.core.Preview;
 import androidx.camera.core.UseCase;
@@ -173,9 +173,9 @@
     private final RestrictedCameraInfo mAdapterSecondaryCameraInfo;
 
     @NonNull
-    private final LayoutSettings mLayoutSettings;
+    private final CompositionSettings mCompositionSettings;
     @NonNull
-    private final LayoutSettings mSecondaryLayoutSettings;
+    private final CompositionSettings mSecondaryCompositionSettings;
 
     /**
      * Create a new {@link CameraUseCaseAdapter} instance.
@@ -196,8 +196,8 @@
                 new RestrictedCameraInfo(camera.getCameraInfoInternal(),
                         CameraConfigs.defaultConfig()),
                 null,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 cameraCoordinator,
                 cameraDeviceSurfaceManager,
                 useCaseConfigFactory);
@@ -212,10 +212,10 @@
      *                                   information to configure the {@link CameraInternal} when
      *                                   attaching the uses cases of this adapter to the camera.
      * @param secondaryRestrictedCameraInfo The {@link RestrictedCameraInfo} of secondary camera.
-     * @param layoutSettings             The layout settings that will be used to configure the
+     * @param compositionSettings        The composition settings that will be used to configure the
      *                                   camera.
-     * @param secondaryLayoutSettings    The layout settings that will be used to configure the
-     *                                   secondary camera.
+     * @param secondaryCompositionSettings  The composition settings that will be used to configure
+     *                                      the secondary camera.
      * @param cameraCoordinator          Camera coordinator that exposes concurrent camera mode.
      * @param cameraDeviceSurfaceManager A class that checks for whether a specific camera
      *                                   can support the set of Surface with set resolutions.
@@ -227,15 +227,15 @@
             @Nullable CameraInternal secondaryCamera,
             @NonNull RestrictedCameraInfo restrictedCameraInfo,
             @Nullable RestrictedCameraInfo secondaryRestrictedCameraInfo,
-            @NonNull LayoutSettings layoutSettings,
-            @NonNull LayoutSettings secondaryLayoutSettings,
+            @NonNull CompositionSettings compositionSettings,
+            @NonNull CompositionSettings secondaryCompositionSettings,
             @NonNull CameraCoordinator cameraCoordinator,
             @NonNull CameraDeviceSurfaceManager cameraDeviceSurfaceManager,
             @NonNull UseCaseConfigFactory useCaseConfigFactory) {
         mCameraInternal = camera;
         mSecondaryCameraInternal = secondaryCamera;
-        mLayoutSettings = layoutSettings;
-        mSecondaryLayoutSettings = secondaryLayoutSettings;
+        mCompositionSettings = compositionSettings;
+        mSecondaryCompositionSettings = secondaryCompositionSettings;
         mCameraCoordinator = cameraCoordinator;
         mCameraDeviceSurfaceManager = cameraDeviceSurfaceManager;
         mUseCaseConfigFactory = useCaseConfigFactory;
@@ -622,8 +622,8 @@
 
             return new StreamSharing(mCameraInternal,
                     mSecondaryCameraInternal,
-                    mLayoutSettings,
-                    mSecondaryLayoutSettings,
+                    mCompositionSettings,
+                    mSecondaryCompositionSettings,
                     newChildren,
                     mUseCaseConfigFactory);
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java
index fc7fe62..cb5fdfc 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualOpenGlRenderer.java
@@ -34,8 +34,8 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.DynamicRange;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.Logger;
 import androidx.camera.core.SurfaceOutput;
 import androidx.camera.core.processing.OpenGlRenderer;
@@ -61,15 +61,15 @@
     private int mSecondaryExternalTextureId = -1;
 
     @NonNull
-    private final LayoutSettings mPrimaryLayoutSettings;
+    private final CompositionSettings mPrimaryCompositionSettings;
     @NonNull
-    private final LayoutSettings mSecondaryLayoutSettings;
+    private final CompositionSettings mSecondaryCompositionSettings;
 
     public DualOpenGlRenderer(
-            @NonNull LayoutSettings primaryLayoutSettings,
-            @NonNull LayoutSettings secondaryLayoutSettings) {
-        mPrimaryLayoutSettings = primaryLayoutSettings;
-        mSecondaryLayoutSettings = secondaryLayoutSettings;
+            @NonNull CompositionSettings primaryCompositionSettings,
+            @NonNull CompositionSettings secondaryCompositionSettings) {
+        mPrimaryCompositionSettings = primaryCompositionSettings;
+        mSecondaryCompositionSettings = secondaryCompositionSettings;
     }
 
     @NonNull
@@ -138,11 +138,11 @@
         GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
         // Primary Camera
         renderInternal(outputSurface, surfaceOutput, primarySurfaceTexture,
-                mPrimaryLayoutSettings, mPrimaryExternalTextureId, true);
+                mPrimaryCompositionSettings, mPrimaryExternalTextureId, true);
         // Secondary Camera
         // Only use primary camera info for output surface
         renderInternal(outputSurface, surfaceOutput, secondarySurfaceTexture,
-                mSecondaryLayoutSettings, mSecondaryExternalTextureId, true);
+                mSecondaryCompositionSettings, mSecondaryExternalTextureId, true);
 
         EGLExt.eglPresentationTimeANDROID(mEglDisplay, outputSurface.getEglSurface(), timestampNs);
 
@@ -157,7 +157,7 @@
             @NonNull OutputSurface outputSurface,
             @NonNull SurfaceOutput surfaceOutput,
             @NonNull SurfaceTexture surfaceTexture,
-            @NonNull LayoutSettings layoutSettings,
+            @NonNull CompositionSettings compositionSettings,
             int externalTextureId,
             boolean isPrimary) {
         useAndConfigureProgramWithTexture(externalTextureId);
@@ -179,13 +179,13 @@
         }
 
         float[] transTransform = getTransformMatrix(
-                new Size((int) (outputSurface.getWidth() * layoutSettings.getWidth()),
-                        (int) (outputSurface.getHeight() * layoutSettings.getHeight())),
+                new Size((int) (outputSurface.getWidth() * compositionSettings.getScale().first),
+                        (int) (outputSurface.getHeight() * compositionSettings.getScale().second)),
                 new Size(outputSurface.getWidth(), outputSurface.getHeight()),
-                layoutSettings);
+                compositionSettings);
         currentProgram.updateTransformMatrix(transTransform);
 
-        currentProgram.updateAlpha(layoutSettings.getAlpha());
+        currentProgram.updateAlpha(compositionSettings.getAlpha());
 
         GLES20.glEnable(GLES20.GL_BLEND);
         GLES20.glBlendFuncSeparate(
@@ -204,7 +204,7 @@
     private static float[] getTransformMatrix(
             @NonNull Size overlaySize,
             @NonNull Size backgroundSize,
-            @NonNull LayoutSettings layoutSettings) {
+            @NonNull CompositionSettings compositionSettings) {
         float[] aspectRatioMatrix = create4x4IdentityMatrix();
         float[] overlayFrameAnchorMatrix = create4x4IdentityMatrix();
         float[] transformationMatrix = create4x4IdentityMatrix();
@@ -217,12 +217,15 @@
                 /* z= */ 1.0f);
 
         // Translate the image.
-        Matrix.translateM(
-                overlayFrameAnchorMatrix,
-                /* mOffset= */ 0,
-                layoutSettings.getOffsetX() / layoutSettings.getWidth(),
-                layoutSettings.getOffsetY()  / layoutSettings.getHeight(),
-                /* z= */ 0.0f);
+        if (compositionSettings.getScale().first != 0.0f
+                || compositionSettings.getScale().second != 0.0f) {
+            Matrix.translateM(
+                    overlayFrameAnchorMatrix,
+                    /* mOffset= */ 0,
+                    compositionSettings.getOffset().first / compositionSettings.getScale().first,
+                    compositionSettings.getOffset().second / compositionSettings.getScale().second,
+                    /* z= */ 0.0f);
+        }
 
         // Correct for aspect ratio of image in output frame.
         Matrix.multiplyMM(
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
index afa52f8..3f5be70 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/concurrent/DualSurfaceProcessor.java
@@ -27,8 +27,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.DynamicRange;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.Logger;
 import androidx.camera.core.ProcessingException;
 import androidx.camera.core.SurfaceOutput;
@@ -81,21 +81,23 @@
     private SurfaceTexture mSecondarySurfaceTexture;
 
     DualSurfaceProcessor(@NonNull DynamicRange dynamicRange,
-            @NonNull LayoutSettings primaryLayoutSettings,
-            @NonNull LayoutSettings secondaryLayoutSettings) {
-        this(dynamicRange, Collections.emptyMap(), primaryLayoutSettings, secondaryLayoutSettings);
+            @NonNull CompositionSettings primaryCompositionSettings,
+            @NonNull CompositionSettings secondaryCompositionSettings) {
+        this(dynamicRange, Collections.emptyMap(),
+                primaryCompositionSettings, secondaryCompositionSettings);
     }
 
     DualSurfaceProcessor(
             @NonNull DynamicRange dynamicRange,
             @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides,
-            @NonNull LayoutSettings primaryLayoutSettings,
-            @NonNull LayoutSettings secondaryLayoutSettings) {
+            @NonNull CompositionSettings primaryCompositionSettings,
+            @NonNull CompositionSettings secondaryCompositionSettings) {
         mGlThread = new HandlerThread("GL Thread");
         mGlThread.start();
         mGlHandler = new Handler(mGlThread.getLooper());
         mGlExecutor = CameraXExecutors.newHandlerExecutor(mGlHandler);
-        mGlRenderer = new DualOpenGlRenderer(primaryLayoutSettings, secondaryLayoutSettings);
+        mGlRenderer = new DualOpenGlRenderer(
+                primaryCompositionSettings, secondaryCompositionSettings);
         try {
             initGlRenderer(dynamicRange, shaderProviderOverrides);
         } catch (RuntimeException e) {
@@ -263,8 +265,9 @@
         private Factory() {
         }
 
-        private static Function3<DynamicRange, LayoutSettings,
-                LayoutSettings, SurfaceProcessorInternal> sSupplier = DualSurfaceProcessor::new;
+        private static Function3<DynamicRange, CompositionSettings,
+                CompositionSettings, SurfaceProcessorInternal> sSupplier =
+                DualSurfaceProcessor::new;
 
         /**
          * Creates a new {@link DefaultSurfaceProcessor} with no-op shader.
@@ -272,9 +275,10 @@
         @NonNull
         public static SurfaceProcessorInternal newInstance(
                 @NonNull DynamicRange dynamicRange,
-                @NonNull LayoutSettings primaryLayoutSettings,
-                @NonNull LayoutSettings secondaryLayoutSettings) {
-            return sSupplier.invoke(dynamicRange, primaryLayoutSettings, secondaryLayoutSettings);
+                @NonNull CompositionSettings primaryCompositionSettings,
+                @NonNull CompositionSettings secondaryCompositionSettings) {
+            return sSupplier.invoke(dynamicRange,
+                    primaryCompositionSettings, secondaryCompositionSettings);
         }
 
         /**
@@ -283,8 +287,8 @@
         @VisibleForTesting
         public static void setSupplier(
                 @NonNull Function3<DynamicRange,
-                        LayoutSettings,
-                        LayoutSettings,
+                        CompositionSettings,
+                        CompositionSettings,
                         SurfaceProcessorInternal> supplier) {
             sSupplier = supplier;
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
index 8afe48f..99a6d8d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
@@ -44,8 +44,8 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.CameraEffect;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.ImageCapture;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.MirrorMode;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.impl.CameraInfoInternal;
@@ -89,12 +89,12 @@
 
     @NonNull
     private final VirtualCameraAdapter mVirtualCameraAdapter;
-    // The layout settings of primary camera in dual camera case.
+    // The composition settings of primary camera in dual camera case.
     @NonNull
-    private final LayoutSettings mLayoutSettings;
-    // The layout settings of secondary camera in dual camera case.
+    private final CompositionSettings mCompositionSettings;
+    // The composition settings of secondary camera in dual camera case.
     @NonNull
-    private final LayoutSettings mSecondaryLayoutSettings;
+    private final CompositionSettings mSecondaryCompositionSettings;
     // Node that applies effect to the input.
     @Nullable
     private SurfaceProcessorNode mEffectNode;
@@ -149,14 +149,14 @@
      */
     public StreamSharing(@NonNull CameraInternal camera,
             @Nullable CameraInternal secondaryCamera,
-            @NonNull LayoutSettings layoutSettings,
-            @NonNull LayoutSettings secondaryLayoutSettings,
+            @NonNull CompositionSettings compositionSettings,
+            @NonNull CompositionSettings secondaryCompositionSettings,
             @NonNull Set<UseCase> children,
             @NonNull UseCaseConfigFactory useCaseConfigFactory) {
         super(getDefaultConfig(children));
         mDefaultConfig = getDefaultConfig(children);
-        mLayoutSettings = layoutSettings;
-        mSecondaryLayoutSettings = secondaryLayoutSettings;
+        mCompositionSettings = compositionSettings;
+        mSecondaryCompositionSettings = secondaryCompositionSettings;
         mVirtualCameraAdapter = new VirtualCameraAdapter(
                 camera, secondaryCamera, children, useCaseConfigFactory,
                 (jpegQuality, rotationDegrees) -> {
@@ -316,8 +316,8 @@
                     getCamera(),
                     getSecondaryCamera(),
                     primaryStreamSpec, // use primary stream spec
-                    mLayoutSettings,
-                    mSecondaryLayoutSettings);
+                    mCompositionSettings,
+                    mSecondaryCompositionSettings);
             boolean isViewportSet = getViewPortCropRect() != null;
             Map<UseCase, DualOutConfig> outConfigMap =
                     mVirtualCameraAdapter.getChildrenOutConfigs(
@@ -511,14 +511,14 @@
             @NonNull CameraInternal primaryCamera,
             @NonNull CameraInternal secondaryCamera,
             @NonNull StreamSpec streamSpec,
-            @NonNull LayoutSettings primaryLayoutSettings,
-            @NonNull LayoutSettings secondaryLayoutSettings) {
+            @NonNull CompositionSettings primaryCompositionSettings,
+            @NonNull CompositionSettings secondaryCompositionSettings) {
         // TODO: handle EffectNode for dual camera case
         return new DualSurfaceProcessorNode(primaryCamera, secondaryCamera,
                 DualSurfaceProcessor.Factory.newInstance(
                         streamSpec.getDynamicRange(),
-                        primaryLayoutSettings,
-                        secondaryLayoutSettings));
+                        primaryCompositionSettings,
+                        secondaryCompositionSettings));
     }
 
     private int getRotationAppliedByEffect() {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index a28d501..42652ae 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -30,6 +30,7 @@
 import androidx.camera.core.CameraEffect
 import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
+import androidx.camera.core.CompositionSettings
 import androidx.camera.core.DynamicRange.HDR_UNSPECIFIED_10_BIT
 import androidx.camera.core.FocusMeteringAction
 import androidx.camera.core.FocusMeteringAction.FLAG_AE
@@ -37,7 +38,6 @@
 import androidx.camera.core.FocusMeteringAction.FLAG_AWB
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
-import androidx.camera.core.LayoutSettings
 import androidx.camera.core.Preview
 import androidx.camera.core.SurfaceOrientedMeteringPointFactory
 import androidx.camera.core.TorchState
@@ -317,8 +317,8 @@
                 null,
                 RestrictedCameraInfo(fakeCamera.cameraInfoInternal, extensionsConfig),
                 null,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 FakeCameraCoordinator(),
                 fakeManager,
                 FakeUseCaseConfigFactory(),
@@ -364,8 +364,8 @@
             StreamSharing(
                 fakeCamera,
                 null,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(preview, video),
                 useCaseConfigFactory
             )
@@ -1388,8 +1388,8 @@
                 RestrictedCameraInfo(camera.cameraInfoInternal, cameraConfig),
                 if (secondaryCamera == null) null
                 else RestrictedCameraInfo(secondaryCamera.cameraInfoInternal, cameraConfig),
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 cameraCoordinator,
                 fakeCameraDeviceSurfaceManager,
                 useCaseConfigFactory
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
index c5e0c42..72f99ff 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
@@ -37,13 +37,13 @@
 import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
 import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
+import androidx.camera.core.CompositionSettings
 import androidx.camera.core.DynamicRange
 import androidx.camera.core.DynamicRange.HLG_10_BIT
 import androidx.camera.core.DynamicRange.SDR
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
 import androidx.camera.core.ImageProxy
-import androidx.camera.core.LayoutSettings
 import androidx.camera.core.Preview
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.CameraCaptureCallback
@@ -134,8 +134,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1, child2),
                 useCaseConfigFactory
             )
@@ -171,8 +171,8 @@
             StreamSharing(
                 frontCamera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(preview, videoCapture),
                 useCaseConfigFactory
             )
@@ -207,8 +207,8 @@
             StreamSharing(
                 frontCamera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1),
                 useCaseConfigFactory
             )
@@ -230,8 +230,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1),
                 useCaseConfigFactory
             )
@@ -260,8 +260,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1),
                 useCaseConfigFactory
             )
@@ -358,8 +358,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1, imageCapture),
                 useCaseConfigFactory
             )
@@ -415,8 +415,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(unspecifiedChild, hdrChild),
                 useCaseConfigFactory
             )
@@ -452,8 +452,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(sdrChild, hdrChild),
                 useCaseConfigFactory
             )
@@ -535,8 +535,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1, imageCapture),
                 useCaseConfigFactory
             )
@@ -569,8 +569,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1, imageCapture),
                 useCaseConfigFactory
             )
@@ -605,8 +605,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1),
                 useCaseConfigFactory
             )
@@ -628,8 +628,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(child1),
                 useCaseConfigFactory
             )
@@ -655,8 +655,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(previewBuilder.build()),
                 Camera2UseCaseConfigFactory(context)
             )
@@ -873,8 +873,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(preview, videoCapture),
                 useCaseConfigFactory
             )
@@ -902,8 +902,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(preview, videoCapture),
                 useCaseConfigFactory
             )
@@ -928,8 +928,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(preview, imageCapture),
                 useCaseConfigFactory
             )
@@ -952,8 +952,8 @@
             StreamSharing(
                 camera,
                 secondaryCamera,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 setOf(preview, imageCapture, videoCapture),
                 useCaseConfigFactory
             )
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
index 35bbda9..b137a04 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
@@ -22,7 +22,7 @@
 
 import static java.util.Collections.emptyList;
 
-import androidx.camera.core.LayoutSettings;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.concurrent.CameraCoordinator;
 import androidx.camera.core.impl.CameraConfig;
 import androidx.camera.core.impl.CameraConfigs;
@@ -633,8 +633,8 @@
                 new RestrictedCameraInfo((CameraInfoInternal) mCamera.getCameraInfo(),
                         cameraConfig),
                 null,
-                LayoutSettings.DEFAULT,
-                LayoutSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
+                CompositionSettings.DEFAULT,
                 mCameraCoordinator,
                 new FakeCameraDeviceSurfaceManager(),
                 new FakeUseCaseConfigFactory());
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
index 8e1c9b4..1ac2586 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.kt
@@ -32,13 +32,13 @@
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.CameraX
 import androidx.camera.core.CameraXConfig
+import androidx.camera.core.CompositionSettings
 import androidx.camera.core.ConcurrentCamera
 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
 import androidx.camera.core.ExperimentalCameraInfo
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.InitializationException
-import androidx.camera.core.LayoutSettings
 import androidx.camera.core.Preview
 import androidx.camera.core.UseCase
 import androidx.camera.core.UseCaseGroup
@@ -214,8 +214,8 @@
                     lifecycleOwner,
                     cameraSelector,
                     null,
-                    LayoutSettings.DEFAULT,
-                    LayoutSettings.DEFAULT,
+                    CompositionSettings.DEFAULT,
+                    CompositionSettings.DEFAULT,
                     null,
                     emptyList<CameraEffect>(),
                     *useCases
@@ -254,8 +254,8 @@
                     lifecycleOwner,
                     cameraSelector,
                     null,
-                    LayoutSettings.DEFAULT,
-                    LayoutSettings.DEFAULT,
+                    CompositionSettings.DEFAULT,
+                    CompositionSettings.DEFAULT,
                     useCaseGroup.viewPort,
                     useCaseGroup.effects,
                     *useCaseGroup.useCases.toTypedArray<UseCase>()
@@ -285,7 +285,7 @@
      * If the concurrent logical cameras are binding the same preview and video capture use cases,
      * the concurrent cameras video recording will be supported. The concurrent camera preview
      * stream will be shared with video capture and record the concurrent cameras as a whole. The
-     * [LayoutSettings] can be used to configure the position of each camera stream.
+     * [CompositionSettings] can be used to configure the position of each camera stream.
      *
      * If we want to open concurrent physical cameras, which are two front cameras or two back
      * cameras, the device needs to support physical cameras and the capability could be checked via
@@ -381,8 +381,8 @@
                         lifecycleOwner,
                         cameraSelector,
                         null,
-                        LayoutSettings.DEFAULT,
-                        LayoutSettings.DEFAULT,
+                        CompositionSettings.DEFAULT,
+                        CompositionSettings.DEFAULT,
                         viewPort,
                         effects,
                         *useCases.toTypedArray<UseCase>()
@@ -446,8 +446,8 @@
                             firstCameraConfig.lifecycleOwner,
                             firstCameraConfig.cameraSelector,
                             secondCameraConfig.cameraSelector,
-                            firstCameraConfig.layoutSettings,
-                            secondCameraConfig.layoutSettings,
+                            firstCameraConfig.compositionSettings,
+                            secondCameraConfig.compositionSettings,
                             firstCameraConfig.useCaseGroup.viewPort,
                             firstCameraConfig.useCaseGroup.effects,
                             *firstCameraConfig.useCaseGroup.useCases.toTypedArray<UseCase>(),
@@ -460,8 +460,8 @@
                                 config!!.lifecycleOwner,
                                 config.cameraSelector,
                                 null,
-                                LayoutSettings.DEFAULT,
-                                LayoutSettings.DEFAULT,
+                                CompositionSettings.DEFAULT,
+                                CompositionSettings.DEFAULT,
                                 config.useCaseGroup.viewPort,
                                 config.useCaseGroup.effects,
                                 *config.useCaseGroup.useCases.toTypedArray<UseCase>()
@@ -530,8 +530,8 @@
      * @param primaryCameraSelector The primary camera selector which determines the camera to use
      *   for set of use cases.
      * @param secondaryCameraSelector The secondary camera selector in dual camera case.
-     * @param primaryLayoutSettings The layout settings for the primary camera.
-     * @param secondaryLayoutSettings The layout settings for the secondary camera.
+     * @param primaryCompositionSettings The composition settings for the primary camera.
+     * @param secondaryCompositionSettings The composition settings for the secondary camera.
      * @param viewPort The viewPort which represents the visible camera sensor rect.
      * @param effects The effects applied to the camera outputs.
      * @param useCases The use cases to bind to a lifecycle.
@@ -548,8 +548,8 @@
         lifecycleOwner: LifecycleOwner,
         primaryCameraSelector: CameraSelector,
         secondaryCameraSelector: CameraSelector?,
-        primaryLayoutSettings: LayoutSettings,
-        secondaryLayoutSettings: LayoutSettings,
+        primaryCompositionSettings: CompositionSettings,
+        secondaryCompositionSettings: CompositionSettings,
         viewPort: ViewPort?,
         effects: List<CameraEffect?>,
         vararg useCases: UseCase?
@@ -611,8 +611,8 @@
                             secondaryCameraInternal,
                             primaryRestrictedCameraInfo,
                             secondaryRestrictedCameraInfo,
-                            primaryLayoutSettings,
-                            secondaryLayoutSettings,
+                            primaryCompositionSettings,
+                            secondaryCompositionSettings,
                             mCameraX!!.cameraFactory.cameraCoordinator,
                             mCameraX!!.cameraDeviceSurfaceManager,
                             mCameraX!!.defaultConfigFactory
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
index dd5fe93..418e14e 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
@@ -53,8 +53,8 @@
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraX;
 import androidx.camera.core.CameraXConfig;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.ExperimentalRetryPolicy;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.Logger;
 import androidx.camera.core.RetryPolicy;
 import androidx.camera.core.UseCase;
@@ -633,8 +633,8 @@
                     null,
                     new RestrictedCameraInfo(camera.getCameraInfoInternal(), cameraConfig),
                     null,
-                    LayoutSettings.DEFAULT,
-                    LayoutSettings.DEFAULT,
+                    CompositionSettings.DEFAULT,
+                    CompositionSettings.DEFAULT,
                     cameraCoordinator,
                     cameraX.getCameraDeviceSurfaceManager(),
                     cameraX.getDefaultConfigFactory());
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java b/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java
index a42bd1b..529e630 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/ScreenFlashView.java
@@ -19,6 +19,8 @@
 import static androidx.camera.core.ImageCapture.FLASH_MODE_SCREEN;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.Color;
@@ -29,6 +31,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
 import androidx.annotation.UiThread;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCapture.ScreenFlash;
@@ -58,8 +61,8 @@
  * {@link PreviewView} does not encompass the full screen, users may want to use this view
  * separately so that whole screen can be encompassed during screen flash operation.
  *
+ * @see #getScreenFlash
  * @see ImageCapture#FLASH_MODE_SCREEN
- * @see PreviewView#getScreenFlash
  */
 public final class ScreenFlashView extends View {
     private static final String TAG = "ScreenFlashView";
@@ -67,6 +70,9 @@
     private Window mScreenFlashWindow;
     private ImageCapture.ScreenFlash mScreenFlash;
 
+    /** The timeout in seconds for the visibility animation at {@link ScreenFlash#apply}. */
+    private static final long ANIMATION_DURATION_MILLIS = 1000;
+
     @UiThread
     public ScreenFlashView(@NonNull Context context) {
         this(context, null);
@@ -80,7 +86,7 @@
     @UiThread
     public ScreenFlashView(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr) {
-        this(context, attrs,  defStyleAttr, 0);
+        this(context, attrs, defStyleAttr, 0);
     }
 
     @UiThread
@@ -134,7 +140,7 @@
             return;
         }
         mCameraController.setScreenFlashUiInfo(new ScreenFlashUiInfo(
-                        ScreenFlashUiInfo.ProviderType.SCREEN_FLASH_VIEW, control));
+                ScreenFlashUiInfo.ProviderType.SCREEN_FLASH_VIEW, control));
     }
 
     /**
@@ -170,45 +176,122 @@
         if (mScreenFlashWindow != window) {
             mScreenFlash = window == null ? null : new ScreenFlash() {
                 private float mPreviousBrightness;
+                private ValueAnimator mAnimator;
 
                 @Override
                 public void apply(long expirationTimeMillis,
                         @NonNull ImageCapture.ScreenFlashListener screenFlashListener) {
                     Logger.d(TAG, "ScreenFlash#apply");
 
-                    setAlpha(1f);
+                    mPreviousBrightness = getBrightness();
+                    setBrightness(1.0f);
 
-                    // Maximize screen brightness
-                    WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
-                    mPreviousBrightness = layoutParam.screenBrightness;
-                    layoutParam.screenBrightness = 1F;
-                    mScreenFlashWindow.setAttributes(layoutParam);
-
-                    screenFlashListener.onCompleted();
+                    if (mAnimator != null) {
+                        mAnimator.cancel();
+                    }
+                    mAnimator = animateToFullOpacity(screenFlashListener::onCompleted);
                 }
 
                 @Override
                 public void clear() {
                     Logger.d(TAG, "ScreenFlash#clearScreenFlashUi");
 
+                    if (mAnimator != null) {
+                        mAnimator.cancel();
+                        mAnimator = null;
+                    }
+
                     setAlpha(0f);
 
                     // Restore screen brightness
-                    WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
-                    layoutParam.screenBrightness = mPreviousBrightness;
-                    mScreenFlashWindow.setAttributes(layoutParam);
+                    setBrightness(mPreviousBrightness);
                 }
             };
         }
     }
 
+    private ValueAnimator animateToFullOpacity(@Nullable Runnable onAnimationEnd) {
+        Logger.d(TAG, "animateToFullOpacity");
+
+        ValueAnimator animator = ValueAnimator.ofFloat(0F, 1F);
+
+        // TODO: b/355168952 - Allow users to overwrite the animation duration.
+        animator.setDuration(getVisibilityRampUpAnimationDurationMillis());
+
+        animator.addUpdateListener(animation -> {
+            Logger.d(TAG, "animateToFullOpacity: value = " + (float) animation.getAnimatedValue());
+            setAlpha((float) animation.getAnimatedValue());
+        });
+
+        animator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation) {
+
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation) {
+                Logger.d(TAG, "ScreenFlash#apply: onAnimationEnd");
+                if (onAnimationEnd != null) {
+                    onAnimationEnd.run();
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(@NonNull Animator animation) {
+
+            }
+
+            @Override
+            public void onAnimationRepeat(@NonNull Animator animation) {
+
+            }
+        });
+
+        animator.start();
+
+        return animator;
+    }
+
+    private float getBrightness() {
+        if (mScreenFlashWindow == null) {
+            Logger.e(TAG, "setBrightness: mScreenFlashWindow is null!");
+            return Float.NaN;
+        }
+
+        WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
+        return layoutParam.screenBrightness;
+    }
+
+    private void setBrightness(float value) {
+        if (mScreenFlashWindow == null) {
+            Logger.e(TAG, "setBrightness: mScreenFlashWindow is null!");
+            return;
+        }
+
+        if (Float.isNaN(value)) {
+            Logger.e(TAG, "setBrightness: value is NaN!");
+            return;
+        }
+
+        WindowManager.LayoutParams layoutParam = mScreenFlashWindow.getAttributes();
+        layoutParam.screenBrightness = value;
+        mScreenFlashWindow.setAttributes(layoutParam);
+        Logger.d(TAG, "Brightness set to " + layoutParam.screenBrightness);
+    }
+
     /**
      * Returns an {@link ScreenFlash} implementation based on the {@link Window} instance
      * set via {@link #setScreenFlashWindow(Window)}.
      *
      * <p> When {@link ScreenFlash#apply(long, ImageCapture.ScreenFlashListener)} is invoked,
-     * this view becomes fully visible and screen brightness is maximized using the provided
-     * {@code Window}. The default color of the overlay view is {@link Color#WHITE}. To change
+     * this view becomes fully visible gradually with an animation and screen brightness is
+     * maximized using the provided {@code Window}. Since brightness change of the display happens
+     * asynchronously and may take some time to be completed, the animation to ramp up visibility
+     * may require a duration of sufficient delay (decided internally) before
+     * {@link ImageCapture.ScreenFlashListener#onCompleted()} is invoked.
+     *
+     * <p> The default color of the overlay view is {@link Color#WHITE}. To change
      * the color, use {@link #setBackgroundColor(int)}.
      *
      * <p> When {@link ScreenFlash#clear()} is invoked, the view
@@ -219,11 +302,23 @@
      * Window} is set or none set at all, a null value will be returned by this method.
      *
      * @return A simple {@link ScreenFlash} implementation, or null value if a non-null
-     *         {@code Window} instance hasn't been set.
+     * {@code Window} instance hasn't been set.
      */
     @UiThread
     @Nullable
     public ScreenFlash getScreenFlash() {
         return mScreenFlash;
     }
+
+    /**
+     * Returns the duration of the visibility ramp-up animation.
+     *
+     * <p> This is currently used in {@link ScreenFlash#apply}.
+     *
+     * @see #getScreenFlash()
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public long getVisibilityRampUpAnimationDurationMillis() {
+        return ANIMATION_DURATION_MILLIS;
+    }
 }
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt b/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt
index 981552b..510c632 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/ScreenFlashViewTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.os.Build
+import android.os.Looper.getMainLooper
 import android.view.Window
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.ImageCapture
@@ -26,12 +27,14 @@
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.runBlocking
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 import org.robolectric.shadows.ShadowWindow
@@ -98,13 +101,29 @@
     }
 
     @Test
-    fun isFullyVisible_whenScreenFlashApplyInvoked() {
+    fun isNotVisibleImmediately_whenScreenFlashApplyInvoked() {
         val screenFlash = getScreenFlashAfterSettingWindow(true)
         screenFlash!!.apply(
             System.currentTimeMillis() +
                 TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS),
             noOpListener,
         )
+        assertThat(screenFlashView.alpha).isEqualTo(0f)
+    }
+
+    @Test
+    fun isFullyVisibleAfterAnimationDuration_whenScreenFlashApplyInvoked() = runBlocking {
+        val screenFlash = getScreenFlashAfterSettingWindow(true)
+        screenFlash!!.apply(
+            System.currentTimeMillis() +
+                TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS),
+            noOpListener,
+        )
+        shadowOf(getMainLooper())
+            .idleFor(
+                screenFlashView.visibilityRampUpAnimationDurationMillis + 1,
+                TimeUnit.MILLISECONDS
+            )
         assertThat(screenFlashView.alpha).isEqualTo(1f)
     }
 
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
index f45d709..73dfc42 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CaptureOptionSubmissionTest.kt
@@ -141,11 +141,6 @@
     @Test
     fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToPreviewOnly() = runBlocking {
         assumeTrue(
-            "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-            implName != CameraPipeConfig::class.simpleName
-        )
-
-        assumeTrue(
             "TODO(b/332235883): Enable for legacy when the bug is resolved",
             !isHwLevelLegacy()
         )
@@ -184,11 +179,6 @@
     fun canSubmitSupportedAeTargetFpsRanges_whenTargetFrameRateSetToVideoCaptureOnly() =
         runBlocking {
             assumeTrue(
-                "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-                implName != CameraPipeConfig::class.simpleName
-            )
-
-            assumeTrue(
                 "TODO(b/332235883): Enable for legacy when the bug is resolved",
                 !isHwLevelLegacy()
             )
@@ -233,11 +223,6 @@
     @Test
     fun canSetAeTargetFpsRangeWithCamera2Interop() = runBlocking {
         assumeTrue(
-            "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-            implName != CameraPipeConfig::class.simpleName
-        )
-
-        assumeTrue(
             "TODO(b/332235883): Enable for legacy when the bug is resolved",
             !isHwLevelLegacy()
         )
@@ -292,11 +277,6 @@
     @Test
     fun canOverwriteFpsRangeWithCamera2Interop_whenAnotherSetViaSetTargetFrameRate() = runBlocking {
         assumeTrue(
-            "TODO(b/331900702): Enable when the bug is fixed at camera-pipe",
-            implName != CameraPipeConfig::class.simpleName
-        )
-
-        assumeTrue(
             "TODO(b/332235883): Enable for legacy when the bug is resolved",
             !isHwLevelLegacy()
         )
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
index 35a4e1f..53eb0a0 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
@@ -68,13 +68,13 @@
 import androidx.camera.core.CameraControl;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CompositionSettings;
 import androidx.camera.core.ConcurrentCamera;
 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.ExperimentalCameraInfo;
 import androidx.camera.core.ExperimentalMirrorMode;
 import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.LayoutSettings;
 import androidx.camera.core.MeteringPoint;
 import androidx.camera.core.MirrorMode;
 import androidx.camera.core.Preview;
@@ -483,23 +483,19 @@
                 SingleCameraConfig primary = new SingleCameraConfig(
                         cameraSelectorPrimary,
                         useCaseGroup,
-                        new LayoutSettings.Builder()
+                        new CompositionSettings.Builder()
                                 .setAlpha(1.0f)
-                                .setOffsetX(0.0f)
-                                .setOffsetY(0.0f)
-                                .setWidth(1.0f)
-                                .setHeight(1.0f)
+                                .setOffset(0.0f, 0.0f)
+                                .setScale(1.0f, 1.0f)
                                 .build(),
                         lifecycleOwner);
                 SingleCameraConfig secondary = new SingleCameraConfig(
                         cameraSelectorSecondary,
                         useCaseGroup,
-                        new LayoutSettings.Builder()
+                        new CompositionSettings.Builder()
                                 .setAlpha(1.0f)
-                                .setOffsetX(-0.3f)
-                                .setOffsetY(-0.4f)
-                                .setWidth(0.3f)
-                                .setHeight(0.3f)
+                                .setOffset(-0.3f, -0.4f)
+                                .setScale(0.3f, 0.3f)
                                 .build(),
                         lifecycleOwner);
                 cameraProvider.bindToLifecycle(ImmutableList.of(primary, secondary));
diff --git a/camera/integration-tests/timingtestapp/build.gradle b/camera/integration-tests/timingtestapp/build.gradle
index 4a1f62f..d3e0f94 100644
--- a/camera/integration-tests/timingtestapp/build.gradle
+++ b/camera/integration-tests/timingtestapp/build.gradle
@@ -58,9 +58,9 @@
     implementation("androidx.fragment:fragment-ktx:1.3.0")
     implementation("androidx.appcompat:appcompat:1.1.0")
     implementation("androidx.collection:collection:1.4.2")
-    implementation("androidx.preference:preference:1.1.0")
+    implementation("androidx.preference:preference:1.2.1")
     implementation("androidx.exifinterface:exifinterface:1.0.0")
-    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.2.0")
+    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")
     implementation(libs.constraintLayout)
     implementation(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesAndroid)
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
index 87f2448..5461b40 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
@@ -36,9 +36,7 @@
         lifecycleRegistry.currentState = Lifecycle.State.CREATED
     }
 
-    override fun getLifecycle(): Lifecycle {
-        return lifecycleRegistry
-    }
+    override val lifecycle: Lifecycle = lifecycleRegistry
 
     fun start() {
         if (Looper.myLooper() != mainHandler.looper) {
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
index 3c7762a..570146b 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
@@ -157,7 +157,7 @@
 
         // Human readable report
         val humanReadableReportObserver =
-            Observer<String> { newReport -> binding.textLog.text = newReport ?: "" }
+            Observer<String> { newReport -> binding.textLog.text = newReport }
         camViewModel.getHumanReadableReport().observe(this, humanReadableReportObserver)
     }
 
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
index ddcb57f..6bb1454 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
@@ -32,11 +32,11 @@
 
     override fun onResume() {
         super.onResume()
-        preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+        preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
     }
 
     override fun onPause() {
         super.onPause()
-        preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
+        preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
     }
 }
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
index 55dee0e..c4ace27 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
@@ -54,12 +54,12 @@
 
     override fun onResume() {
         super.onResume()
-        preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+        preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
     }
 
     override fun onPause() {
         super.onPause()
-        preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
+        preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
     }
 
     /**
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 88bc7b3..e8a0b35 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -1746,6 +1746,48 @@
     method public int trim();
   }
 
+  public final class MutableOrderedScatterSet<E> extends androidx.collection.OrderedScatterSet<E> {
+    ctor public MutableOrderedScatterSet();
+    ctor public MutableOrderedScatterSet(optional int initialCapacity);
+    method public boolean add(E element);
+    method public boolean addAll(androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean addAll(E[] elements);
+    method public boolean addAll(Iterable<? extends E> elements);
+    method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public java.util.Set<E> asMutableSet();
+    method public void clear();
+    method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
+    method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+    method public operator void minusAssign(E element);
+    method public operator void minusAssign(E[] elements);
+    method public operator void minusAssign(Iterable<? extends E> elements);
+    method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
+    method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+    method public operator void plusAssign(E element);
+    method public operator void plusAssign(E[] elements);
+    method public operator void plusAssign(Iterable<? extends E> elements);
+    method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public boolean remove(E element);
+    method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+    method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean removeAll(E[] elements);
+    method public boolean removeAll(Iterable<? extends E> elements);
+    method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
+    method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method @IntRange(from=0L) public int trim();
+    method public void trimToSize(int maxSize);
+  }
+
   public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
     ctor public MutableScatterMap();
     ctor public MutableScatterMap(optional int initialCapacity);
@@ -1783,6 +1825,7 @@
     ctor public MutableScatterSet(optional int initialCapacity);
     method public boolean add(E element);
     method public boolean addAll(androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
     method public boolean addAll(androidx.collection.ScatterSet<E> elements);
     method public boolean addAll(E[] elements);
     method public boolean addAll(Iterable<? extends E> elements);
@@ -1790,12 +1833,14 @@
     method public java.util.Set<E> asMutableSet();
     method public void clear();
     method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
     method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
     method public operator void minusAssign(E element);
     method public operator void minusAssign(E[] elements);
     method public operator void minusAssign(Iterable<? extends E> elements);
     method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
     method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
     method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
     method public operator void plusAssign(E element);
     method public operator void plusAssign(E[] elements);
@@ -1803,11 +1848,16 @@
     method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
     method public boolean remove(E element);
     method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+    method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
     method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
     method public boolean removeAll(E[] elements);
     method public boolean removeAll(Iterable<? extends E> elements);
     method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
     method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
+    method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
     method @IntRange(from=0L) public int trim();
   }
 
@@ -2034,6 +2084,53 @@
     method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
   }
 
+  public abstract sealed class OrderedScatterSet<E> {
+    method public final inline boolean all(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final java.util.Set<E> asSet();
+    method public final operator boolean contains(E element);
+    method @IntRange(from=0L) public final int count();
+    method @IntRange(from=0L) public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final E first();
+    method public final E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method public final inline void forEachReverse(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method @IntRange(from=0L) public final int getCapacity();
+    method @IntRange(from=0L) public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
+    method public final E last();
+    method public final E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean none();
+    method public final inline java.util.List<E> toList();
+    property @IntRange(from=0L) public final int capacity;
+    property @IntRange(from=0L) public final int size;
+  }
+
+  public final class OrderedScatterSetKt {
+    method public static <E> androidx.collection.OrderedScatterSet<E> emptyOrderedScatterSet();
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf();
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1);
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2);
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2, E element3);
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E... elements);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf();
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2, E element3);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E... elements);
+  }
+
   public abstract sealed class ScatterMap<K, V> {
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index de5f4d1..509b164 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -1835,6 +1835,49 @@
     method public int trim();
   }
 
+  public final class MutableOrderedScatterSet<E> extends androidx.collection.OrderedScatterSet<E> {
+    ctor public MutableOrderedScatterSet();
+    ctor public MutableOrderedScatterSet(optional int initialCapacity);
+    method public boolean add(E element);
+    method public boolean addAll(androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean addAll(E[] elements);
+    method public boolean addAll(Iterable<? extends E> elements);
+    method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public java.util.Set<E> asMutableSet();
+    method public void clear();
+    method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
+    method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+    method public operator void minusAssign(E element);
+    method public operator void minusAssign(E[] elements);
+    method public operator void minusAssign(Iterable<? extends E> elements);
+    method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
+    method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+    method public operator void plusAssign(E element);
+    method public operator void plusAssign(E[] elements);
+    method public operator void plusAssign(Iterable<? extends E> elements);
+    method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public boolean remove(E element);
+    method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+    method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean removeAll(E[] elements);
+    method public boolean removeAll(Iterable<? extends E> elements);
+    method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+    method @kotlin.PublishedApi internal void removeElementAt(int index);
+    method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
+    method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method @IntRange(from=0L) public int trim();
+    method public void trimToSize(int maxSize);
+  }
+
   public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
     ctor public MutableScatterMap();
     ctor public MutableScatterMap(optional int initialCapacity);
@@ -1874,6 +1917,7 @@
     ctor public MutableScatterSet(optional int initialCapacity);
     method public boolean add(E element);
     method public boolean addAll(androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(androidx.collection.OrderedScatterSet<E> elements);
     method public boolean addAll(androidx.collection.ScatterSet<E> elements);
     method public boolean addAll(E[] elements);
     method public boolean addAll(Iterable<? extends E> elements);
@@ -1881,12 +1925,14 @@
     method public java.util.Set<E> asMutableSet();
     method public void clear();
     method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void minusAssign(androidx.collection.OrderedScatterSet<E> elements);
     method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
     method public operator void minusAssign(E element);
     method public operator void minusAssign(E[] elements);
     method public operator void minusAssign(Iterable<? extends E> elements);
     method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
     method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void plusAssign(androidx.collection.OrderedScatterSet<E> elements);
     method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
     method public operator void plusAssign(E element);
     method public operator void plusAssign(E[] elements);
@@ -1894,12 +1940,17 @@
     method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
     method public boolean remove(E element);
     method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+    method public boolean removeAll(androidx.collection.OrderedScatterSet<E> elements);
     method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
     method public boolean removeAll(E[] elements);
     method public boolean removeAll(Iterable<? extends E> elements);
     method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
     method @kotlin.PublishedApi internal void removeElementAt(int index);
     method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public boolean retainAll(androidx.collection.OrderedScatterSet<E> elements);
+    method public boolean retainAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
+    method public boolean retainAll(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
     method @IntRange(from=0L) public int trim();
   }
 
@@ -2143,6 +2194,60 @@
     method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
   }
 
+  public abstract sealed class OrderedScatterSet<E> {
+    method public final inline boolean all(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final java.util.Set<E> asSet();
+    method public final operator boolean contains(E element);
+    method @IntRange(from=0L) public final int count();
+    method @IntRange(from=0L) public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final E first();
+    method public final E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method public final inline void forEachReverse(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method @IntRange(from=0L) public final int getCapacity();
+    method @IntRange(from=0L) public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
+    method public final E last();
+    method public final E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean none();
+    method public final inline java.util.List<E> toList();
+    method @kotlin.PublishedApi internal final inline void unorderedForEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void unorderedForEachIndex(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    property @IntRange(from=0L) public final int capacity;
+    property @IntRange(from=0L) public final int size;
+    field @kotlin.PublishedApi internal Object?[] elements;
+    field @kotlin.PublishedApi internal int head;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal long[] nodes;
+    field @kotlin.PublishedApi internal int tail;
+  }
+
+  public final class OrderedScatterSetKt {
+    method public static <E> androidx.collection.OrderedScatterSet<E> emptyOrderedScatterSet();
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf();
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1);
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2);
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E element1, E element2, E element3);
+    method public static <E> androidx.collection.MutableOrderedScatterSet<E> mutableOrderedScatterSetOf(E... elements);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf();
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E element1, E element2, E element3);
+    method public static <E> androidx.collection.OrderedScatterSet<E> orderedScatterSetOf(E... elements);
+  }
+
   public abstract sealed class ScatterMap<K, V> {
     method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Boolean> predicate);
     method public final boolean any();
@@ -2296,6 +2401,11 @@
     field @kotlin.PublishedApi internal Object?[] values;
   }
 
+  public final class SieveCacheKt {
+    field @kotlin.PublishedApi internal static final int NodeInvalidLink = 2147483647; // 0x7fffffff
+    field @kotlin.PublishedApi internal static final long NodeLinkMask = 2147483647L; // 0x7fffffffL
+  }
+
   public class SimpleArrayMap<K, V> {
     ctor public SimpleArrayMap();
     ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<? extends K,? extends V>? map);
diff --git a/collection/collection/bcv/native/current.txt b/collection/collection/bcv/native/current.txt
index e63db68..a89ea397 100644
--- a/collection/collection/bcv/native/current.txt
+++ b/collection/collection/bcv/native/current.txt
@@ -313,10 +313,52 @@
     final inline fun plusAssign(androidx.collection/ObjectLongMap<#A>) // androidx.collection/MutableObjectLongMap.plusAssign|plusAssign(androidx.collection.ObjectLongMap<1:0>){}[0]
     final inline fun removeIf(kotlin/Function2<#A, kotlin/Long, kotlin/Boolean>) // androidx.collection/MutableObjectLongMap.removeIf|removeIf(kotlin.Function2<1:0,kotlin.Long,kotlin.Boolean>){}[0]
 }
+final class <#A: kotlin/Any?> androidx.collection/MutableOrderedScatterSet : androidx.collection/OrderedScatterSet<#A> { // androidx.collection/MutableOrderedScatterSet|null[0]
+    constructor <init>(kotlin/Int =...) // androidx.collection/MutableOrderedScatterSet.<init>|<init>(kotlin.Int){}[0]
+    final fun add(#A): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.add|add(1:0){}[0]
+    final fun addAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(androidx.collection.ObjectList<1:0>){}[0]
+    final fun addAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+    final fun addAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(androidx.collection.ScatterSet<1:0>){}[0]
+    final fun addAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(kotlin.collections.Iterable<1:0>){}[0]
+    final fun addAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(kotlin.sequences.Sequence<1:0>){}[0]
+    final fun addAll(kotlin/Array<out #A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.addAll|addAll(kotlin.Array<out|1:0>){}[0]
+    final fun asMutableSet(): kotlin.collections/MutableSet<#A> // androidx.collection/MutableOrderedScatterSet.asMutableSet|asMutableSet(){}[0]
+    final fun clear() // androidx.collection/MutableOrderedScatterSet.clear|clear(){}[0]
+    final fun minusAssign(#A) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(1:0){}[0]
+    final fun minusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(androidx.collection.ObjectList<1:0>){}[0]
+    final fun minusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
+    final fun minusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(androidx.collection.ScatterSet<1:0>){}[0]
+    final fun minusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(kotlin.collections.Iterable<1:0>){}[0]
+    final fun minusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(kotlin.sequences.Sequence<1:0>){}[0]
+    final fun minusAssign(kotlin/Array<out #A>) // androidx.collection/MutableOrderedScatterSet.minusAssign|minusAssign(kotlin.Array<out|1:0>){}[0]
+    final fun plusAssign(#A) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(1:0){}[0]
+    final fun plusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(androidx.collection.ObjectList<1:0>){}[0]
+    final fun plusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
+    final fun plusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(androidx.collection.ScatterSet<1:0>){}[0]
+    final fun plusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(kotlin.collections.Iterable<1:0>){}[0]
+    final fun plusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(kotlin.sequences.Sequence<1:0>){}[0]
+    final fun plusAssign(kotlin/Array<out #A>) // androidx.collection/MutableOrderedScatterSet.plusAssign|plusAssign(kotlin.Array<out|1:0>){}[0]
+    final fun remove(#A): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.remove|remove(1:0){}[0]
+    final fun removeAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(androidx.collection.ObjectList<1:0>){}[0]
+    final fun removeAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+    final fun removeAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(androidx.collection.ScatterSet<1:0>){}[0]
+    final fun removeAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(kotlin.collections.Iterable<1:0>){}[0]
+    final fun removeAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(kotlin.sequences.Sequence<1:0>){}[0]
+    final fun removeAll(kotlin/Array<out #A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.removeAll|removeAll(kotlin.Array<out|1:0>){}[0]
+    final fun removeElementAt(kotlin/Int) // androidx.collection/MutableOrderedScatterSet.removeElementAt|removeElementAt(kotlin.Int){}[0]
+    final fun retainAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+    final fun retainAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(androidx.collection.ScatterSet<1:0>){}[0]
+    final fun retainAll(kotlin.collections/Collection<#A>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(kotlin.collections.Collection<1:0>){}[0]
+    final fun retainAll(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/MutableOrderedScatterSet.retainAll|retainAll(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final fun trim(): kotlin/Int // androidx.collection/MutableOrderedScatterSet.trim|trim(){}[0]
+    final fun trimToSize(kotlin/Int) // androidx.collection/MutableOrderedScatterSet.trimToSize|trimToSize(kotlin.Int){}[0]
+    final inline fun removeIf(kotlin/Function1<#A, kotlin/Boolean>) // androidx.collection/MutableOrderedScatterSet.removeIf|removeIf(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+}
 final class <#A: kotlin/Any?> androidx.collection/MutableScatterSet : androidx.collection/ScatterSet<#A> { // androidx.collection/MutableScatterSet|null[0]
     constructor <init>(kotlin/Int =...) // androidx.collection/MutableScatterSet.<init>|<init>(kotlin.Int){}[0]
     final fun add(#A): kotlin/Boolean // androidx.collection/MutableScatterSet.add|add(1:0){}[0]
     final fun addAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(androidx.collection.ObjectList<1:0>){}[0]
+    final fun addAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
     final fun addAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(androidx.collection.ScatterSet<1:0>){}[0]
     final fun addAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(kotlin.collections.Iterable<1:0>){}[0]
     final fun addAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.addAll|addAll(kotlin.sequences.Sequence<1:0>){}[0]
@@ -325,23 +367,30 @@
     final fun clear() // androidx.collection/MutableScatterSet.clear|clear(){}[0]
     final fun minusAssign(#A) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(1:0){}[0]
     final fun minusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(androidx.collection.ObjectList<1:0>){}[0]
+    final fun minusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
     final fun minusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(androidx.collection.ScatterSet<1:0>){}[0]
     final fun minusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(kotlin.collections.Iterable<1:0>){}[0]
     final fun minusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(kotlin.sequences.Sequence<1:0>){}[0]
     final fun minusAssign(kotlin/Array<out #A>) // androidx.collection/MutableScatterSet.minusAssign|minusAssign(kotlin.Array<out|1:0>){}[0]
     final fun plusAssign(#A) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(1:0){}[0]
     final fun plusAssign(androidx.collection/ObjectList<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(androidx.collection.ObjectList<1:0>){}[0]
+    final fun plusAssign(androidx.collection/OrderedScatterSet<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(androidx.collection.OrderedScatterSet<1:0>){}[0]
     final fun plusAssign(androidx.collection/ScatterSet<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(androidx.collection.ScatterSet<1:0>){}[0]
     final fun plusAssign(kotlin.collections/Iterable<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(kotlin.collections.Iterable<1:0>){}[0]
     final fun plusAssign(kotlin.sequences/Sequence<#A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(kotlin.sequences.Sequence<1:0>){}[0]
     final fun plusAssign(kotlin/Array<out #A>) // androidx.collection/MutableScatterSet.plusAssign|plusAssign(kotlin.Array<out|1:0>){}[0]
     final fun remove(#A): kotlin/Boolean // androidx.collection/MutableScatterSet.remove|remove(1:0){}[0]
     final fun removeAll(androidx.collection/ObjectList<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(androidx.collection.ObjectList<1:0>){}[0]
+    final fun removeAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
     final fun removeAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(androidx.collection.ScatterSet<1:0>){}[0]
     final fun removeAll(kotlin.collections/Iterable<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(kotlin.collections.Iterable<1:0>){}[0]
     final fun removeAll(kotlin.sequences/Sequence<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(kotlin.sequences.Sequence<1:0>){}[0]
     final fun removeAll(kotlin/Array<out #A>): kotlin/Boolean // androidx.collection/MutableScatterSet.removeAll|removeAll(kotlin.Array<out|1:0>){}[0]
     final fun removeElementAt(kotlin/Int) // androidx.collection/MutableScatterSet.removeElementAt|removeElementAt(kotlin.Int){}[0]
+    final fun retainAll(androidx.collection/OrderedScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(androidx.collection.OrderedScatterSet<1:0>){}[0]
+    final fun retainAll(androidx.collection/ScatterSet<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(androidx.collection.ScatterSet<1:0>){}[0]
+    final fun retainAll(kotlin.collections/Collection<#A>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(kotlin.collections.Collection<1:0>){}[0]
+    final fun retainAll(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/MutableScatterSet.retainAll|retainAll(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
     final fun trim(): kotlin/Int // androidx.collection/MutableScatterSet.trim|trim(){}[0]
     final inline fun removeIf(kotlin/Function1<#A, kotlin/Boolean>) // androidx.collection/MutableScatterSet.removeIf|removeIf(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
 }
@@ -720,6 +769,10 @@
     final fun <get-BitmaskLsb>(): kotlin/Long // androidx.collection/BitmaskLsb.<get-BitmaskLsb>|<get-BitmaskLsb>(){}[0]
 final const val androidx.collection/BitmaskMsb // androidx.collection/BitmaskMsb|<get-BitmaskMsb>(){}[0]
     final fun <get-BitmaskMsb>(): kotlin/Long // androidx.collection/BitmaskMsb.<get-BitmaskMsb>|<get-BitmaskMsb>(){}[0]
+final const val androidx.collection/NodeInvalidLink // androidx.collection/NodeInvalidLink|{}NodeInvalidLink[0]
+    final fun <get-NodeInvalidLink>(): kotlin/Int // androidx.collection/NodeInvalidLink.<get-NodeInvalidLink>|<get-NodeInvalidLink>(){}[0]
+final const val androidx.collection/NodeLinkMask // androidx.collection/NodeLinkMask|{}NodeLinkMask[0]
+    final fun <get-NodeLinkMask>(): kotlin/Long // androidx.collection/NodeLinkMask.<get-NodeLinkMask>|<get-NodeLinkMask>(){}[0]
 final const val androidx.collection/Sentinel // androidx.collection/Sentinel|{}Sentinel[0]
     final fun <get-Sentinel>(): kotlin/Long // androidx.collection/Sentinel.<get-Sentinel>|<get-Sentinel>(){}[0]
 final fun <#A: kotlin/Any?, #B: kotlin/Any?> androidx.collection/emptyScatterMap(): androidx.collection/ScatterMap<#A, #B> // androidx.collection/emptyScatterMap|emptyScatterMap(){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
@@ -741,6 +794,7 @@
 final fun <#A: kotlin/Any?> androidx.collection/emptyObjectIntMap(): androidx.collection/ObjectIntMap<#A> // androidx.collection/emptyObjectIntMap|emptyObjectIntMap(){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/emptyObjectList(): androidx.collection/ObjectList<#A> // androidx.collection/emptyObjectList|emptyObjectList(){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/emptyObjectLongMap(): androidx.collection/ObjectLongMap<#A> // androidx.collection/emptyObjectLongMap|emptyObjectLongMap(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/emptyOrderedScatterSet(): androidx.collection/OrderedScatterSet<#A> // androidx.collection/emptyOrderedScatterSet|emptyOrderedScatterSet(){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/emptyScatterSet(): androidx.collection/ScatterSet<#A> // androidx.collection/emptyScatterSet|emptyScatterSet(){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/floatObjectMapOf(): androidx.collection/FloatObjectMap<#A> // androidx.collection/floatObjectMapOf|floatObjectMapOf(){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/floatObjectMapOf(kotlin/Float, #A): androidx.collection/FloatObjectMap<#A> // androidx.collection/floatObjectMapOf|floatObjectMapOf(kotlin.Float;0:0){0§<kotlin.Any?>}[0]
@@ -799,6 +853,11 @@
 final fun <#A: kotlin/Any?> androidx.collection/mutableObjectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/MutableObjectLongMap<#A> // androidx.collection/mutableObjectLongMapOf|mutableObjectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/mutableObjectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/MutableObjectLongMap<#A> // androidx.collection/mutableObjectLongMapOf|mutableObjectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/mutableObjectLongMapOf(): androidx.collection/MutableObjectLongMap<#A> // androidx.collection/mutableObjectLongMapOf|mutableObjectLongMapOf(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(#A): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(#A, #A): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(#A, #A, #A): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/mutableOrderedScatterSetOf(kotlin/Array<out #A>...): androidx.collection/MutableOrderedScatterSet<#A> // androidx.collection/mutableOrderedScatterSetOf|mutableOrderedScatterSetOf(kotlin.Array<out|0:0>...){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/mutableScatterSetOf(#A): androidx.collection/MutableScatterSet<#A> // androidx.collection/mutableScatterSetOf|mutableScatterSetOf(0:0){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/mutableScatterSetOf(#A, #A): androidx.collection/MutableScatterSet<#A> // androidx.collection/mutableScatterSetOf|mutableScatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/mutableScatterSetOf(#A, #A, #A): androidx.collection/MutableScatterSet<#A> // androidx.collection/mutableScatterSetOf|mutableScatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
@@ -827,6 +886,11 @@
 final fun <#A: kotlin/Any?> androidx.collection/objectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/ObjectLongMap<#A> // androidx.collection/objectLongMapOf|objectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/objectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/ObjectLongMap<#A> // androidx.collection/objectLongMapOf|objectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/objectLongMapOf(#A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long, #A, kotlin/Long): androidx.collection/ObjectLongMap<#A> // androidx.collection/objectLongMapOf|objectLongMapOf(0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long;0:0;kotlin.Long){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(#A): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(#A, #A): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(#A, #A, #A): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(){0§<kotlin.Any?>}[0]
+final fun <#A: kotlin/Any?> androidx.collection/orderedScatterSetOf(kotlin/Array<out #A>...): androidx.collection/OrderedScatterSet<#A> // androidx.collection/orderedScatterSetOf|orderedScatterSetOf(kotlin.Array<out|0:0>...){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/scatterSetOf(#A): androidx.collection/ScatterSet<#A> // androidx.collection/scatterSetOf|scatterSetOf(0:0){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/scatterSetOf(#A, #A): androidx.collection/ScatterSet<#A> // androidx.collection/scatterSetOf|scatterSetOf(0:0;0:0){0§<kotlin.Any?>}[0]
 final fun <#A: kotlin/Any?> androidx.collection/scatterSetOf(#A, #A, #A): androidx.collection/ScatterSet<#A> // androidx.collection/scatterSetOf|scatterSetOf(0:0;0:0;0:0){0§<kotlin.Any?>}[0]
@@ -1048,6 +1112,10 @@
 final inline fun androidx.collection/mutableLongListOf(): androidx.collection/MutableLongList // androidx.collection/mutableLongListOf|mutableLongListOf(){}[0]
 final inline fun androidx.collection/mutableLongListOf(kotlin/LongArray...): androidx.collection/MutableLongList // androidx.collection/mutableLongListOf|mutableLongListOf(kotlin.LongArray...){}[0]
 final inline fun androidx.collection/readRawMetadata(kotlin/LongArray, kotlin/Int): kotlin/Long // androidx.collection/readRawMetadata|readRawMetadata(kotlin.LongArray;kotlin.Int){}[0]
+final val androidx.collection/nextNode // androidx.collection/nextNode|<get-nextNode>@kotlin.Long(){}[0]
+    final inline fun (kotlin/Long).<get-nextNode>(): kotlin/Int // androidx.collection/nextNode.<get-nextNode>|<get-nextNode>@kotlin.Long(){}[0]
+final val androidx.collection/previousNode // androidx.collection/previousNode|<get-previousNode>@kotlin.Long(){}[0]
+    final inline fun (kotlin/Long).<get-previousNode>(): kotlin/Int // androidx.collection/previousNode.<get-previousNode>|<get-previousNode>@kotlin.Long(){}[0]
 final val androidx.collection/size // androidx.collection/size|@androidx.collection.LongSparseArray<0:0>{0§<kotlin.Any?>}size[0]
     final inline fun <#A1: kotlin/Any?> (androidx.collection/LongSparseArray<#A1>).<get-size>(): kotlin/Int // androidx.collection/size.<get-size>|<get-size>@androidx.collection.LongSparseArray<0:0>(){0§<kotlin.Any?>}[0]
 final val androidx.collection/size // androidx.collection/size|@androidx.collection.SparseArrayCompat<0:0>{0§<kotlin.Any?>}size[0]
@@ -1512,6 +1580,53 @@
     open fun hashCode(): kotlin/Int // androidx.collection/ObjectLongMap.hashCode|hashCode(){}[0]
     open fun toString(): kotlin/String // androidx.collection/ObjectLongMap.toString|toString(){}[0]
 }
+sealed class <#A: kotlin/Any?> androidx.collection/OrderedScatterSet { // androidx.collection/OrderedScatterSet|null[0]
+    constructor <init>() // androidx.collection/OrderedScatterSet.<init>|<init>(){}[0]
+    final fun any(): kotlin/Boolean // androidx.collection/OrderedScatterSet.any|any(){}[0]
+    final fun asSet(): kotlin.collections/Set<#A> // androidx.collection/OrderedScatterSet.asSet|asSet(){}[0]
+    final fun contains(#A): kotlin/Boolean // androidx.collection/OrderedScatterSet.contains|contains(1:0){}[0]
+    final fun count(): kotlin/Int // androidx.collection/OrderedScatterSet.count|count(){}[0]
+    final fun first(): #A // androidx.collection/OrderedScatterSet.first|first(){}[0]
+    final fun first(kotlin/Function1<#A, kotlin/Boolean>): #A // androidx.collection/OrderedScatterSet.first|first(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final fun isEmpty(): kotlin/Boolean // androidx.collection/OrderedScatterSet.isEmpty|isEmpty(){}[0]
+    final fun isNotEmpty(): kotlin/Boolean // androidx.collection/OrderedScatterSet.isNotEmpty|isNotEmpty(){}[0]
+    final fun joinToString(kotlin/CharSequence =..., kotlin/CharSequence =..., kotlin/CharSequence =..., kotlin/Int =..., kotlin/CharSequence =..., kotlin/Function1<#A, kotlin/CharSequence>? =...): kotlin/String // androidx.collection/OrderedScatterSet.joinToString|joinToString(kotlin.CharSequence;kotlin.CharSequence;kotlin.CharSequence;kotlin.Int;kotlin.CharSequence;kotlin.Function1<1:0,kotlin.CharSequence>?){}[0]
+    final fun last(): #A // androidx.collection/OrderedScatterSet.last|last(){}[0]
+    final fun last(kotlin/Function1<#A, kotlin/Boolean>): #A // androidx.collection/OrderedScatterSet.last|last(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final fun none(): kotlin/Boolean // androidx.collection/OrderedScatterSet.none|none(){}[0]
+    final inline fun all(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/OrderedScatterSet.all|all(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final inline fun any(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Boolean // androidx.collection/OrderedScatterSet.any|any(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final inline fun count(kotlin/Function1<#A, kotlin/Boolean>): kotlin/Int // androidx.collection/OrderedScatterSet.count|count(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final inline fun firstOrNull(kotlin/Function1<#A, kotlin/Boolean>): #A? // androidx.collection/OrderedScatterSet.firstOrNull|firstOrNull(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final inline fun forEach(kotlin/Function1<#A, kotlin/Unit>) // androidx.collection/OrderedScatterSet.forEach|forEach(kotlin.Function1<1:0,kotlin.Unit>){}[0]
+    final inline fun forEachReverse(kotlin/Function1<#A, kotlin/Unit>) // androidx.collection/OrderedScatterSet.forEachReverse|forEachReverse(kotlin.Function1<1:0,kotlin.Unit>){}[0]
+    final inline fun lastOrNull(kotlin/Function1<#A, kotlin/Boolean>): #A? // androidx.collection/OrderedScatterSet.lastOrNull|lastOrNull(kotlin.Function1<1:0,kotlin.Boolean>){}[0]
+    final inline fun toList(): kotlin.collections/List<#A> // androidx.collection/OrderedScatterSet.toList|toList(){}[0]
+    final inline fun unorderedForEach(kotlin/Function1<#A, kotlin/Unit>) // androidx.collection/OrderedScatterSet.unorderedForEach|unorderedForEach(kotlin.Function1<1:0,kotlin.Unit>){}[0]
+    final inline fun unorderedForEachIndex(kotlin/Function1<kotlin/Int, kotlin/Unit>) // androidx.collection/OrderedScatterSet.unorderedForEachIndex|unorderedForEachIndex(kotlin.Function1<kotlin.Int,kotlin.Unit>){}[0]
+    final val capacity // androidx.collection/OrderedScatterSet.capacity|{}capacity[0]
+        final fun <get-capacity>(): kotlin/Int // androidx.collection/OrderedScatterSet.capacity.<get-capacity>|<get-capacity>(){}[0]
+    final val size // androidx.collection/OrderedScatterSet.size|{}size[0]
+        final fun <get-size>(): kotlin/Int // androidx.collection/OrderedScatterSet.size.<get-size>|<get-size>(){}[0]
+    final var elements // androidx.collection/OrderedScatterSet.elements|{}elements[0]
+        final fun <get-elements>(): kotlin/Array<kotlin/Any?> // androidx.collection/OrderedScatterSet.elements.<get-elements>|<get-elements>(){}[0]
+        final fun <set-elements>(kotlin/Array<kotlin/Any?>) // androidx.collection/OrderedScatterSet.elements.<set-elements>|<set-elements>(kotlin.Array<kotlin.Any?>){}[0]
+    final var head // androidx.collection/OrderedScatterSet.head|{}head[0]
+        final fun <get-head>(): kotlin/Int // androidx.collection/OrderedScatterSet.head.<get-head>|<get-head>(){}[0]
+        final fun <set-head>(kotlin/Int) // androidx.collection/OrderedScatterSet.head.<set-head>|<set-head>(kotlin.Int){}[0]
+    final var metadata // androidx.collection/OrderedScatterSet.metadata|{}metadata[0]
+        final fun <get-metadata>(): kotlin/LongArray // androidx.collection/OrderedScatterSet.metadata.<get-metadata>|<get-metadata>(){}[0]
+        final fun <set-metadata>(kotlin/LongArray) // androidx.collection/OrderedScatterSet.metadata.<set-metadata>|<set-metadata>(kotlin.LongArray){}[0]
+    final var nodes // androidx.collection/OrderedScatterSet.nodes|{}nodes[0]
+        final fun <get-nodes>(): kotlin/LongArray // androidx.collection/OrderedScatterSet.nodes.<get-nodes>|<get-nodes>(){}[0]
+        final fun <set-nodes>(kotlin/LongArray) // androidx.collection/OrderedScatterSet.nodes.<set-nodes>|<set-nodes>(kotlin.LongArray){}[0]
+    final var tail // androidx.collection/OrderedScatterSet.tail|{}tail[0]
+        final fun <get-tail>(): kotlin/Int // androidx.collection/OrderedScatterSet.tail.<get-tail>|<get-tail>(){}[0]
+        final fun <set-tail>(kotlin/Int) // androidx.collection/OrderedScatterSet.tail.<set-tail>|<set-tail>(kotlin.Int){}[0]
+    open fun equals(kotlin/Any?): kotlin/Boolean // androidx.collection/OrderedScatterSet.equals|equals(kotlin.Any?){}[0]
+    open fun hashCode(): kotlin/Int // androidx.collection/OrderedScatterSet.hashCode|hashCode(){}[0]
+    open fun toString(): kotlin/String // androidx.collection/OrderedScatterSet.toString|toString(){}[0]
+}
 sealed class <#A: kotlin/Any?> androidx.collection/ScatterSet { // androidx.collection/ScatterSet|null[0]
     constructor <init>() // androidx.collection/ScatterSet.<init>|<init>(){}[0]
     final fun any(): kotlin/Boolean // androidx.collection/ScatterSet.any|any(){}[0]
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/OrderedScatterSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/OrderedScatterSet.kt
new file mode 100644
index 0000000..4942e42
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/OrderedScatterSet.kt
@@ -0,0 +1,1430 @@
+/*
+ * 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.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "KotlinRedundantDiagnosticSuppress",
+    "KotlinConstantConditions",
+    "PropertyName",
+    "ConstPropertyName",
+    "PrivatePropertyName",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.annotation.IntRange
+import androidx.collection.internal.EMPTY_OBJECTS
+import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.contract
+import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
+
+// Default empty set to avoid allocations
+private val EmptyOrderedScatterSet = MutableOrderedScatterSet<Any?>(0)
+
+/** Returns an empty, read-only [OrderedScatterSet]. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> emptyOrderedScatterSet(): OrderedScatterSet<E> =
+    EmptyOrderedScatterSet as OrderedScatterSet<E>
+
+/** Returns an empty, read-only [OrderedScatterSet]. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(): OrderedScatterSet<E> =
+    EmptyOrderedScatterSet as OrderedScatterSet<E>
+
+/** Returns a new read-only [OrderedScatterSet] with only [element1] in it. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(element1: E): OrderedScatterSet<E> =
+    mutableOrderedScatterSetOf(element1)
+
+/** Returns a new read-only [OrderedScatterSet] with only [element1] and [element2] in it. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(element1: E, element2: E): OrderedScatterSet<E> =
+    mutableOrderedScatterSetOf(element1, element2)
+
+/**
+ * Returns a new read-only [OrderedScatterSet] with only [element1], [element2], and [element3] in
+ * it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(element1: E, element2: E, element3: E): OrderedScatterSet<E> =
+    mutableOrderedScatterSetOf(element1, element2, element3)
+
+/** Returns a new read-only [OrderedScatterSet] with only [elements] in it. */
+@Suppress("UNCHECKED_CAST")
+public fun <E> orderedScatterSetOf(vararg elements: E): OrderedScatterSet<E> =
+    MutableOrderedScatterSet<E>(elements.size).apply { plusAssign(elements) }
+
+/** Returns a new [MutableOrderedScatterSet]. */
+public fun <E> mutableOrderedScatterSetOf(): MutableOrderedScatterSet<E> =
+    MutableOrderedScatterSet()
+
+/** Returns a new [MutableOrderedScatterSet] with only [element1] in it. */
+public fun <E> mutableOrderedScatterSetOf(element1: E): MutableOrderedScatterSet<E> =
+    MutableOrderedScatterSet<E>(1).apply { plusAssign(element1) }
+
+/** Returns a new [MutableOrderedScatterSet] with only [element1] and [element2] in it. */
+public fun <E> mutableOrderedScatterSetOf(element1: E, element2: E): MutableOrderedScatterSet<E> =
+    MutableOrderedScatterSet<E>(2).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+    }
+
+/**
+ * Returns a new [MutableOrderedScatterSet] with only [element1], [element2], and [element3] in it.
+ */
+public fun <E> mutableOrderedScatterSetOf(
+    element1: E,
+    element2: E,
+    element3: E
+): MutableOrderedScatterSet<E> =
+    MutableOrderedScatterSet<E>(3).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+        plusAssign(element3)
+    }
+
+/** Returns a new [MutableOrderedScatterSet] with the specified contents. */
+public fun <E> mutableOrderedScatterSetOf(vararg elements: E): MutableOrderedScatterSet<E> =
+    MutableOrderedScatterSet<E>(elements.size).apply { plusAssign(elements) }
+
+/**
+ * [OrderedScatterSet] is a container with a [Set]-like interface based on a flat hash table
+ * implementation that preserves the insertion order for iteration. The underlying implementation is
+ * designed to avoid all allocations on insertion, removal, retrieval, and iteration. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to accommodate newly
+ * added elements to the set.
+ *
+ * This implementation guarantees the order of the elements when iterating over them using [forEach]
+ * or the iterator provided by [asSet].
+ *
+ * Though [OrderedScatterSet] offers a read-only interface, it is always backed by a
+ * [MutableOrderedScatterSet]. Read operations alone are thread-safe. However, any mutations done
+ * through the backing [MutableScatterSet] while reading on another thread are not safe and the
+ * developer must protect the set from such changes during read operations.
+ *
+ * **Note**: when a [Set] is absolutely necessary, you can use the method [asSet] to create a thin
+ * wrapper around an [OrderedScatterSet]. Please refer to [asSet] for more details and caveats.
+ *
+ * @see [MutableOrderedScatterSet]
+ */
+public sealed class OrderedScatterSet<E> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` elements, including when
+    // the set is empty (see [EmptyGroup]).
+    @PublishedApi @JvmField internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi @JvmField internal var elements: Array<Any?> = EMPTY_OBJECTS
+
+    @PublishedApi @JvmField internal var nodes: LongArray = EmptyNodes
+    @PublishedApi @JvmField internal var head: Int = NodeInvalidLink
+    @PublishedApi @JvmField internal var tail: Int = NodeInvalidLink
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @JvmField internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of elements that can be stored in this set without requiring internal
+     * storage reallocation.
+     */
+    @get:IntRange(from = 0)
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @JvmField internal var _size: Int = 0
+
+    /** Returns the number of elements in this set. */
+    @get:IntRange(from = 0)
+    public val size: Int
+        get() = _size
+
+    /** Returns `true` if this set has at least one element. */
+    public fun any(): Boolean = _size != 0
+
+    /** Returns `true` if this set has no elements. */
+    public fun none(): Boolean = _size == 0
+
+    /** Indicates whether this set is empty. */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /** Returns `true` if this set is not empty. */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the first element in the collection.
+     *
+     * @throws NoSuchElementException if the collection is empty
+     */
+    public fun first(): E {
+        forEach {
+            return it
+        }
+        throw NoSuchElementException("The OrderedScatterSet is empty")
+    }
+
+    /**
+     * Returns the first element in insertion order in the collection for which [predicate] returns
+     * `true`.
+     *
+     * @param predicate called with each element until it returns `true`.
+     * @return The element for which [predicate] returns `true`.
+     * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+     *   collection is empty.
+     */
+    public fun first(predicate: (element: E) -> Boolean): E {
+        contract { callsInPlace(predicate) }
+        forEach { if (predicate(it)) return it }
+        throw NoSuchElementException("Could not find a match")
+    }
+
+    /**
+     * Returns the first element in insertion order in the collection for which [predicate] returns
+     * `true` or `null` if there are no elements that match [predicate].
+     *
+     * @param predicate called with each element until it returns `true`.
+     * @return The element for which [predicate] returns `true` or `null` if there are no elements
+     *   in the set or [predicate] returned `false` for every element in the set.
+     */
+    public inline fun firstOrNull(predicate: (element: E) -> Boolean): E? {
+        contract { callsInPlace(predicate) }
+        forEach { if (predicate(it)) return it }
+        return null
+    }
+
+    /**
+     * Returns the last element in the collection.
+     *
+     * @throws NoSuchElementException if the collection is empty
+     */
+    public fun last(): E {
+        forEachReverse {
+            return it
+        }
+        throw NoSuchElementException("The OrderedScatterSet is empty")
+    }
+
+    /**
+     * Returns the last element in insertion order in the collection for which [predicate] returns
+     * `true`.
+     *
+     * @param predicate called with each element until it returns `true`.
+     * @return The element for which [predicate] returns `true`.
+     * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+     *   collection is empty.
+     */
+    public fun last(predicate: (element: E) -> Boolean): E {
+        contract { callsInPlace(predicate) }
+        forEachReverse { if (predicate(it)) return it }
+        throw NoSuchElementException("Could not find a match")
+    }
+
+    /**
+     * Returns the first element in insertion order in the collection for which [predicate] returns
+     * `true` or `null` if there are no elements that match [predicate].
+     *
+     * @param predicate called with each element until it returns `true`.
+     * @return The element for which [predicate] returns `true` or `null` if there are no elements
+     *   in the set or [predicate] returned `false` for every element in the set.
+     */
+    public inline fun lastOrNull(predicate: (element: E) -> Boolean): E? {
+        contract { callsInPlace(predicate) }
+        forEachReverse { if (predicate(it)) return it }
+        return null
+    }
+
+    internal inline fun forEachIndex(block: (index: Int) -> Unit) {
+        contract { callsInPlace(block) }
+        val nodes = nodes
+
+        var candidate = tail
+        while (candidate != NodeInvalidLink) {
+            val previousNode = nodes[candidate].previousNode
+            @Suppress("UNCHECKED_CAST") block(candidate)
+            candidate = previousNode
+        }
+    }
+
+    /** Iterates over every element stored in this set by invoking the specified [block] lambda. */
+    @PublishedApi
+    internal inline fun unorderedForEachIndex(block: (index: Int) -> Unit) {
+        contract { callsInPlace(block) }
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 elements
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every element stored in this set by invoking the specified [block] lambda. The
+     * iteration order is the same as the insertion order. It is safe to remove the element passed
+     * to [block] during iteration.
+     *
+     * @param block called with each element in the set
+     */
+    public inline fun forEach(block: (element: E) -> Unit) {
+        contract { callsInPlace(block) }
+        val elements = elements
+        val nodes = nodes
+
+        var candidate = tail
+        while (candidate != NodeInvalidLink) {
+            val previousNode = nodes[candidate].previousNode
+            @Suppress("UNCHECKED_CAST") block(elements[candidate] as E)
+            candidate = previousNode
+        }
+    }
+
+    /**
+     * Iterates over every element stored in this set by invoking the specified [block] lambda. The
+     * iteration order is the reverse of the insertion order. It is safe to remove the element
+     * passed to [block] during iteration.
+     *
+     * @param block called with each element in the set
+     */
+    public inline fun forEachReverse(block: (element: E) -> Unit) {
+        contract { callsInPlace(block) }
+        val elements = elements
+        val nodes = nodes
+
+        var candidate = head
+        while (candidate != NodeInvalidLink) {
+            val nextNode = nodes[candidate].nextNode
+            @Suppress("UNCHECKED_CAST") block(elements[candidate] as E)
+            candidate = nextNode
+        }
+    }
+
+    /**
+     * Iterates over every element stored in this set by invoking the specified [block] lambda. The
+     * elements are iterated in arbitrary order.
+     *
+     * @param block called with each element in the set
+     */
+    @PublishedApi
+    internal inline fun unorderedForEach(block: (element: E) -> Unit) {
+        contract { callsInPlace(block) }
+        val elements = elements
+        unorderedForEachIndex { index -> @Suppress("UNCHECKED_CAST") block(elements[index] as E) }
+    }
+
+    /** Returns a new list containing this set's entries, in insertion order. */
+    public inline fun toList(): List<E> {
+        return ArrayList<E>(size).apply {
+            this@OrderedScatterSet.forEach { element -> add(element) }
+        }
+    }
+
+    /**
+     * Returns true if all elements match the given [predicate]. If there are no elements in the
+     * set, `true` is returned. The order in which the predicate is called over the elements is
+     * arbitrary.
+     *
+     * @param predicate called for elements in the set to determine if it returns return `true` for
+     *   all elements.
+     */
+    public inline fun all(predicate: (element: E) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        unorderedForEach { element -> if (!predicate(element)) return false }
+        return true
+    }
+
+    /**
+     * Returns true if at least one element matches the given [predicate]. The order in which the
+     * predicate is called over the elements is arbitrary.
+     *
+     * @param predicate called for elements in the set to determine if it returns `true` for any
+     *   elements.
+     */
+    public inline fun any(predicate: (element: E) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        unorderedForEach { element -> if (predicate(element)) return true }
+        return false
+    }
+
+    /** Returns the number of elements in this set. */
+    @IntRange(from = 0) public fun count(): Int = size
+
+    /**
+     * Returns the number of elements matching the given [predicate].The order in which the
+     * predicate is called over the elements is arbitrary.
+     *
+     * @param predicate Called for all elements in the set to count the number for which it returns
+     *   `true`.
+     */
+    @IntRange(from = 0)
+    public inline fun count(predicate: (element: E) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        var count = 0
+        unorderedForEach { element -> if (predicate(element)) count++ }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [element] is present in this hash set, false otherwise.
+     *
+     * @param element The element to look for in this set
+     */
+    public operator fun contains(element: E): Boolean = findElementIndex(element) >= 0
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before and
+     * [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used to
+     * generate the string. If the collection holds more than [limit] items, the string is
+     * terminated with [truncated].
+     *
+     * [transform] may be supplied to convert each element to a custom String.
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        transform: ((E) -> CharSequence)? = null
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@OrderedScatterSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            if (transform == null) {
+                append(element)
+            } else {
+                append(transform(element))
+            }
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Returns the hash code value for this set. The hash code of a set is based on the sum of the
+     * hash codes of the elements in the set, where the hash code of a null element is defined to be
+     * zero.
+     */
+    public override fun hashCode(): Int {
+        var hash = _capacity
+        hash = 31 * hash + _size
+
+        unorderedForEach { element ->
+            if (element != this) {
+                hash += element.hashCode()
+            }
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash set for equality. The two objects are
+     * considered equal if [other]:
+     * - Is a [OrderedScatterSet]
+     * - Has the same [size] as this set
+     * - Contains elements equal to this set's elements
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is OrderedScatterSet<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        @Suppress("UNCHECKED_CAST") val o = other as OrderedScatterSet<Any?>
+
+        unorderedForEach { element ->
+            if (element !in o) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this set. The set is denoted in the string by the `[]`.
+     * Each element is separated by `, `.
+     */
+    override fun toString(): String =
+        joinToString(prefix = "[", postfix = "]") { element ->
+            if (element === this) {
+                "(this)"
+            } else {
+                element.toString()
+            }
+        }
+
+    /**
+     * Scans the set to find the index in the backing arrays of the specified [element]. Returns -1
+     * if the element is not present.
+     */
+    internal inline fun findElementIndex(element: E): Int {
+        val hash = hash(element)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (elements[index] == element) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+
+    /**
+     * Wraps this [OrderedScatterSet] with a [Set] interface. The [Set] is backed by the
+     * [OrderedScatterSet], so changes to the [OrderedScatterSet] are reflected in the [Set]. If the
+     * [OrderedScatterSet] is modified while an iteration over the [Set] is in progress, the results
+     * of the iteration are undefined.
+     *
+     * **Note**: while this method is useful to use this [OrderedScatterSet] with APIs accepting
+     * [Set] interfaces, it is less efficient to do so than to use [OrderedScatterSet]'s APIs
+     * directly. While the [Set] implementation returned by this method tries to be as efficient as
+     * possible, the semantics of [Set] may require the allocation of temporary objects for access
+     * and iteration.
+     */
+    public fun asSet(): Set<E> = SetWrapper()
+
+    internal open inner class SetWrapper : Set<E> {
+        override val size: Int
+            get() = this@OrderedScatterSet._size
+
+        override fun containsAll(elements: Collection<E>): Boolean {
+            elements.forEach { element ->
+                if (!this@OrderedScatterSet.contains(element)) {
+                    return false
+                }
+            }
+            return true
+        }
+
+        @Suppress("KotlinOperator")
+        override fun contains(element: E): Boolean {
+            return this@OrderedScatterSet.contains(element)
+        }
+
+        override fun isEmpty(): Boolean = this@OrderedScatterSet.isEmpty()
+
+        override fun iterator(): Iterator<E> {
+            return iterator { this@OrderedScatterSet.forEach { element -> yield(element) } }
+        }
+    }
+}
+
+/**
+ * [MutableOrderedScatterSet] is a container with a [MutableSet]-like interface based on a flat hash
+ * table implementation that preserves the insertion order for iteration. The underlying
+ * implementation is designed to avoid all allocations on insertion, removal, retrieval, and
+ * iteration. Allocations may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added elements to the set.
+ *
+ * This implementation guarantees the order of the elements when iterating over them using [forEach]
+ * or the iterator provided by [asMutableSet].
+ *
+ * This implementation is not thread-safe: if multiple threads access this container concurrently,
+ * and one or more threads modify the structure of the set (insertion or removal for instance), the
+ * calling code must provide the appropriate synchronization. Concurrent reads are however safe.
+ *
+ * **Note**: when a [Set] is absolutely necessary, you can use the method [asSet] to create a thin
+ * wrapper around a [MutableOrderedScatterSet]. Please refer to [asSet] for more details and
+ * caveats.
+ *
+ * **Note**: when a [MutableSet] is absolutely necessary, you can use the method [asMutableSet] to
+ * create a thin wrapper around a [MutableOrderedScatterSet]. Please refer to [asMutableSet] for
+ * more details and caveats.
+ *
+ * @param initialCapacity The initial desired capacity for this container. the container will honor
+ *   this value by guaranteeing its internal structures can hold that many elements without
+ *   requiring any allocations. The initial capacity can be set to 0.
+ * @constructor Creates a new [MutableOrderedScatterSet]
+ * @see Set
+ */
+public class MutableOrderedScatterSet<E>(initialCapacity: Int = DefaultScatterCapacity) :
+    OrderedScatterSet<E>() {
+    // Number of elements we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        requirePrecondition(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity =
+            if (initialCapacity > 0) {
+                // Since we use longs for storage, our capacity is never < 7, enforce
+                // it here. We do have a special case for 0 to create small empty maps
+                maxOf(7, normalizeCapacity(initialCapacity))
+            } else {
+                0
+            }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        elements = if (newCapacity == 0) EMPTY_OBJECTS else arrayOfNulls(newCapacity)
+        nodes =
+            if (newCapacity == 0) EmptyNodes else LongArray(newCapacity).apply { fill(EmptyNode) }
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata =
+            if (capacity == 0) {
+                EmptyGroup
+            } else {
+                // Round up to the next multiple of 8 and find how many longs we need
+                val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+                LongArray(size).apply { fill(AllEmpty) }
+            }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Adds the specified element to the set.
+     *
+     * @param element The element to add to the set.
+     * @return `true` if the element has been added or `false` if the element is already contained
+     *   within the set.
+     */
+    public fun add(element: E): Boolean {
+        val oldSize = size
+        val index = findAbsoluteInsertIndex(element)
+        elements[index] = element
+        moveNodeToHead(index)
+        return size != oldSize
+    }
+
+    /**
+     * Adds the specified element to the set.
+     *
+     * @param element The element to add to the set.
+     */
+    public operator fun plusAssign(element: E) {
+        val index = findAbsoluteInsertIndex(element)
+        elements[index] = element
+        moveNodeToHead(index)
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     *
+     * @param elements An array of elements to add to the set.
+     * @return `true` if any of the specified elements were added to the collection, `false` if the
+     *   collection was not modified.
+     */
+    public fun addAll(@Suppress("ArrayReturn") elements: Array<out E>): Boolean {
+        val oldSize = size
+        plusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     *
+     * @param elements Iterable elements to add to the set.
+     * @return `true` if any of the specified elements were added to the collection, `false` if the
+     *   collection was not modified.
+     */
+    public fun addAll(elements: Iterable<E>): Boolean {
+        val oldSize = size
+        plusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     *
+     * @param elements The sequence of elements to add to the set.
+     * @return `true` if any of the specified elements were added to the collection, `false` if the
+     *   collection was not modified.
+     */
+    public fun addAll(elements: Sequence<E>): Boolean {
+        val oldSize = size
+        plusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
+     * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+     * @return `true` if any of the specified elements were added to the collection, `false` if the
+     *   collection was not modified.
+     */
+    public fun addAll(elements: OrderedScatterSet<E>): Boolean {
+        val oldSize = size
+        plusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
+     * @param elements A [ScatterSet] whose elements are to be added to the set
+     * @return `true` if any of the specified elements were added to the collection, `false` if the
+     *   collection was not modified.
+     */
+    public fun addAll(elements: ScatterSet<E>): Boolean {
+        val oldSize = size
+        plusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
+     * @param elements An [ObjectList] whose elements are to be added to the set
+     * @return `true` if any of the specified elements were added to the collection, `false` if the
+     *   collection was not modified.
+     */
+    public fun addAll(elements: ObjectList<E>): Boolean {
+        val oldSize = size
+        plusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     *
+     * @param elements An array of elements to add to the set.
+     */
+    public operator fun plusAssign(@Suppress("ArrayReturn") elements: Array<out E>) {
+        elements.forEach { element -> plusAssign(element) }
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     *
+     * @param elements Iterable elements to add to the set.
+     */
+    public operator fun plusAssign(elements: Iterable<E>) {
+        elements.forEach { element -> plusAssign(element) }
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     *
+     * @param elements The sequence of elements to add to the set.
+     */
+    public operator fun plusAssign(elements: Sequence<E>) {
+        elements.forEach { element -> plusAssign(element) }
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
+     * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+     */
+    public operator fun plusAssign(elements: OrderedScatterSet<E>) {
+        elements.forEach { element -> plusAssign(element) }
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
+     * @param elements A [ScatterSet] whose elements are to be added to the set
+     */
+    public operator fun plusAssign(elements: ScatterSet<E>) {
+        elements.forEach { element -> plusAssign(element) }
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
+     * @param elements An [ObjectList] whose elements are to be added to the set
+     */
+    public operator fun plusAssign(elements: ObjectList<E>) {
+        elements.forEach { element -> plusAssign(element) }
+    }
+
+    /**
+     * Removes the specified [element] from the set.
+     *
+     * @param element The element to be removed from the set.
+     * @return `true` if the [element] was present in the set, or `false` if it wasn't present
+     *   before removal.
+     */
+    public fun remove(element: E): Boolean {
+        val index = findElementIndex(element)
+        val exists = index >= 0
+        if (exists) {
+            removeElementAt(index)
+        }
+        return exists
+    }
+
+    /**
+     * Removes the specified [element] from the set if it is present.
+     *
+     * @param element The element to be removed from the set.
+     */
+    public operator fun minusAssign(element: E) {
+        val index = findElementIndex(element)
+        if (index >= 0) {
+            removeElementAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements An array of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(@Suppress("ArrayReturn") elements: Array<out E>): Boolean {
+        val oldSize = size
+        minusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A sequence of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(elements: Sequence<E>): Boolean {
+        val oldSize = size
+        minusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A Iterable of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(elements: Iterable<E>): Boolean {
+        val oldSize = size
+        minusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(elements: OrderedScatterSet<E>): Boolean {
+        val oldSize = size
+        minusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A [ScatterSet] whose elements should be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(elements: ScatterSet<E>): Boolean {
+        val oldSize = size
+        minusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements An [ObjectList] whose elements should be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(elements: ObjectList<E>): Boolean {
+        val oldSize = size
+        minusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements An array of elements to be removed from the set.
+     */
+    public operator fun minusAssign(@Suppress("ArrayReturn") elements: Array<out E>) {
+        elements.forEach { element -> minusAssign(element) }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A sequence of elements to be removed from the set.
+     */
+    public operator fun minusAssign(elements: Sequence<E>) {
+        elements.forEach { element -> minusAssign(element) }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A Iterable of elements to be removed from the set.
+     */
+    public operator fun minusAssign(elements: Iterable<E>) {
+        elements.forEach { element -> minusAssign(element) }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+     */
+    public operator fun minusAssign(elements: OrderedScatterSet<E>) {
+        elements.forEach { element -> minusAssign(element) }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements A [ScatterSet] whose elements should be removed from the set.
+     */
+    public operator fun minusAssign(elements: ScatterSet<E>) {
+        elements.forEach { element -> minusAssign(element) }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
+     * @param elements An [ObjectList] whose elements should be removed from the set.
+     */
+    public operator fun minusAssign(elements: ObjectList<E>) {
+        elements.forEach { element -> minusAssign(element) }
+    }
+
+    /** Removes any values for which the specified [predicate] returns true. */
+    public inline fun removeIf(predicate: (E) -> Boolean) {
+        val elements = elements
+        unorderedForEachIndex { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(elements[index] as E)) {
+                removeElementAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes all the entries in this set that are not contained in [elements].
+     *
+     * @param elements A collection of elements to preserve in this set.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(elements: Collection<E>): Boolean {
+        val internalElements = this.elements
+        val startSize = _size
+        unorderedForEachIndex { index ->
+            if (internalElements[index] !in elements) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
+    /**
+     * Removes all the entries in this set that are not contained in [elements].
+     *
+     * @params elements A set of elements to preserve in this set.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(elements: OrderedScatterSet<E>): Boolean {
+        val internalElements = this.elements
+        val startSize = _size
+        unorderedForEachIndex { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (internalElements[index] as E !in elements) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
+    /**
+     * Removes all the entries in this set that are not contained in [elements].
+     *
+     * @params elements A set of elements to preserve in this set.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(elements: ScatterSet<E>): Boolean {
+        val internalElements = this.elements
+        val startSize = _size
+        unorderedForEachIndex { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (internalElements[index] as E !in elements) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
+    /**
+     * Removes all the elements in this set for which the specified [predicate] is `true`. For each
+     * element in the set, the predicate is invoked with that element as the sole parameter.
+     *
+     * @param predicate Predicate invoked for each element in the set. When the predicate returns
+     *   `true`, the element is kept in the set, otherwise it is removed.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(predicate: (E) -> Boolean): Boolean {
+        val elements = elements
+        val startSize = _size
+        unorderedForEachIndex { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (!predicate(elements[index] as E)) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
+    @PublishedApi
+    internal fun removeElementAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the element as empty if there's a group
+        //       window around this element that was already empty
+        writeMetadata(metadata, _capacity, index, Deleted)
+        elements[index] = null
+
+        removeNode(index)
+    }
+
+    private inline fun moveNodeToHead(index: Int) {
+        nodes[index] = createLinkToNext(head)
+
+        if (head != NodeInvalidLink) {
+            nodes[head] = setLinkToPrevious(nodes[head], index)
+        }
+        head = index
+
+        if (tail == NodeInvalidLink) {
+            tail = index
+        }
+    }
+
+    private inline fun removeNode(index: Int) {
+        val nodes = nodes
+        val node = nodes[index]
+        val previousIndex = node.previousNode
+        val nextIndex = node.nextNode
+
+        if (previousIndex != NodeInvalidLink) {
+            nodes[previousIndex] = setLinkToNext(nodes[previousIndex], nextIndex)
+        } else {
+            head = nextIndex
+        }
+
+        if (nextIndex != NodeInvalidLink) {
+            nodes[nextIndex] = setLinkToPrevious(nodes[nextIndex], previousIndex)
+        } else {
+            tail = previousIndex
+        }
+
+        nodes[index] = EmptyNode
+    }
+
+    /** Removes all elements from this set. */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        elements.fill(null, 0, _capacity)
+        nodes.fill(EmptyNode)
+        head = NodeInvalidLink
+        tail = NodeInvalidLink
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the set to find the index at which we can store the given [element]. If the element
+     * already exists in the set, its index will be returned, otherwise the index of an empty slot
+     * will be returned. Calling this function may cause the internal storage to be reallocated if
+     * the set is full.
+     */
+    private fun findAbsoluteInsertIndex(element: E): Int {
+        val hash = hash(element)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (elements[index] == element) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(metadata, _capacity, index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the set in which we can store an element without
+     * resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableOrderedScatterSet]'s storage so it is sized appropriately to hold the
+     * current elements.
+     *
+     * Returns the number of empty elements removed from this set's storage. Returns 0 if no
+     * trimming is necessary or possible.
+     */
+    @IntRange(from = 0)
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Remove entries until the total size of the remaining entries is less than or equal to
+     * [maxSize]. Entries added last are removed first. Calling this method has no effect if
+     * [maxSize] is greater than [size].
+     */
+    public fun trimToSize(maxSize: Int) {
+        val nodes = nodes
+        var candidate = head
+        while (candidate != NodeInvalidLink && _size > maxSize && _size != 0) {
+            val nextNode = nodes[candidate].nextNode
+            removeElementAt(candidate)
+            candidate = nextNode
+        }
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to remove deleted elements
+     * from the set to avoid an expensive reallocation of the underlying storage. This "rehash in
+     * place" occurs when the current size is <= 25/32 of the set capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_map`.
+     */
+    internal fun adjustStorage() { // Internal to prevent inlining
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            dropDeletes()
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    // Internal to prevent inlining
+    internal fun dropDeletes() {
+        val metadata = metadata
+        // TODO: This shouldn't be required, but without it the compiler generates an extra
+        //       200+ aarch64 instructions to generate a NullPointerException.
+        @Suppress("SENSELESS_COMPARISON") if (metadata == null) return
+
+        val capacity = _capacity
+        val elements = elements
+        val nodes = nodes
+
+        val indexMapping = IntArray(capacity)
+
+        // Converts Sentinel and Deleted to Empty, and Full to Deleted
+        convertMetadataForCleanup(metadata, capacity)
+
+        var swapIndex = -1
+        var index = 0
+
+        // Drop deleted items and re-hashes surviving entries
+        while (index != capacity) {
+            var m = readRawMetadata(metadata, index)
+            // Formerly Deleted entry, we can use it as a swap spot
+            if (m == Empty) {
+                swapIndex = index
+                index++
+                continue
+            }
+
+            // Formerly Full entries are now marked Deleted. If we see an
+            // entry that's not marked Deleted, we can ignore it completely
+            if (m != Deleted) {
+                index++
+                continue
+            }
+
+            val hash = hash(elements[index])
+            val hash1 = h1(hash)
+            val targetIndex = findFirstAvailableSlot(hash1)
+
+            // Test if the current index (i) and the new index (targetIndex) fall
+            // within the same group based on the hash. If the group doesn't change,
+            // we don't move the entry
+            val probeOffset = hash1 and capacity
+            val newProbeIndex = ((targetIndex - probeOffset) and capacity) / GroupWidth
+            val oldProbeIndex = ((index - probeOffset) and capacity) / GroupWidth
+
+            if (newProbeIndex == oldProbeIndex) {
+                val hash2 = h2(hash)
+                writeRawMetadata(metadata, index, hash2.toLong())
+
+                indexMapping[index] = index
+
+                // Copies the metadata into the clone area
+                metadata[metadata.size - 1] = metadata[0]
+
+                index++
+                continue
+            }
+
+            m = readRawMetadata(metadata, targetIndex)
+            if (m == Empty) {
+                // The target is empty so we can transfer directly
+                val hash2 = h2(hash)
+                writeRawMetadata(metadata, targetIndex, hash2.toLong())
+                writeRawMetadata(metadata, index, Empty)
+
+                elements[targetIndex] = elements[index]
+                elements[index] = null
+
+                nodes[targetIndex] = nodes[index]
+                nodes[index] = EmptyNode
+
+                indexMapping[index] = targetIndex
+
+                swapIndex = index
+            } else /* m == Deleted */ {
+                // The target isn't empty so we use an empty slot denoted by
+                // swapIndex to perform the swap
+                val hash2 = h2(hash)
+                writeRawMetadata(metadata, targetIndex, hash2.toLong())
+
+                if (swapIndex == -1) {
+                    swapIndex = findEmptySlot(metadata, index + 1, capacity)
+                }
+
+                elements[swapIndex] = elements[targetIndex]
+                elements[targetIndex] = elements[index]
+                elements[index] = elements[swapIndex]
+
+                nodes[swapIndex] = nodes[targetIndex]
+                nodes[targetIndex] = nodes[index]
+                nodes[index] = nodes[swapIndex]
+
+                indexMapping[index] = targetIndex
+                indexMapping[targetIndex] = index
+
+                // Since we exchanged two slots we must repeat the process with
+                // element we just moved in the current location
+                index--
+            }
+
+            // Copies the metadata into the clone area
+            metadata[metadata.size - 1] = metadata[0]
+
+            index++
+        }
+
+        initializeGrowth()
+
+        fixupNodes(indexMapping)
+    }
+
+    // Internal to prevent inlining
+    internal fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousElements = elements
+        val previousNodes = nodes
+        val previousCapacity = _capacity
+
+        val indexMapping = IntArray(previousCapacity)
+
+        initializeStorage(newCapacity)
+
+        val newMetadata = metadata
+        val newElements = elements
+        val newNodes = nodes
+        val capacity = _capacity
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousElements[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(newMetadata, capacity, index, h2(hash).toLong())
+                newElements[index] = previousKey
+                newNodes[index] = previousNodes[i]
+
+                indexMapping[i] = index
+            }
+        }
+
+        fixupNodes(indexMapping)
+    }
+
+    private fun fixupNodes(mapping: IntArray) {
+        val nodes = nodes
+        for (i in nodes.indices) {
+            val node = nodes[i]
+            val previous = node.previousNode
+            val next = node.nextNode
+            nodes[i] = createLinks(node, previous, next, mapping)
+        }
+        if (head != NodeInvalidLink) head = mapping[head]
+        if (tail != NodeInvalidLink) tail = mapping[tail]
+    }
+
+    /**
+     * Wraps this [OrderedScatterSet] with a [MutableSet] interface. The [MutableSet] is backed by
+     * the [OrderedScatterSet], so changes to the [OrderedScatterSet] are reflected in the
+     * [MutableSet] and vice-versa. If the [OrderedScatterSet] is modified while an iteration over
+     * the [MutableSet] is in progress (and vice- versa), the results of the iteration are
+     * undefined.
+     *
+     * **Note**: while this method is useful to use this [MutableOrderedScatterSet] with APIs
+     * accepting [MutableSet] interfaces, it is less efficient to do so than to use
+     * [MutableOrderedScatterSet]'s APIs directly. While the [MutableSet] implementation returned by
+     * this method tries to be as efficient as possible, the semantics of [MutableSet] may require
+     * the allocation of temporary objects for access and iteration.
+     */
+    public fun asMutableSet(): MutableSet<E> = MutableSetWrapper()
+
+    private inner class MutableSetWrapper : SetWrapper(), MutableSet<E> {
+        override fun add(element: E): Boolean = this@MutableOrderedScatterSet.add(element)
+
+        override fun addAll(elements: Collection<E>): Boolean =
+            this@MutableOrderedScatterSet.addAll(elements)
+
+        override fun clear() {
+            this@MutableOrderedScatterSet.clear()
+        }
+
+        override fun iterator(): MutableIterator<E> =
+            object : MutableIterator<E> {
+                var current = -1
+                val iterator = iterator {
+                    this@MutableOrderedScatterSet.forEachIndex { index ->
+                        current = index
+                        @Suppress("UNCHECKED_CAST") yield(elements[index] as E)
+                    }
+                }
+
+                override fun hasNext(): Boolean = iterator.hasNext()
+
+                override fun next(): E = iterator.next()
+
+                override fun remove() {
+                    if (current != -1) {
+                        this@MutableOrderedScatterSet.removeElementAt(current)
+                        current = -1
+                    }
+                }
+            }
+
+        override fun remove(element: E): Boolean = this@MutableOrderedScatterSet.remove(element)
+
+        override fun retainAll(elements: Collection<E>): Boolean =
+            this@MutableOrderedScatterSet.retainAll(elements)
+
+        override fun removeAll(elements: Collection<E>): Boolean =
+            this@MutableOrderedScatterSet.removeAll(elements)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
index c3eb566..816650c 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
@@ -98,7 +98,8 @@
  * storage needs to grow to accommodate newly added elements to the set.
  *
  * This implementation makes no guarantee as to the order of the elements, nor does it make
- * guarantees that the order remains constant over time.
+ * guarantees that the order remains constant over time. If the order of the elements must be
+ * preserved, please refer to [OrderedScatterSet].
  *
  * Though [ScatterSet] offers a read-only interface, it is always backed by a [MutableScatterSet].
  * Read operations alone are thread-safe. However, any mutations done through the backing
@@ -220,15 +221,15 @@
     }
 
     /**
-     * Iterates over every element stored in this set by invoking the specified [block] lambda.
+     * Iterates over every element stored in this set by invoking the specified [block] lambda. It
+     * is safe to remove the element passed to [block] during iteration.
      *
      * @param block called with each element in the set
      */
     public inline fun forEach(block: (element: E) -> Unit) {
         contract { callsInPlace(block) }
-        val k = elements
-
-        forEachIndex { index -> @Suppress("UNCHECKED_CAST") block(k[index] as E) }
+        val elements = elements
+        forEachIndex { index -> @Suppress("UNCHECKED_CAST") block(elements[index] as E) }
     }
 
     /**
@@ -320,14 +321,19 @@
     }
 
     /**
-     * Returns the hash code value for this set. The hash code of a set is defined to be the sum of
-     * the hash codes of the elements in the set, where the hash code of a null element is defined
-     * to be zero
+     * Returns the hash code value for this set. The hash code of a set is based on the sum of the
+     * hash codes of the elements in the set, where the hash code of a null element is defined to be
+     * zero.
      */
     public override fun hashCode(): Int {
-        var hash = 0
+        var hash = _capacity
+        hash = 31 * hash + _size
 
-        forEach { element -> hash += element.hashCode() }
+        forEach { element ->
+            if (element != this) {
+                hash += element.hashCode()
+            }
+        }
 
         return hash
     }
@@ -492,7 +498,7 @@
             }
         _capacity = newCapacity
         initializeMetadata(newCapacity)
-        elements = arrayOfNulls(newCapacity)
+        elements = if (newCapacity == 0) EMPTY_OBJECTS else arrayOfNulls(newCapacity)
     }
 
     private fun initializeMetadata(capacity: Int) {
@@ -591,6 +597,19 @@
     /**
      * Adds all the elements in the [elements] set into this set.
      *
+     * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+     * @return `true` if any of the specified elements were added to the collection, `false` if the
+     *   collection was not modified.
+     */
+    public fun addAll(elements: OrderedScatterSet<E>): Boolean {
+        val oldSize = size
+        plusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
      * @param elements An [ObjectList] whose elements are to be added to the set
      * @return `true` if any of the specified elements were added to the collection, `false` if the
      *   collection was not modified.
@@ -640,6 +659,15 @@
     /**
      * Adds all the elements in the [elements] set into this set.
      *
+     * @param elements A [OrderedScatterSet] whose elements are to be added to the set
+     */
+    public operator fun plusAssign(elements: OrderedScatterSet<E>) {
+        elements.forEach { element -> plusAssign(element) }
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     *
      * @param elements An [ObjectList] whose elements are to be added to the set
      */
     public operator fun plusAssign(elements: ObjectList<E>) {
@@ -725,6 +753,18 @@
     /**
      * Removes the specified [elements] from the set, if present.
      *
+     * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(elements: OrderedScatterSet<E>): Boolean {
+        val oldSize = size
+        minusAssign(elements)
+        return oldSize != size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
      * @param elements An [ObjectList] whose elements should be removed from the set.
      * @return `true` if the set was changed or `false` if none of the elements were present.
      */
@@ -773,6 +813,15 @@
     /**
      * Removes the specified [elements] from the set, if present.
      *
+     * @param elements A [OrderedScatterSet] whose elements should be removed from the set.
+     */
+    public operator fun minusAssign(elements: OrderedScatterSet<E>) {
+        elements.forEach { element -> minusAssign(element) }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     *
      * @param elements An [ObjectList] whose elements should be removed from the set.
      */
     public operator fun minusAssign(elements: ObjectList<E>) {
@@ -790,6 +839,79 @@
         }
     }
 
+    /**
+     * Removes all the entries in this set that are not contained in [elements].
+     *
+     * @param elements A collection of elements to preserve in this set.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(elements: Collection<E>): Boolean {
+        val internalElements = this.elements
+        val startSize = _size
+        forEachIndex { index ->
+            if (internalElements[index] !in elements) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
+    /**
+     * Removes all the entries in this set that are not contained in [elements].
+     *
+     * @params elements A set of elements to preserve in this set.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(elements: ScatterSet<E>): Boolean {
+        val internalElements = this.elements
+        val startSize = _size
+        forEachIndex { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (internalElements[index] as E !in elements) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
+    /**
+     * Removes all the entries in this set that are not contained in [elements].
+     *
+     * @params elements A set of elements to preserve in this set.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(elements: OrderedScatterSet<E>): Boolean {
+        val internalElements = this.elements
+        val startSize = _size
+        forEachIndex { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (internalElements[index] as E !in elements) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
+    /**
+     * Removes all the elements in this set for which the specified [predicate] is `true`. For each
+     * element in the set, the predicate is invoked with that element as the sole parameter.
+     *
+     * @param predicate Predicate invoked for each element in the set. When the predicate returns
+     *   `true`, the element is kept in the set, otherwise it is removed.
+     * @return `true` if this set was modified, `false` otherwise.
+     */
+    public fun retainAll(predicate: (E) -> Boolean): Boolean {
+        val elements = elements
+        val startSize = _size
+        forEachIndex { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (!predicate(elements[index] as E)) {
+                removeElementAt(index)
+            }
+        }
+        return startSize != _size
+    }
+
     @PublishedApi
     internal fun removeElementAt(index: Int) {
         _size -= 1
@@ -1072,25 +1194,10 @@
 
         override fun remove(element: E): Boolean = this@MutableScatterSet.remove(element)
 
-        override fun retainAll(elements: Collection<E>): Boolean {
-            var changed = false
-            this@MutableScatterSet.forEachIndex { index ->
-                @Suppress("UNCHECKED_CAST")
-                val element = this@MutableScatterSet.elements[index] as E
-                if (element !in elements) {
-                    this@MutableScatterSet.removeElementAt(index)
-                    changed = true
-                }
-            }
-            return changed
-        }
+        override fun retainAll(elements: Collection<E>): Boolean =
+            this@MutableScatterSet.retainAll(elements)
 
-        override fun removeAll(elements: Collection<E>): Boolean {
-            val oldSize = this@MutableScatterSet.size
-            for (element in elements) {
-                this@MutableScatterSet -= element
-            }
-            return oldSize != this@MutableScatterSet.size
-        }
+        override fun removeAll(elements: Collection<E>): Boolean =
+            this@MutableScatterSet.removeAll(elements)
     }
 }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt
index 39e2571..79e0f05 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/SieveCache.kt
@@ -26,16 +26,16 @@
 
 private const val MaxSize = Int.MAX_VALUE.toLong() - 1
 
-private const val NodeInvalidLink = 0x7fff_ffff
-private const val NodeLinkMask = 0x7fff_ffffL
-private const val NodeLinksMask = 0x3fffffff_ffffffffL
-private const val NodeVisitedBit = 0x40000000_00000000L
-private const val NodeMetaMask = -0x40000000_00000000L // 0xc0000000_00000000UL.toLong()
-private const val NodeMetaAndNextMask = -0x3fffffff_80000001L // 0xc0000000_7fffffffUL.toLong()
-private const val NodeMetaAndPreviousMask = -0x00000000_80000000L // 0xffffffff_80000000UL.toLong()
+@PublishedApi internal const val NodeInvalidLink: Int = 0x7fff_ffff
+@PublishedApi internal const val NodeLinkMask: Long = 0x7fff_ffffL
+internal const val NodeLinksMask = 0x3fffffff_ffffffffL
+internal const val NodeVisitedBit = 0x40000000_00000000L
+internal const val NodeMetaMask = -0x40000000_00000000L // 0xc0000000_00000000UL.toLong()
+internal const val NodeMetaAndNextMask = -0x3fffffff_80000001L // 0xc0000000_7fffffffUL.toLong()
+internal const val NodeMetaAndPreviousMask = -0x00000000_80000000L // 0xffffffff_80000000UL.toLong()
 
-private const val EmptyNode = 0x3fffffff_ffffffffL
-private val EmptyNodes = LongArray(0)
+internal const val EmptyNode = 0x3fffffff_ffffffffL
+internal val EmptyNodes = LongArray(0)
 
 /**
  * [SieveCache] is an in-memory cache that holds strong references to a limited number of values
@@ -267,18 +267,20 @@
         values[index] = value
         keys[index] = key
 
-        moveNodeToHead(index)
-
         _size += sizeOf(key, value)
 
         if (previousValue != null) {
             _size -= sizeOf(key, previousValue)
             onEntryRemoved(key, previousValue, value, false)
+            trimToSize(_maxSize)
+            return previousValue
         }
 
         // TODO: We should trim to size before doing the insertion. The insertion might cause
         //       the underlying storage to resize unnecessarily.
         trimToSize(_maxSize)
+        // We need to make sure we update the linked list after eviction
+        moveNodeToHead(index)
 
         return previousValue
     }
@@ -1015,7 +1017,7 @@
     }
 }
 
-private inline fun createLinks(node: Long, previous: Int, next: Int, mapping: IntArray): Long {
+internal inline fun createLinks(node: Long, previous: Int, next: Int, mapping: IntArray): Long {
     return (node and NodeMetaMask) or
         (if (previous == NodeInvalidLink) NodeInvalidLink else mapping[previous]).toLong() shl
         31 or
@@ -1023,20 +1025,24 @@
 }
 
 // set meta to 0 (visited = false) and previous to NodeInvalidLink
-private inline fun createLinkToNext(next: Int) =
+internal inline fun createLinkToNext(next: Int) =
     0x3fffffff_80000000L or (next.toLong() and NodeLinkMask)
 
-private inline fun setLinkToPrevious(node: Long, previous: Int) =
+internal inline fun setLinkToPrevious(node: Long, previous: Int) =
     (node and NodeMetaAndNextMask) or ((previous.toLong() and NodeLinkMask) shl 31)
 
-private inline fun setLinkToNext(node: Long, next: Int) =
+internal inline fun setLinkToNext(node: Long, next: Int) =
     (node and NodeMetaAndPreviousMask) or (next.toLong() and NodeLinkMask)
 
-private inline fun clearVisitedBit(node: Long) = node and NodeLinksMask
+internal inline fun clearVisitedBit(node: Long) = node and NodeLinksMask
 
-private inline val Long.previousNode: Int
+@PublishedApi
+internal inline val Long.previousNode: Int
     get() = ((this shr 31) and NodeLinkMask).toInt()
-private inline val Long.nextNode: Int
+
+@PublishedApi
+internal inline val Long.nextNode: Int
     get() = (this and NodeLinkMask).toInt()
-private inline val Long.visited: Int
+
+internal inline val Long.visited: Int
     get() = ((this shr 62) and 0x1).toInt()
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/OrderedScatterSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/OrderedScatterSetTest.kt
new file mode 100644
index 0000000..86a6d13
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/OrderedScatterSetTest.kt
@@ -0,0 +1,1270 @@
+/*
+ * 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.collection
+
+import kotlin.test.Test
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+class OrderedScatterSetTest {
+    @Test
+    fun emptyScatterSetConstructor() {
+        val set = MutableOrderedScatterSet<String>()
+        assertEquals(7, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun immutableEmptyScatterSet() {
+        val set = emptyOrderedScatterSet<String>()
+        assertEquals(0, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun zeroCapacityScatterSet() {
+        val set = MutableOrderedScatterSet<String>(0)
+        assertEquals(0, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun emptyScatterSetWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val set = MutableOrderedScatterSet<String>(1800)
+        assertEquals(4095, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun mutableScatterSetBuilder() {
+        val empty = mutableOrderedScatterSetOf<String>()
+        assertEquals(0, empty.size)
+
+        val withElements = mutableOrderedScatterSetOf("Hello", "World")
+        assertEquals(2, withElements.size)
+        assertTrue("Hello" in withElements)
+        assertTrue("World" in withElements)
+    }
+
+    @Test
+    fun addToScatterSet() {
+        val set = MutableOrderedScatterSet<String>()
+        set += "Hello"
+        assertTrue(set.add("World"))
+
+        assertEquals(2, set.size)
+        val elements = Array(2) { "" }
+        var index = 0
+        set.forEach { element -> elements[index++] = element }
+        elements.sort()
+        assertEquals("Hello", elements[0])
+        assertEquals("World", elements[1])
+    }
+
+    @Test
+    fun addToSizedScatterSet() {
+        val set = MutableOrderedScatterSet<String>(12)
+        set += "Hello"
+
+        assertEquals(1, set.size)
+        assertEquals("Hello", set.first())
+    }
+
+    @Test
+    fun addExistingElement() {
+        val set = MutableOrderedScatterSet<String>(12)
+        set += "Hello"
+        assertFalse(set.add("Hello"))
+        set += "Hello"
+
+        assertEquals(1, set.size)
+        assertEquals("Hello", set.first())
+    }
+
+    @Test
+    fun addAllArray() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        assertFalse(set.addAll(arrayOf("Hello")))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(arrayOf("Hello", "World")))
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun addAllIterable() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        assertFalse(set.addAll(listOf("Hello")))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(listOf("Hello", "World")))
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun addAllSequence() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        assertFalse(set.addAll(listOf("Hello").asSequence()))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(listOf("Hello", "World").asSequence()))
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun addAllScatterSet() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        assertFalse(set.addAll(mutableOrderedScatterSetOf("Hello")))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(mutableOrderedScatterSetOf("Hello", "World")))
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun addAllObjectList() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        assertFalse(set.addAll(objectListOf("Hello")))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(objectListOf("Hello", "World")))
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun plusAssignArray() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        set += arrayOf("Hello")
+        assertEquals(1, set.size)
+        set += arrayOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun plusAssignIterable() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        set += listOf("Hello")
+        assertEquals(1, set.size)
+        set += listOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun plusAssignSequence() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        set += listOf("Hello").asSequence()
+        assertEquals(1, set.size)
+        set += listOf("Hello", "World").asSequence()
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun plusAssignScatterSet() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        set += mutableOrderedScatterSetOf("Hello")
+        assertEquals(1, set.size)
+        set += mutableOrderedScatterSetOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun plusAssignObjectList() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        set += objectListOf("Hello")
+        assertEquals(1, set.size)
+        set += objectListOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
+    fun nullElement() {
+        val set = MutableOrderedScatterSet<String?>()
+        set += null
+
+        assertEquals(1, set.size)
+        assertNull(set.first())
+    }
+
+    @Test
+    fun firstWithValue() {
+        val set = MutableOrderedScatterSet<String>()
+        set += "Hello"
+        set += "World"
+        var element: String? = null
+        var otherElement: String? = null
+        set.forEach { if (element == null) element = it else otherElement = it }
+        assertEquals(element, set.first())
+        set -= element!!
+        assertEquals(otherElement, set.first())
+    }
+
+    @Test
+    fun firstEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableOrderedScatterSet<String>()
+            set.first()
+        }
+    }
+
+    @Test
+    fun firstMatching() {
+        val set = MutableOrderedScatterSet<String>()
+        set += "Hello"
+        set += "Hallo"
+        set += "World"
+        set += "Welt"
+        assertEquals("Hello", set.first { it.contains('H') })
+        assertEquals("World", set.first { it.contains('W') })
+    }
+
+    @Test
+    fun firstMatchingEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableOrderedScatterSet<String>()
+            set.first { it.contains('H') }
+        }
+    }
+
+    @Test
+    fun firstMatchingNoMatch() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableOrderedScatterSet<String>()
+            set += "Hello"
+            set += "World"
+            set.first { it.startsWith("Q") }
+        }
+    }
+
+    @Test
+    fun firstOrNull() {
+        val set = MutableOrderedScatterSet<String>()
+        assertNull(set.firstOrNull { it.startsWith('H') })
+        set += "Hello"
+        set += "Hallo"
+        set += "World"
+        set += "Welt"
+        var element: String? = null
+        set.forEach { if (element == null) element = it }
+        assertEquals(element, set.firstOrNull { it.contains('l') })
+        assertEquals("Hello", set.firstOrNull { it.contains('H') })
+        assertEquals("World", set.firstOrNull { it.contains('W') })
+        assertNull(set.firstOrNull { it.startsWith('Q') })
+    }
+
+    @Test
+    fun lastWithValue() {
+        val set = MutableOrderedScatterSet<String>()
+        set += "Hello"
+        set += "World"
+        var element: String? = null
+        var otherElement: String? = null
+        set.forEach { if (otherElement == null) otherElement = it else element = it }
+        assertEquals(element, set.last())
+        set -= element!!
+        assertEquals(otherElement, set.last())
+    }
+
+    @Test
+    fun lastEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableOrderedScatterSet<String>()
+            set.last()
+        }
+    }
+
+    @Test
+    fun lastMatching() {
+        val set = MutableOrderedScatterSet<String>()
+        set += "Hello"
+        set += "Hallo"
+        set += "World"
+        set += "Welt"
+        assertEquals("Hallo", set.last { it.contains('H') })
+        assertEquals("Welt", set.last { it.contains('W') })
+    }
+
+    @Test
+    fun lastMatchingEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableOrderedScatterSet<String>()
+            set.last { it.contains('H') }
+        }
+    }
+
+    @Test
+    fun lastMatchingNoMatch() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableOrderedScatterSet<String>()
+            set += "Hello"
+            set += "World"
+            set.last { it.startsWith("Q") }
+        }
+    }
+
+    @Test
+    fun lastOrNull() {
+        val set = MutableOrderedScatterSet<String>()
+        assertNull(set.lastOrNull { it.startsWith('H') })
+        set += "Hello"
+        set += "Hallo"
+        set += "World"
+        set += "Welt"
+        var element: String? = null
+        set.forEachReverse { if (element == null) element = it }
+        assertEquals(element, set.lastOrNull { it.contains('l') })
+        assertEquals("Hallo", set.lastOrNull { it.contains('H') })
+        assertEquals("Welt", set.lastOrNull { it.contains('W') })
+        assertNull(set.lastOrNull { it.startsWith('Q') })
+    }
+
+    @Test
+    fun remove() {
+        val set = MutableOrderedScatterSet<String?>()
+        assertFalse(set.remove("Hello"))
+
+        set += "Hello"
+        assertTrue(set.remove("Hello"))
+        assertEquals(0, set.size)
+
+        set += "Hello"
+        set -= "Hello"
+        assertEquals(0, set.size)
+
+        set += null
+        assertTrue(set.remove(null))
+        assertEquals(0, set.size)
+
+        set += null
+        set -= null
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val set = MutableOrderedScatterSet<String?>(6)
+        set += "Hello"
+        set += "Bonjour"
+        set += "Hallo"
+        set += "Konnichiwa"
+        set += "Ciao"
+        set += "Annyeong"
+
+        // Removing all the entries will mark the medata as deleted
+        set.remove("Hello")
+        set.remove("Bonjour")
+        set.remove("Hallo")
+        set.remove("Konnichiwa")
+        set.remove("Ciao")
+        set.remove("Annyeong")
+
+        assertEquals(0, set.size)
+
+        val capacity = set.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        set += "Hello"
+
+        assertEquals(1, set.size)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun removeAllArray() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        assertFalse(set.removeAll(arrayOf("Hola", "Bonjour")))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(arrayOf("Hola", "Hello", "Bonjour")))
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun removeAllIterable() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        assertFalse(set.removeAll(listOf("Hola", "Bonjour")))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(listOf("Hola", "Hello", "Bonjour")))
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun removeAllSequence() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        assertFalse(set.removeAll(sequenceOf("Hola", "Bonjour")))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(sequenceOf("Hola", "Hello", "Bonjour")))
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun removeAllScatterSet() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        assertFalse(set.removeAll(mutableOrderedScatterSetOf("Hola", "Bonjour")))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(mutableOrderedScatterSetOf("Hola", "Hello", "Bonjour")))
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun removeAllObjectList() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        assertFalse(set.removeAll(objectListOf("Hola", "Bonjour")))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(objectListOf("Hola", "Hello", "Bonjour")))
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun removeDoesNotCauseGrowthOnInsert() {
+        val set = MutableOrderedScatterSet<String>(10) // Must be > GroupWidth (8)
+        assertEquals(15, set.capacity)
+
+        set += "Hello"
+        set += "Bonjour"
+        set += "Hallo"
+        set += "Konnichiwa"
+        set += "Ciao"
+        set += "Annyeong"
+
+        // Reach the upper limit of what we can store without increasing the map size
+        for (i in 0..7) {
+            set += i.toString()
+        }
+
+        // Delete a few items
+        for (i in 0..5) {
+            set.remove(i.toString())
+        }
+
+        // Inserting a new item shouldn't cause growth, but the deleted markers to be purged
+        set += "Foo"
+        assertEquals(15, set.capacity)
+
+        assertTrue(set.contains("Foo"))
+    }
+
+    @Test
+    fun minusAssignArray() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        set -= arrayOf("Hola", "Bonjour")
+        assertEquals(2, set.size)
+        set -= arrayOf("Hola", "Hello", "Bonjour")
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun minusAssignIterable() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        set -= listOf("Hola", "Bonjour")
+        assertEquals(2, set.size)
+        set -= listOf("Hola", "Hello", "Bonjour")
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun minusAssignSequence() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        set -= sequenceOf("Hola", "Bonjour")
+        assertEquals(2, set.size)
+        set -= sequenceOf("Hola", "Hello", "Bonjour")
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun minusAssignScatterSet() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        set -= mutableOrderedScatterSetOf("Hola", "Bonjour")
+        assertEquals(2, set.size)
+        set -= mutableOrderedScatterSetOf("Hola", "Hello", "Bonjour")
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun minusAssignObjectList() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        set -= objectListOf("Hola", "Bonjour")
+        assertEquals(2, set.size)
+        set -= objectListOf("Hola", "Hello", "Bonjour")
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val set = MutableOrderedScatterSet<String>()
+
+        for (i in 0 until 1700) {
+            set += i.toString()
+        }
+
+        assertEquals(1700, set.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 8..24) {
+            val set = MutableOrderedScatterSet<Int>()
+
+            for (j in 0 until i) {
+                set += j
+            }
+
+            val elements = Array(i) { -1 }
+            var index = 0
+            set.forEach { element ->
+                println(element)
+                elements[index++] = element
+            }
+
+            index = 0
+            elements.forEach { element ->
+                assertEquals(element, index)
+                index++
+            }
+        }
+    }
+
+    @Test
+    fun forEachIsOrdered() {
+        val expected =
+            mutableListOf(
+                "Hello",
+                "World",
+                "Hola",
+                "Mundo",
+                "Bonjour",
+                "Monde",
+                "Hallo",
+            )
+        val set = mutableOrderedScatterSetOf<String>()
+
+        set += expected
+        assertContentEquals(expected, set.toList())
+
+        // Removing an item should preserve order
+        expected -= "Hallo"
+        set -= "Hallo"
+        assertContentEquals(expected, set.toList())
+
+        // Adding an item should preserve order
+        expected += "Barev"
+        set += "Barev"
+        assertContentEquals(expected, set.toList())
+
+        // Causing a resize should preserve order
+        val extra = listOf("Welt", "Konnichiwa", "Sekai", "Ciao", "Mondo", "Annyeong", "Sesang")
+        expected += extra
+        set += extra
+        assertContentEquals(expected, set.toList())
+
+        // Trimming preserves order
+        set.trim()
+        assertContentEquals(expected, set.toList())
+
+        // Trimming to a new size preserves order
+        expected.clear()
+        expected += listOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde", "Barev")
+        set.trimToSize(7)
+        assertContentEquals(expected, set.toList())
+
+        set.clear()
+        assertContentEquals(listOf(), set.toList())
+    }
+
+    @Test
+    fun iteratorIsOrdered() {
+        val expected =
+            mutableListOf(
+                "Hello",
+                "World",
+                "Hola",
+                "Mundo",
+                "Bonjour",
+                "Monde",
+                "Hallo",
+            )
+        val set = mutableOrderedScatterSetOf<String>()
+
+        set += expected
+        assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+        assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+        // Removing an item should preserve order
+        expected -= "Hallo"
+        set -= "Hallo"
+        assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+        assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+        // Adding an item should preserve order
+        expected += "Barev"
+        set += "Barev"
+        assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+        assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+        // Causing a resize should preserve order
+        val extra = listOf("Welt", "Konnichiwa", "Sekai", "Ciao", "Mondo", "Annyeong", "Sesang")
+        expected += extra
+        set += extra
+        assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+        assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+        // Trimming preserves order
+        set.trim()
+        assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+        assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+        // Trimming to a new size preserves order
+        expected.clear()
+        expected += listOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde", "Barev")
+        set.trimToSize(7)
+        assertContentEquals(expected, set.asMutableSet().iterator().asSequence().toList())
+        assertContentEquals(expected, set.asSet().iterator().asSequence().toList())
+
+        set.clear()
+        assertContentEquals(listOf(), set.asMutableSet().iterator().asSequence().toList())
+        assertContentEquals(listOf(), set.asSet().iterator().asSequence().toList())
+    }
+
+    @Test
+    fun trimToSize() {
+        val set =
+            mutableOrderedScatterSetOf(
+                "Hello",
+                "World",
+                "Hola",
+                "Mundo",
+                "Bonjour",
+                "Monde",
+                "Hallo"
+            )
+
+        val size = set.size
+        set.trimToSize(size)
+        assertEquals(size, set.size)
+
+        set.trimToSize(4)
+        assertEquals(4, set.size)
+
+        set.trimToSize(17)
+        assertEquals(4, set.size)
+
+        set.trimToSize(0)
+        assertEquals(0, set.size)
+
+        set += listOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde", "Hallo")
+        set.trimToSize(-1)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun clear() {
+        val set = MutableOrderedScatterSet<String>()
+
+        for (i in 0 until 32) {
+            set += i.toString()
+        }
+
+        val capacity = set.capacity
+        set.clear()
+
+        assertEquals(0, set.size)
+        assertEquals(capacity, set.capacity)
+
+        set.forEach { fail() }
+    }
+
+    @Test
+    fun string() {
+        val set = MutableOrderedScatterSet<String?>()
+        assertEquals("[]", set.toString())
+
+        set += "Hello"
+        set += "Bonjour"
+        assertTrue("[Hello, Bonjour]" == set.toString() || "[Bonjour, Hello]" == set.toString())
+
+        set.clear()
+        set += null
+        assertEquals("[null]", set.toString())
+
+        set.clear()
+
+        val selfAsElement = MutableOrderedScatterSet<Any>()
+        selfAsElement.add(selfAsElement)
+        assertEquals("[(this)]", selfAsElement.toString())
+    }
+
+    @Test
+    fun joinToString() {
+        val set = scatterSetOf(1, 2, 3, 4, 5)
+        val order = IntArray(5)
+        var index = 0
+        set.forEach { element -> order[index++] = element }
+        assertEquals(
+            "${order[0]}, ${order[1]}, ${order[2]}, ${order[3]}, ${order[4]}",
+            set.joinToString()
+        )
+        assertEquals(
+            "x${order[0]}, ${order[1]}, ${order[2]}...",
+            set.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0]}-${order[1]}-${order[2]}-${order[3]}-${order[4]}<",
+            set.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            set.joinToString(limit = 3) { names[it] }
+        )
+    }
+
+    @Test
+    fun hashCodeAddValues() {
+        val set = mutableOrderedScatterSetOf<String?>()
+        assertEquals(217, set.hashCode())
+        set += null
+        assertEquals(218, set.hashCode())
+        set += "Hello"
+        val h1 = set.hashCode()
+        set += "World"
+        assertNotEquals(h1, set.hashCode())
+    }
+
+    @Test
+    fun equals() {
+        val set = MutableOrderedScatterSet<String?>()
+        set += "Hello"
+        set += null
+        set += "Bonjour"
+
+        assertFalse(set.equals(null))
+        assertEquals(set, set)
+
+        val set2 = MutableOrderedScatterSet<String?>()
+        set2 += "Bonjour"
+        set2 += null
+
+        assertNotEquals(set, set2)
+
+        set2 += "Hello"
+        assertEquals(set, set2)
+    }
+
+    @Test
+    fun contains() {
+        val set = MutableOrderedScatterSet<String?>()
+        set += "Hello"
+        set += null
+        set += "Bonjour"
+
+        assertTrue(set.contains("Hello"))
+        assertTrue(set.contains(null))
+        assertFalse(set.contains("World"))
+    }
+
+    @Test
+    fun empty() {
+        val set = MutableOrderedScatterSet<String?>()
+        assertTrue(set.isEmpty())
+        assertFalse(set.isNotEmpty())
+        assertTrue(set.none())
+        assertFalse(set.any())
+
+        set += "Hello"
+
+        assertFalse(set.isEmpty())
+        assertTrue(set.isNotEmpty())
+        assertTrue(set.any())
+        assertFalse(set.none())
+    }
+
+    @Test
+    fun count() {
+        val set = MutableOrderedScatterSet<String>()
+        assertEquals(0, set.count())
+
+        set += "Hello"
+        assertEquals(1, set.count())
+
+        set += "Bonjour"
+        set += "Hallo"
+        set += "Konnichiwa"
+        set += "Ciao"
+        set += "Annyeong"
+
+        assertEquals(2, set.count { it.startsWith("H") })
+        assertEquals(0, set.count { it.startsWith("W") })
+    }
+
+    @Test
+    fun any() {
+        val set = MutableOrderedScatterSet<String>()
+        set += "Hello"
+        set += "Bonjour"
+        set += "Hallo"
+        set += "Konnichiwa"
+        set += "Ciao"
+        set += "Annyeong"
+
+        assertTrue(set.any { it.startsWith("K") })
+        assertFalse(set.any { it.startsWith("W") })
+    }
+
+    @Test
+    fun all() {
+        val set = MutableOrderedScatterSet<String>()
+        set += "Hello"
+        set += "Bonjour"
+        set += "Hallo"
+        set += "Konnichiwa"
+        set += "Ciao"
+        set += "Annyeong"
+
+        assertTrue(set.all { it.length >= 4 })
+        assertFalse(set.all { it.length >= 5 })
+    }
+
+    @Test
+    fun asSet() {
+        val scatterSet = mutableOrderedScatterSetOf("Hello", "World")
+        val set = scatterSet.asSet()
+        assertEquals(2, set.size)
+        assertTrue(set.containsAll(listOf("Hello", "World")))
+        assertFalse(set.containsAll(listOf("Hola", "World")))
+        assertTrue(set.contains("Hello"))
+        assertTrue(set.contains("World"))
+        assertFalse(set.contains("Hola"))
+        assertFalse(set.isEmpty())
+        val elements = Array(2) { "" }
+        set.forEachIndexed { index, element -> elements[index] = element }
+        elements.sort()
+        assertEquals("Hello", elements[0])
+        assertEquals("World", elements[1])
+    }
+
+    @Test
+    fun asMutableSet() {
+        val scatterSet = mutableOrderedScatterSetOf("Hello", "World")
+        val set = scatterSet.asMutableSet()
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertFalse("Bonjour" in set)
+
+        assertFalse(set.add("Hello"))
+        assertEquals(2, set.size)
+
+        assertTrue(set.add("Hola"))
+        assertEquals(3, set.size)
+        assertTrue("Hola" in set)
+
+        assertFalse(set.addAll(listOf("World", "Hello")))
+        assertEquals(3, set.size)
+
+        assertTrue(set.addAll(listOf("Hello", "Mundo")))
+        assertEquals(4, set.size)
+        assertTrue("Mundo" in set)
+
+        assertFalse(set.remove("Bonjour"))
+        assertEquals(4, set.size)
+
+        assertTrue(set.remove("World"))
+        assertEquals(3, set.size)
+        assertFalse("World" in set)
+
+        assertFalse(set.retainAll(listOf("Hola", "Hello", "Mundo")))
+        assertEquals(3, set.size)
+
+        assertTrue(set.retainAll(listOf("Hola", "Hello")))
+        assertEquals(2, set.size)
+        assertFalse("Mundo" in set)
+
+        assertFalse(set.removeAll(listOf("Bonjour", "Mundo")))
+        assertEquals(2, set.size)
+
+        assertTrue(set.removeAll(listOf("Hello", "Mundo")))
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+
+        set.clear()
+        assertEquals(0, set.size)
+        assertFalse("Hola" in set)
+    }
+
+    @Test
+    fun trim() {
+        val set = mutableOrderedScatterSetOf("Hello", "World", "Hola", "Mundo", "Bonjour", "Monde")
+        val capacity = set.capacity
+        assertEquals(0, set.trim())
+        set.clear()
+        assertEquals(capacity, set.trim())
+        assertEquals(0, set.capacity)
+        set.addAll(
+            arrayOf(
+                "Hello",
+                "World",
+                "Hola",
+                "Mundo",
+                "Bonjour",
+                "Monde",
+                "Hallo",
+                "Welt",
+                "Konnichiwa",
+                "Sekai",
+                "Ciao",
+                "Mondo",
+                "Annyeong",
+                "Sesang"
+            )
+        )
+        set.removeAll(
+            arrayOf("Hallo", "Welt", "Konnichiwa", "Sekai", "Ciao", "Mondo", "Annyeong", "Sesang")
+        )
+        assertTrue(set.trim() > 0)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun scatterSetOfEmpty() {
+        assertSame(emptyScatterSet(), scatterSetOf<String>())
+        assertEquals(0, scatterSetOf<String>().size)
+    }
+
+    @Test
+    fun scatterSetOfOne() {
+        val set = scatterSetOf("Hello")
+        assertEquals(1, set.size)
+        assertEquals("Hello", set.first())
+    }
+
+    @Test
+    fun scatterSetOfTwo() {
+        val set = scatterSetOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun scatterSetOfThree() {
+        val set = scatterSetOf("Hello", "World", "Hola")
+        assertEquals(3, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun scatterSetOfFour() {
+        val set = scatterSetOf("Hello", "World", "Hola", "Mundo")
+        assertEquals(4, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertTrue("Mundo" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun mutableOrderedScatterSetOfOne() {
+        val set = mutableOrderedScatterSetOf("Hello")
+        assertEquals(1, set.size)
+        assertEquals("Hello", set.first())
+    }
+
+    @Test
+    fun mutableOrderedScatterSetOfTwo() {
+        val set = mutableOrderedScatterSetOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun mutableOrderedScatterSetOfThree() {
+        val set = mutableOrderedScatterSetOf("Hello", "World", "Hola")
+        assertEquals(3, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun mutableOrderedScatterSetOfFour() {
+        val set = mutableOrderedScatterSetOf("Hello", "World", "Hola", "Mundo")
+        assertEquals(4, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertTrue("Mundo" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun removeIf() {
+        val set = MutableOrderedScatterSet<String>()
+        set.add("Hello")
+        set.add("Bonjour")
+        set.add("Hallo")
+        set.add("Konnichiwa")
+        set.add("Ciao")
+        set.add("Annyeong")
+
+        set.removeIf { value -> value.startsWith('H') }
+
+        assertEquals(4, set.size)
+        assertTrue(set.contains("Bonjour"))
+        assertTrue(set.contains("Konnichiwa"))
+        assertTrue(set.contains("Ciao"))
+        assertTrue(set.contains("Annyeong"))
+    }
+
+    @Test
+    fun insertOneRemoveOne() {
+        val set = MutableOrderedScatterSet<Int>()
+
+        for (i in 0..1000000) {
+            set.add(i)
+            set.remove(i)
+            assertTrue(set.capacity < 16, "Set grew larger than 16 after step $i")
+        }
+    }
+
+    @Test
+    fun insertManyRemoveMany() {
+        val map = MutableOrderedScatterSet<String>()
+
+        for (i in 0..100) {
+            map.add(i.toString())
+        }
+
+        for (i in 0..100) {
+            if (i % 2 == 0) {
+                map.remove(i.toString())
+            }
+        }
+
+        for (i in 0..100) {
+            if (i % 2 == 0) {
+                map.add(i.toString())
+            }
+        }
+
+        for (i in 0..100) {
+            if (i % 2 != 0) {
+                map.remove(i.toString())
+            }
+        }
+
+        for (i in 0..100) {
+            if (i % 2 != 0) {
+                map.add(i.toString())
+            }
+        }
+
+        assertEquals(127, map.capacity)
+        for (i in 0..100) {
+            assertTrue(map.contains(i.toString()), "Map should contain element $i")
+        }
+    }
+
+    @Test
+    fun removeWhenIterating() {
+        val set = MutableOrderedScatterSet<String>()
+        set.add("Hello")
+        set.add("Bonjour")
+        set.add("Hallo")
+        set.add("Konnichiwa")
+        set.add("Ciao")
+        set.add("Annyeong")
+
+        val iterator = set.asMutableSet().iterator()
+        while (iterator.hasNext()) {
+            iterator.next()
+            iterator.remove()
+        }
+
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun removeWhenForEach() {
+        val set = MutableOrderedScatterSet<String>()
+        set.add("Hello")
+        set.add("Bonjour")
+        set.add("Hallo")
+        set.add("Konnichiwa")
+        set.add("Ciao")
+        set.add("Annyeong")
+
+        set.forEach { element -> set.remove(element) }
+
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun removeWhenForEachReverse() {
+        val set = MutableOrderedScatterSet<String>()
+        set.add("Hello")
+        set.add("Bonjour")
+        set.add("Hallo")
+        set.add("Konnichiwa")
+        set.add("Ciao")
+        set.add("Annyeong")
+
+        set.forEachReverse { element -> set.remove(element) }
+
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun retainAllCollection() {
+        val set = mutableOrderedScatterSetOf<String>()
+
+        set.retainAll(listOf())
+        assertTrue(set.isEmpty())
+
+        set.retainAll(listOf("Monde", "Hallo", "Welt", "Konnichiwa"))
+        assertTrue(set.isEmpty())
+
+        set +=
+            listOf(
+                "Hello",
+                "World",
+                "Hola",
+                "Mundo",
+                "Bonjour",
+                "Monde",
+                "Hallo",
+                "Welt",
+                "Konnichiwa",
+                "Sekai",
+                "Ciao",
+                "Mondo",
+                "Annyeong",
+                "Sesang"
+            )
+
+        val content = listOf("Monde", "Hallo", "Sekai", "Ciao")
+        set.retainAll(content)
+        assertContentEquals(content, set.toList())
+
+        set.retainAll(listOf())
+        assertTrue(set.isEmpty())
+        set.forEach { fail() }
+    }
+
+    @Test
+    fun retainAllSet() {
+        val set = mutableOrderedScatterSetOf<String>()
+
+        set.retainAll(listOf())
+        assertTrue(set.isEmpty())
+
+        set.retainAll(orderedScatterSetOf("Monde", "Hallo", "Welt", "Konnichiwa"))
+        assertTrue(set.isEmpty())
+
+        set +=
+            listOf(
+                "Hello",
+                "World",
+                "Hola",
+                "Mundo",
+                "Bonjour",
+                "Monde",
+                "Hallo",
+                "Welt",
+                "Konnichiwa",
+                "Sekai",
+                "Ciao",
+                "Mondo",
+                "Annyeong",
+                "Sesang"
+            )
+
+        val content = orderedScatterSetOf("Monde", "Hallo", "Sekai", "Ciao")
+        set.retainAll(content)
+        assertContentEquals(content.toList(), set.toList())
+
+        set.retainAll(listOf())
+        assertTrue(set.isEmpty())
+        set.forEach { fail() }
+    }
+
+    @Test
+    fun retainAllPredicate() {
+        val set = mutableOrderedScatterSetOf<String>()
+
+        set.retainAll { false }
+        assertTrue(set.isEmpty())
+
+        set.retainAll { true }
+        assertTrue(set.isEmpty())
+
+        set +=
+            listOf(
+                "Hello",
+                "World",
+                "Hola",
+                "Mundo",
+                "Bonjour",
+                "Monde",
+                "Hallo",
+                "Welt",
+                "Konnichiwa",
+                "Sekai",
+                "Ciao",
+                "Mondo",
+                "Annyeong",
+                "Sesang"
+            )
+
+        val content = listOf("World", "Welt")
+        set.retainAll { it.startsWith('W') }
+        assertContentEquals(content.toList(), set.toList())
+
+        set.retainAll { false }
+        assertTrue(set.isEmpty())
+        set.forEach { fail() }
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
index 108548e..dad4c38 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
@@ -25,6 +25,7 @@
 import kotlin.test.assertNull
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
+import kotlin.test.fail
 
 class ScatterMapTest {
     @Test
@@ -626,6 +627,8 @@
 
         assertEquals(0, map.size)
         assertEquals(capacity, map.capacity)
+
+        map.forEach { _, _ -> fail() }
     }
 
     @Test
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
index 5185166..a527025 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
@@ -550,13 +550,13 @@
     @Test
     fun hashCodeAddValues() {
         val set = mutableScatterSetOf<String?>()
-        assertEquals(0, set.hashCode())
+        assertEquals(217, set.hashCode())
         set += null
-        assertEquals(0, set.hashCode())
+        assertEquals(218, set.hashCode())
         set += "Hello"
-        assertEquals("Hello".hashCode(), set.hashCode())
+        val h1 = set.hashCode()
         set += "World"
-        assertEquals("World".hashCode() + "Hello".hashCode(), set.hashCode())
+        assertNotEquals(h1, set.hashCode())
     }
 
     @Test
@@ -899,4 +899,38 @@
             assertTrue(map.contains(i), "Map should contain element $i")
         }
     }
+
+    @Test
+    fun removeWhenIterating() {
+        val set = MutableScatterSet<String>()
+        set.add("Hello")
+        set.add("Bonjour")
+        set.add("Hallo")
+        set.add("Konnichiwa")
+        set.add("Ciao")
+        set.add("Annyeong")
+
+        val iterator = set.asMutableSet().iterator()
+        while (iterator.hasNext()) {
+            iterator.next()
+            iterator.remove()
+        }
+
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun removeWhenForEach() {
+        val set = MutableScatterSet<String>()
+        set.add("Hello")
+        set.add("Bonjour")
+        set.add("Hallo")
+        set.add("Konnichiwa")
+        set.add("Ciao")
+        set.add("Annyeong")
+
+        set.forEach { element -> set.remove(element) }
+
+        assertEquals(0, set.size)
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt
index efcec45..bbe1487 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/SieveCacheTest.kt
@@ -856,6 +856,19 @@
         assertEquals(1, cache.size)
     }
 
+    @Test
+    fun evictionInCacheOfSize1() {
+        val cache = SieveCache<String, String>(1, 1)
+        cache["1"] = "a"
+
+        // Visit the entry
+        assertEquals("a", cache["1"])
+
+        cache["2"] = "b"
+        assertFalse("1" in cache)
+        assertEquals("b", cache["2"])
+    }
+
     private fun createCreatingCache(): SieveCache<String, String> {
         return SieveCache(4, createValueFromKey = { key -> "created-$key" })
     }
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index 595aef5..8eac369 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -46,7 +46,7 @@
                 implementation(project(":compose:ui:ui-unit"))
                 implementation(project(":compose:ui:ui-graphics"))
                 implementation(project(":compose:ui:ui-util"))
-                implementation(project(":collection:collection"))
+                implementation("androidx.collection:collection:1.4.2")
                 implementation(libs.kotlinStdlib)
                 api(libs.kotlinCoroutinesCore)
             }
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index 16f1350..399503d 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -49,7 +49,7 @@
                 api(project(":compose:ui:ui-geometry"))
 
                 implementation(project(":compose:ui:ui-util"))
-                implementation(project(":collection:collection"))
+                implementation("androidx.collection:collection:1.4.2")
             }
         }
         androidMain.dependencies {
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index f968628..a2950c4 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -52,7 +52,7 @@
                 implementation(project(":compose:ui:ui-util"))
                 implementation(project(":compose:ui:ui-graphics"))
 
-                implementation(project(":collection:collection"))
+                implementation("androidx.collection:collection:1.4.2")
             }
         }
 
diff --git a/compose/docs/compose-component-api-guidelines.md b/compose/docs/compose-component-api-guidelines.md
index 857ee3e..978d7ff 100644
--- a/compose/docs/compose-component-api-guidelines.md
+++ b/compose/docs/compose-component-api-guidelines.md
@@ -632,7 +632,7 @@
 The order of parameters in a component must be as follows:
 
 1. Required parameters.
-2. Single` modifier: Modifier = Modifier`.
+2. Single `modifier: Modifier = Modifier`.
 3. Optional parameters.
 4. (optional) trailing `@Composable` lambda.
 
diff --git a/compose/foundation/foundation-layout/benchmark/build.gradle b/compose/foundation/foundation-layout/benchmark/build.gradle
index 098eb82..8726ebd 100644
--- a/compose/foundation/foundation-layout/benchmark/build.gradle
+++ b/compose/foundation/foundation-layout/benchmark/build.gradle
@@ -41,7 +41,4 @@
 android {
     compileSdk 35
     namespace "androidx.compose.foundation.layout.benchmark"
-
-    // Experiment to observe effect of minification on noise, see b/354264743
-    buildTypes.release.androidTest.enableMinification = true
 }
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 565c485..703c2f2 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -44,8 +44,8 @@
                 api(project(":compose:ui:ui"))
                 implementation(project(":compose:runtime:runtime"))
                 implementation(project(":compose:ui:ui-util"))
-                implementation(project(":collection:collection"))
                 implementation(project(":compose:ui:ui-unit"))
+                implementation("androidx.collection:collection:1.4.2")
             }
         }
 
@@ -122,4 +122,7 @@
 android {
     compileSdk 35
     namespace "androidx.compose.foundation.layout"
+    buildTypes.configureEach {
+        consumerProguardFiles("proguard-rules.pro")
+    }
 }
diff --git a/compose/foundation/foundation-layout/proguard-rules.pro b/compose/foundation/foundation-layout/proguard-rules.pro
new file mode 100644
index 0000000..d07663a
--- /dev/null
+++ b/compose/foundation/foundation-layout/proguard-rules.pro
@@ -0,0 +1,22 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Keep all the functions created to throw an exception. We don't want these functions to be
+# inlined in any way, which R8 will do by default. The whole point of these functions is to
+# reduce the amount of code generated at the call site.
+-keep,allowshrinking,allowobfuscation class androidx.compose.**.* {
+    static void throw*Exception(...);
+    # For methods returning Nothing
+    static java.lang.Void throw*Exception(...);
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
index bd52f55..fb9ac8c05 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AlignmentLine.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.layout
 
+import androidx.compose.foundation.layout.internal.requirePrecondition
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.AlignmentLine
@@ -190,7 +191,7 @@
     val inspectorInfo: InspectorInfo.() -> Unit
 ) : ModifierNodeElement<AlignmentLineOffsetDpNode>() {
     init {
-        require(
+        requirePrecondition(
             (before.value >= 0f || before == Dp.Unspecified) &&
                 (after.value >= 0f || after == Dp.Unspecified)
         ) {
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt
index 514f2ed..a94f9eb 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/AspectRatio.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.layout
 
 import androidx.annotation.FloatRange
+import androidx.compose.foundation.layout.internal.requirePrecondition
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.IntrinsicMeasurable
@@ -74,7 +75,7 @@
     val inspectorInfo: InspectorInfo.() -> Unit
 ) : ModifierNodeElement<AspectRatioNode>() {
     init {
-        require(aspectRatio > 0) { "aspectRatio $aspectRatio must be > 0" }
+        requirePrecondition(aspectRatio > 0) { "aspectRatio $aspectRatio must be > 0" }
     }
 
     override fun create(): AspectRatioNode {
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
index 70e0789..d6408f2 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.FloatRange
 import androidx.compose.foundation.layout.internal.JvmDefaultWithCompatibility
+import androidx.compose.foundation.layout.internal.requirePrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -358,7 +359,7 @@
 internal object ColumnScopeInstance : ColumnScope {
     @Stable
     override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
-        require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+        requirePrecondition(weight > 0.0) { "invalid weight; must be greater than zero" }
         return this.then(
             LayoutWeightElement(
                 // Coerce Float.POSITIVE_INFINITY to Float.MAX_VALUE to avoid errors
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
index 2504197..0a4280f 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.layout
 
 import androidx.annotation.FloatRange
+import androidx.compose.foundation.layout.internal.requirePrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
@@ -333,10 +334,9 @@
     override val maxHeight: Dp
 ) : RowScope by RowScopeInstance, ContextualFlowRowScope {
     override fun Modifier.fillMaxRowHeight(fraction: Float): Modifier {
-        require(fraction >= 0.0) {
-            "invalid fraction $fraction; must be greater than " + "or equal to zero"
+        requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+            "invalid fraction $fraction; must be >= 0 and <= 1.0"
         }
-        require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
         return this.then(
             FillCrossAxisSizeElement(
                 fraction = fraction,
@@ -353,10 +353,9 @@
     override val maxHeightInLine: Dp
 ) : ColumnScope by ColumnScopeInstance, ContextualFlowColumnScope {
     override fun Modifier.fillMaxColumnWidth(fraction: Float): Modifier {
-        require(fraction >= 0.0) {
-            "invalid fraction $fraction; must be greater than or " + "equal to zero"
+        requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+            "invalid fraction $fraction; must be >= 0 and <= 1.0"
         }
-        require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
         return this.then(
             FillCrossAxisSizeElement(
                 fraction = fraction,
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
index 0900cfe..3b4aff7 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
@@ -20,6 +20,7 @@
 import androidx.collection.IntIntPair
 import androidx.collection.mutableIntListOf
 import androidx.collection.mutableIntObjectMapOf
+import androidx.compose.foundation.layout.internal.requirePrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.collection.MutableVector
@@ -254,10 +255,9 @@
 @OptIn(ExperimentalLayoutApi::class)
 internal object FlowRowScopeInstance : RowScope by RowScopeInstance, FlowRowScope {
     override fun Modifier.fillMaxRowHeight(fraction: Float): Modifier {
-        require(fraction >= 0.0) {
-            "invalid fraction $fraction; must be greater than " + "or equal to zero"
+        requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+            "invalid fraction $fraction; must be >= 0 and <= 1.0"
         }
-        require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
         return this.then(
             FillCrossAxisSizeElement(
                 fraction = fraction,
@@ -285,10 +285,9 @@
 @OptIn(ExperimentalLayoutApi::class)
 internal object FlowColumnScopeInstance : ColumnScope by ColumnScopeInstance, FlowColumnScope {
     override fun Modifier.fillMaxColumnWidth(fraction: Float): Modifier {
-        require(fraction >= 0.0) {
-            "invalid fraction $fraction; must be greater than or " + "equal to zero"
+        requirePrecondition(fraction >= 0.0f && fraction <= 1.0f) {
+            "invalid fraction $fraction; must be >= 0 and <= 1.0"
         }
-        require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater " + "than 1.0" }
         return this.then(
             FillCrossAxisSizeElement(
                 fraction = fraction,
@@ -1457,7 +1456,7 @@
     var totalCrossAxisSize = crossAxisTotalSize
     // cross axis arrangement
     if (isHorizontal) {
-        with(requireNotNull(verticalArrangement) { "null verticalArrangement" }) {
+        with(verticalArrangement) {
             val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
             totalCrossAxisSize += totalCrossAxisSpacing
             totalCrossAxisSize =
@@ -1465,7 +1464,7 @@
             arrange(totalCrossAxisSize, crossAxisSizes, outPosition)
         }
     } else {
-        with(requireNotNull(horizontalArrangement) { "null horizontalArrangement" }) {
+        with(horizontalArrangement) {
             val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
             totalCrossAxisSize += totalCrossAxisSpacing
             totalCrossAxisSize =
@@ -1477,8 +1476,8 @@
     val finalMainAxisTotalSize =
         mainAxisTotalSize.coerceIn(constraints.mainAxisMin, constraints.mainAxisMax)
 
-    var layoutWidth: Int
-    var layoutHeight: Int
+    val layoutWidth: Int
+    val layoutHeight: Int
     if (isHorizontal) {
         layoutWidth = finalMainAxisTotalSize
         layoutHeight = totalCrossAxisSize
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
index 90c74eb..24ac686 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.layout
 
 import androidx.compose.foundation.layout.PaddingValues.Absolute
+import androidx.compose.foundation.layout.internal.requirePrecondition
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
@@ -202,10 +203,10 @@
     ) : PaddingValues {
 
         init {
-            require(left.value >= 0) { "Left padding must be non-negative" }
-            require(top.value >= 0) { "Top padding must be non-negative" }
-            require(right.value >= 0) { "Right padding must be non-negative" }
-            require(bottom.value >= 0) { "Bottom padding must be non-negative" }
+            requirePrecondition(left.value >= 0) { "Left padding must be non-negative" }
+            requirePrecondition(top.value >= 0) { "Top padding must be non-negative" }
+            requirePrecondition(right.value >= 0) { "Right padding must be non-negative" }
+            requirePrecondition(bottom.value >= 0) { "Bottom padding must be non-negative" }
         }
 
         override fun calculateLeftPadding(layoutDirection: LayoutDirection) = left
@@ -290,10 +291,10 @@
 ) : PaddingValues {
 
     init {
-        require(start.value >= 0) { "Start padding must be non-negative" }
-        require(top.value >= 0) { "Top padding must be non-negative" }
-        require(end.value >= 0) { "End padding must be non-negative" }
-        require(bottom.value >= 0) { "Bottom padding must be non-negative" }
+        requirePrecondition(start.value >= 0) { "Start padding must be non-negative" }
+        requirePrecondition(top.value >= 0) { "Top padding must be non-negative" }
+        requirePrecondition(end.value >= 0) { "End padding must be non-negative" }
+        requirePrecondition(bottom.value >= 0) { "Bottom padding must be non-negative" }
     }
 
     override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
@@ -330,7 +331,7 @@
 ) : ModifierNodeElement<PaddingNode>() {
 
     init {
-        require(
+        requirePrecondition(
             (start.value >= 0f || start == Dp.Unspecified) &&
                 (top.value >= 0f || top == Dp.Unspecified) &&
                 (end.value >= 0f || end == Dp.Unspecified) &&
@@ -435,7 +436,7 @@
         measurable: Measurable,
         constraints: Constraints
     ): MeasureResult {
-        require(
+        requirePrecondition(
             paddingValues.calculateLeftPadding(layoutDirection) >= 0.dp &&
                 paddingValues.calculateTopPadding() >= 0.dp &&
                 paddingValues.calculateRightPadding(layoutDirection) >= 0.dp &&
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
index b2e98bc..e3ce313 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.FloatRange
 import androidx.compose.foundation.layout.internal.JvmDefaultWithCompatibility
+import androidx.compose.foundation.layout.internal.requirePrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -380,7 +381,7 @@
 internal object RowScopeInstance : RowScope {
     @Stable
     override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
-        require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+        requirePrecondition(weight > 0.0) { "invalid weight; must be greater than zero" }
         return this.then(
             LayoutWeightElement(
                 // Coerce Float.POSITIVE_INFINITY to Float.MAX_VALUE to avoid errors
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
index 41febd5..8482560 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.layout
 
+import androidx.compose.foundation.layout.internal.checkPrecondition
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
@@ -218,7 +219,7 @@
                         parentData?.flowLayoutData?.let {
                             (it.fillCrossAxisFraction * crossAxisMax).fastRoundToInt()
                         }
-                check(weight > 0) { "All weights <= 0 should have placeables" }
+                checkPrecondition(weight > 0) { "All weights <= 0 should have placeables" }
                 // After the weightUnitSpace rounding, the total space going to be occupied
                 // can be smaller or larger than remainingToTarget. Here we distribute the
                 // loss or gain remainder evenly to the first children.
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/internal/InlineClassHelper.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/internal/InlineClassHelper.kt
new file mode 100644
index 0000000..844d5df
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/internal/InlineClassHelper.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.foundation.layout.internal
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// This function exists so we do *not* inline the throw. It keeps
+// the call site much smaller and since it's the slow path anyway,
+// we don't mind the extra function call
+internal fun throwIllegalStateException(message: String) {
+    throw IllegalStateException(message)
+}
+
+internal fun throwIllegalStateExceptionForNullCheck(message: String): Nothing {
+    throw IllegalStateException(message)
+}
+
+internal fun throwIllegalArgumentException(message: String) {
+    throw IllegalArgumentException(message)
+}
+
+internal fun throwIndexOutOfBoundsException(message: String) {
+    throw IndexOutOfBoundsException(message)
+}
+
+// Like Kotlin's check() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean, lazyMessage: () -> String) {
+    contract { returns() implies value }
+    if (!value) {
+        throwIllegalStateException(lazyMessage())
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean) {
+    contract { returns() implies value }
+    if (!value) {
+        throwIllegalStateException("Check failed.")
+    }
+}
+
+// Like Kotlin's checkNotNull() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?, lazyMessage: () -> String): T {
+    contract { returns() implies (value != null) }
+
+    if (value == null) {
+        throwIllegalStateExceptionForNullCheck(lazyMessage())
+    }
+
+    return value
+}
+
+// Like Kotlin's checkNotNull() but with a non-inline throw
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?): T {
+    contract { returns() implies (value != null) }
+
+    if (value == null) {
+        throwIllegalStateExceptionForNullCheck("Required value was null.")
+    }
+
+    return value
+}
+
+// Like Kotlin's require() but without the .toString() call
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class) // same opt-in as using Kotlin's require()
+internal inline fun requirePrecondition(value: Boolean, lazyMessage: () -> String) {
+    contract { returns() implies value }
+    if (!value) {
+        throwIllegalArgumentException(lazyMessage())
+    }
+}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 79430fc..3c64ee1 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -93,8 +93,10 @@
     method public static androidx.compose.foundation.CombinedClickableNode CombinedClickableNode(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, String? onLongClickLabel, kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.IndicationNodeFactory? indicationNodeFactory, boolean enabled, String? onClickLabel, androidx.compose.ui.semantics.Role? role);
     method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
     method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
   }
 
   public final class ClipScrollableContainerKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 3254ecd..a3d6ffc 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -93,8 +93,10 @@
     method public static androidx.compose.foundation.CombinedClickableNode CombinedClickableNode(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, String? onLongClickLabel, kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.IndicationNodeFactory? indicationNodeFactory, boolean enabled, String? onClickLabel, androidx.compose.ui.semantics.Role? role);
     method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
     method public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, androidx.compose.foundation.Indication? indication, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional boolean hapticFeedbackEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+    method @Deprecated public static androidx.compose.ui.Modifier combinedClickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
   }
 
   public final class ClipScrollableContainerKt {
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 2122e74..ed11b00 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -41,7 +41,7 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlib)
-                api(project(":collection:collection"))
+                api("androidx.collection:collection:1.4.2")
                 api(project(":compose:animation:animation"))
                 api(project(":compose:runtime:runtime"))
                 api(project(":compose:ui:ui"))
@@ -141,6 +141,9 @@
     sourceSets.androidTest.assets.srcDirs +=
             project.rootDir.absolutePath + "/../../golden/compose/foundation/foundation"
     namespace "androidx.compose.foundation"
+    buildTypes.configureEach {
+        consumerProguardFiles("proguard-rules.pro")
+    }
     // TODO(b/345531033)
     experimentalProperties["android.lint.useK2Uast"] = false
 }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
index e603016..2b622b5 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/ScrollableUtils.kt
@@ -17,10 +17,12 @@
 package androidx.compose.foundation
 
 import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
 import androidx.compose.ui.platform.AbstractComposeView
 import androidx.compose.ui.util.fastForEach
 import androidx.test.espresso.Espresso
@@ -35,11 +37,16 @@
 // Very low tolerance on the difference
 internal val VelocityTrackerCalculationThreshold = 1
 
+@OptIn(ExperimentalComposeUiApi::class)
 internal suspend fun savePointerInputEvents(
     tracker: VelocityTracker,
     pointerInputScope: PointerInputScope
 ) {
-    savePointerInputEventsWithFix(tracker, pointerInputScope)
+    if (VelocityTrackerAddPointsFix) {
+        savePointerInputEventsWithFix(tracker, pointerInputScope)
+    } else {
+        savePointerInputEventsLegacy(tracker, pointerInputScope)
+    }
 }
 
 internal suspend fun savePointerInputEventsWithFix(
diff --git a/compose/foundation/foundation/proguard-rules.pro b/compose/foundation/foundation/proguard-rules.pro
new file mode 100644
index 0000000..d07663a
--- /dev/null
+++ b/compose/foundation/foundation/proguard-rules.pro
@@ -0,0 +1,22 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Keep all the functions created to throw an exception. We don't want these functions to be
+# inlined in any way, which R8 will do by default. The whole point of these functions is to
+# reduce the amount of code generated at the call site.
+-keep,allowshrinking,allowobfuscation class androidx.compose.**.* {
+    static void throw*Exception(...);
+    # For methods returning Nothing
+    static java.lang.Void throw*Exception(...);
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
index d5f2f0c..65b960f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableParameterizedKeyInputTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.ReusableContent
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -32,10 +33,13 @@
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.focusTarget
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.input.InputMode.Companion.Keyboard
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
@@ -45,6 +49,7 @@
 import androidx.compose.ui.test.performKeyInput
 import androidx.compose.ui.test.pressKey
 import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -232,6 +237,57 @@
         }
     }
 
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    @LargeTest
+    fun longClickWithKey_doesNotTriggerHapticFeedback() {
+        var clickCounter = 0
+        var longClickCounter = 0
+        val focusRequester = FocusRequester()
+        lateinit var inputModeManager: InputModeManager
+        val performedHaptics = mutableListOf<HapticFeedbackType>()
+
+        val hapticFeedback: HapticFeedback =
+            object : HapticFeedback {
+                override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
+                    performedHaptics += hapticFeedbackType
+                }
+            }
+        rule.setContent {
+            inputModeManager = LocalInputModeManager.current
+            CompositionLocalProvider(LocalHapticFeedback provides hapticFeedback) {
+                BasicText(
+                    "ClickableText",
+                    modifier =
+                        Modifier.testTag("myClickable")
+                            .focusRequester(focusRequester)
+                            .combinedClickable(
+                                onLongClick = { ++longClickCounter },
+                                onClick = { ++clickCounter },
+                                hapticFeedbackEnabled = true
+                            )
+                )
+            }
+        }
+        rule.runOnIdle {
+            inputModeManager.requestInputMode(Keyboard)
+            focusRequester.requestFocus()
+        }
+
+        rule.onNodeWithTag("myClickable").performKeyInput {
+            assertThat(inputModeManager.inputMode).isEqualTo(Keyboard)
+            // The press duration is 100ms longer than the minimum required for a long press.
+            val durationMillis: Long = viewConfiguration.longPressTimeoutMillis + 100
+            pressKey(key, durationMillis)
+        }
+
+        rule.runOnIdle {
+            assertThat(longClickCounter).isEqualTo(1)
+            assertThat(clickCounter).isEqualTo(0)
+            assertThat(performedHaptics).isEmpty()
+        }
+    }
+
     @Test
     @OptIn(ExperimentalTestApi::class)
     fun longClickWithKey_notInvokedIfFocusIsLostWhilePressed() {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
index 44c3864..165c8c5 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
@@ -50,6 +50,8 @@
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusEvent
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.input.InputMode
 import androidx.compose.ui.input.InputMode.Companion.Keyboard
 import androidx.compose.ui.input.InputMode.Companion.Touch
@@ -57,6 +59,7 @@
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
@@ -318,6 +321,107 @@
     }
 
     @Test
+    @LargeTest
+    fun longClick_hapticFeedbackEnabled() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+        val performedHaptics = mutableListOf<HapticFeedbackType>()
+
+        val hapticFeedback: HapticFeedback =
+            object : HapticFeedback {
+                override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
+                    performedHaptics += hapticFeedbackType
+                }
+            }
+
+        rule.setContent {
+            CompositionLocalProvider(LocalHapticFeedback provides hapticFeedback) {
+                Box {
+                    BasicText(
+                        "ClickableText",
+                        modifier =
+                            Modifier.testTag("myClickable").combinedClickable(
+                                onLongClick = onClick,
+                                hapticFeedbackEnabled = true
+                            ) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+        // Advance a small amount of time
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNodeWithTag("myClickable").performTouchInput { up() }
+
+        // Releasing the press before the long click timeout shouldn't trigger haptic feedback
+        rule.runOnIdle { assertThat(counter).isEqualTo(0) }
+        rule.runOnIdle { assertThat(performedHaptics).isEmpty() }
+
+        rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+        // Advance past the long press timeout
+        rule.mainClock.advanceTimeBy(1000)
+
+        // Long press haptic feedback should be invoked
+        rule.runOnIdle { assertThat(counter).isEqualTo(1) }
+        rule.runOnIdle {
+            assertThat(performedHaptics).containsExactly(HapticFeedbackType.LongPress)
+        }
+    }
+
+    @Test
+    @LargeTest
+    fun longClick_hapticFeedbackDisabled() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+        val performedHaptics = mutableListOf<HapticFeedbackType>()
+
+        val hapticFeedback: HapticFeedback =
+            object : HapticFeedback {
+                override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
+                    performedHaptics += hapticFeedbackType
+                }
+            }
+
+        rule.setContent {
+            CompositionLocalProvider(LocalHapticFeedback provides hapticFeedback) {
+                Box {
+                    BasicText(
+                        "ClickableText",
+                        modifier =
+                            Modifier.testTag("myClickable").combinedClickable(
+                                onLongClick = onClick,
+                                hapticFeedbackEnabled = false
+                            ) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+        // Advance a small amount of time
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.onNodeWithTag("myClickable").performTouchInput { up() }
+
+        rule.runOnIdle { assertThat(counter).isEqualTo(0) }
+        rule.runOnIdle { assertThat(performedHaptics).isEmpty() }
+
+        rule.onNodeWithTag("myClickable").performTouchInput { down(center) }
+
+        // Advance past the long press timeout
+        rule.mainClock.advanceTimeBy(1000)
+
+        // Long press should be invoked, without any haptics
+        rule.runOnIdle { assertThat(counter).isEqualTo(1) }
+        rule.runOnIdle { assertThat(performedHaptics).isEmpty() }
+    }
+
+    @Test
     @OptIn(ExperimentalTestApi::class)
     fun longClickWithEnterKeyThenDPadCenter_triggersListenerTwice() {
         var clickCounter = 0
@@ -1808,7 +1912,8 @@
                     "onClick",
                     "onDoubleClick",
                     "onLongClick",
-                    "onLongClickLabel"
+                    "onLongClickLabel",
+                    "hapticFeedbackEnabled"
                 )
         }
     }
@@ -1836,7 +1941,8 @@
                     "onLongClick",
                     "onLongClickLabel",
                     "indicationNodeFactory",
-                    "interactionSource"
+                    "interactionSource",
+                    "hapticFeedbackEnabled"
                 )
         }
     }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index c4d5f68..1f21478 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -60,6 +60,7 @@
 import androidx.compose.testutils.assertModifierIsPure
 import androidx.compose.testutils.first
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.MotionDurationScale
 import androidx.compose.ui.focus.FocusDirection
@@ -79,6 +80,7 @@
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
 import androidx.compose.ui.materialize
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.TraversableNode
@@ -3065,11 +3067,16 @@
 // Very low tolerance on the difference
 internal val VelocityTrackerCalculationThreshold = 1
 
+@OptIn(ExperimentalComposeUiApi::class)
 internal suspend fun savePointerInputEvents(
     tracker: VelocityTracker,
     pointerInputScope: PointerInputScope
 ) {
-    savePointerInputEventsWithFix(tracker, pointerInputScope)
+    if (VelocityTrackerAddPointsFix) {
+        savePointerInputEventsWithFix(tracker, pointerInputScope)
+    } else {
+        savePointerInputEventsLegacy(tracker, pointerInputScope)
+    }
 }
 
 internal suspend fun savePointerInputEventsWithFix(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
index db9f9743..c9fad46 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerAccessibilityTest.kt
@@ -19,6 +19,7 @@
 import android.view.accessibility.AccessibilityNodeProvider
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.focusable
+import androidx.compose.foundation.internal.checkPreconditionNotNull
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
@@ -49,7 +50,7 @@
 
     private val accessibilityNodeProvider: AccessibilityNodeProvider
         get() =
-            checkNotNull(composeView) { "composeView not initialized." }
+            checkPreconditionNotNull(composeView) { "composeView not initialized." }
                 .let { composeView ->
                     ViewCompat.getAccessibilityDelegate(composeView)!!.getAccessibilityNodeProvider(
                             composeView
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
index 89077c7..9cad429 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/InputMethodInterceptor.kt
@@ -19,6 +19,7 @@
 import android.os.Looper
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.InputConnection
+import androidx.compose.foundation.internal.checkPreconditionNotNull
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.platform.InterceptPlatformTextInput
@@ -149,7 +150,7 @@
     fun withInputConnection(block: InputConnection.() -> Unit) {
         runOnIdle {
             val inputConnection =
-                checkNotNull(inputConnection) {
+                checkPreconditionNotNull(inputConnection) {
                     "Tried to read inputConnection while no session was active"
                 }
             block(inputConnection)
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt
index 931c700..6727ef5 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuState.android.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.contextmenu
 
 import androidx.compose.foundation.contextmenu.ContextMenuState.Status
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -49,7 +50,7 @@
             val offset: Offset
         ) : Status() {
             init {
-                check(offset.isSpecified) { UNSPECIFIED_OFFSET_ERROR_MESSAGE }
+                checkPrecondition(offset.isSpecified) { UNSPECIFIED_OFFSET_ERROR_MESSAGE }
             }
 
             override fun toString(): String = "Open(offset=$offset)"
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt
index 595b7b0..e4a3f61 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/contextmenu/ContextMenuUi.android.kt
@@ -25,6 +25,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -280,7 +281,7 @@
     ) {
         composables += { colors ->
             val resolvedLabel = label()
-            check(resolvedLabel.isNotBlank()) { "Label must not be blank" }
+            checkPrecondition(resolvedLabel.isNotBlank()) { "Label must not be blank" }
             ContextMenuItem(
                 modifier = modifier,
                 label = resolvedLabel,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt
index 9d7b797..b623830 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/CheckScrollableContainerConstraints.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.ui.unit.Constraints
 
 /**
@@ -28,7 +29,7 @@
  */
 fun checkScrollableContainerConstraints(constraints: Constraints, orientation: Orientation) {
     if (orientation == Orientation.Vertical) {
-        check(constraints.maxHeight != Constraints.Infinity) {
+        checkPrecondition(constraints.maxHeight != Constraints.Infinity) {
             "Vertically scrollable component was measured with an infinity maximum height " +
                 "constraints, which is disallowed. One of the common reasons is nesting layouts " +
                 "like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a " +
@@ -40,7 +41,7 @@
                 "hierarchy above the scrolling container."
         }
     } else {
-        check(constraints.maxWidth != Constraints.Infinity) {
+        checkPrecondition(constraints.maxWidth != Constraints.Infinity) {
             "Horizontally scrollable component was measured with an infinity maximum width " +
                 "constraints, which is disallowed. One of the common reasons is nesting layouts " +
                 "like LazyRow and Row(Modifier.horizontalScroll()). If you want to add a " +
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 5a2dde7..ee11b6e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -29,6 +29,8 @@
 import androidx.compose.ui.composed
 import androidx.compose.ui.focus.Focusability
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyInputModifierNode
 import androidx.compose.ui.input.key.key
@@ -48,6 +50,7 @@
 import androidx.compose.ui.node.invalidateSemantics
 import androidx.compose.ui.node.traverseAncestors
 import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.Role
@@ -228,6 +231,7 @@
  * @param onLongClickLabel semantic / accessibility label for the [onLongClick] action
  * @param onLongClick will be called when user long presses on the element
  * @param onDoubleClick will be called when user double clicks on the element
+ * @param hapticFeedbackEnabled whether to use the default [HapticFeedback] behavior
  * @param onClick will be called when user clicks on the element
  */
 fun Modifier.combinedClickable(
@@ -237,6 +241,56 @@
     onLongClickLabel: String? = null,
     onLongClick: (() -> Unit)? = null,
     onDoubleClick: (() -> Unit)? = null,
+    hapticFeedbackEnabled: Boolean = true,
+    onClick: () -> Unit
+) =
+    composed(
+        inspectorInfo =
+            debugInspectorInfo {
+                name = "combinedClickable"
+                properties["enabled"] = enabled
+                properties["onClickLabel"] = onClickLabel
+                properties["role"] = role
+                properties["onClick"] = onClick
+                properties["onDoubleClick"] = onDoubleClick
+                properties["onLongClick"] = onLongClick
+                properties["onLongClickLabel"] = onLongClickLabel
+                properties["hapticFeedbackEnabled"] = hapticFeedbackEnabled
+            }
+    ) {
+        val localIndication = LocalIndication.current
+        val interactionSource =
+            if (localIndication is IndicationNodeFactory) {
+                // We can fast path here as it will be created inside clickable lazily
+                null
+            } else {
+                // We need an interaction source to pass between the indication modifier and
+                // clickable, so
+                // by creating here we avoid another composed down the line
+                remember { MutableInteractionSource() }
+            }
+        Modifier.combinedClickable(
+            enabled = enabled,
+            onClickLabel = onClickLabel,
+            onLongClickLabel = onLongClickLabel,
+            onLongClick = onLongClick,
+            onDoubleClick = onDoubleClick,
+            onClick = onClick,
+            role = role,
+            indication = localIndication,
+            interactionSource = interactionSource,
+            hapticFeedbackEnabled = hapticFeedbackEnabled
+        )
+    }
+
+@Deprecated(message = "Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
+fun Modifier.combinedClickable(
+    enabled: Boolean = true,
+    onClickLabel: String? = null,
+    role: Role? = null,
+    onLongClickLabel: String? = null,
+    onLongClick: (() -> Unit)? = null,
+    onDoubleClick: (() -> Unit)? = null,
     onClick: () -> Unit
 ) =
     composed(
@@ -272,7 +326,8 @@
             onClick = onClick,
             role = role,
             indication = localIndication,
-            interactionSource = interactionSource
+            interactionSource = interactionSource,
+            hapticFeedbackEnabled = true
         )
     }
 
@@ -322,6 +377,7 @@
  * @param onLongClickLabel semantic / accessibility label for the [onLongClick] action
  * @param onLongClick will be called when user long presses on the element
  * @param onDoubleClick will be called when user double clicks on the element
+ * @param hapticFeedbackEnabled whether to use the default [HapticFeedback] behavior
  * @param onClick will be called when user clicks on the element
  */
 fun Modifier.combinedClickable(
@@ -333,6 +389,37 @@
     onLongClickLabel: String? = null,
     onLongClick: (() -> Unit)? = null,
     onDoubleClick: (() -> Unit)? = null,
+    hapticFeedbackEnabled: Boolean = true,
+    onClick: () -> Unit
+) =
+    clickableWithIndicationIfNeeded(
+        interactionSource = interactionSource,
+        indication = indication
+    ) { intSource, indicationNodeFactory ->
+        CombinedClickableElement(
+            interactionSource = intSource,
+            indicationNodeFactory = indicationNodeFactory,
+            enabled = enabled,
+            onClickLabel = onClickLabel,
+            role = role,
+            onClick = onClick,
+            onLongClickLabel = onLongClickLabel,
+            onLongClick = onLongClick,
+            onDoubleClick = onDoubleClick,
+            hapticFeedbackEnabled = hapticFeedbackEnabled
+        )
+    }
+
+@Deprecated(message = "Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
+fun Modifier.combinedClickable(
+    interactionSource: MutableInteractionSource?,
+    indication: Indication?,
+    enabled: Boolean = true,
+    onClickLabel: String? = null,
+    role: Role? = null,
+    onLongClickLabel: String? = null,
+    onLongClick: (() -> Unit)? = null,
+    onDoubleClick: (() -> Unit)? = null,
     onClick: () -> Unit
 ) =
     clickableWithIndicationIfNeeded(
@@ -348,7 +435,8 @@
             onClick = onClick,
             onLongClickLabel = onLongClickLabel,
             onLongClick = onLongClick,
-            onDoubleClick = onDoubleClick
+            onDoubleClick = onDoubleClick,
+            hapticFeedbackEnabled = true
         )
     }
 
@@ -481,7 +569,8 @@
     private val onClick: () -> Unit,
     private val onLongClickLabel: String?,
     private val onLongClick: (() -> Unit)?,
-    private val onDoubleClick: (() -> Unit)?
+    private val onDoubleClick: (() -> Unit)?,
+    private val hapticFeedbackEnabled: Boolean,
 ) : ModifierNodeElement<CombinedClickableNodeImpl>() {
     override fun create() =
         CombinedClickableNodeImpl(
@@ -489,6 +578,7 @@
             onLongClickLabel,
             onLongClick,
             onDoubleClick,
+            hapticFeedbackEnabled,
             interactionSource,
             indicationNodeFactory,
             enabled,
@@ -497,6 +587,7 @@
         )
 
     override fun update(node: CombinedClickableNodeImpl) {
+        node.hapticFeedbackEnabled = hapticFeedbackEnabled
         node.update(
             onClick,
             onLongClickLabel,
@@ -521,6 +612,7 @@
         properties["onDoubleClick"] = onDoubleClick
         properties["onLongClick"] = onLongClick
         properties["onLongClickLabel"] = onLongClickLabel
+        properties["hapticFeedbackEnabled"] = hapticFeedbackEnabled
     }
 
     override fun equals(other: Any?): Boolean {
@@ -539,6 +631,7 @@
         if (onLongClickLabel != other.onLongClickLabel) return false
         if (onLongClick !== other.onLongClick) return false
         if (onDoubleClick !== other.onDoubleClick) return false
+        if (hapticFeedbackEnabled != other.hapticFeedbackEnabled) return false
 
         return true
     }
@@ -553,6 +646,7 @@
         result = 31 * result + (onLongClickLabel?.hashCode() ?: 0)
         result = 31 * result + (onLongClick?.hashCode() ?: 0)
         result = 31 * result + (onDoubleClick?.hashCode() ?: 0)
+        result = 31 * result + hapticFeedbackEnabled.hashCode()
         return result
     }
 }
@@ -645,6 +739,7 @@
         onLongClickLabel,
         onLongClick,
         onDoubleClick,
+        hapticFeedbackEnabled = true,
         interactionSource,
         indicationNodeFactory,
         enabled,
@@ -696,6 +791,7 @@
     private var onLongClickLabel: String?,
     private var onLongClick: (() -> Unit)?,
     private var onDoubleClick: (() -> Unit)?,
+    var hapticFeedbackEnabled: Boolean,
     interactionSource: MutableInteractionSource?,
     indicationNodeFactory: IndicationNodeFactory?,
     enabled: Boolean,
@@ -722,7 +818,13 @@
                 } else null,
             onLongPress =
                 if (enabled && onLongClick != null) {
-                    { onLongClick?.invoke() }
+                    {
+                        onLongClick?.invoke()
+                        if (hapticFeedbackEnabled) {
+                            currentValueOf(LocalHapticFeedback)
+                                .performHapticFeedback(HapticFeedbackType.LongPress)
+                        }
+                    }
                 } else null,
             onPress = { offset ->
                 if (enabled) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
index 40708d7..fa72269 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
@@ -34,6 +34,8 @@
 import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
 import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.offset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
@@ -855,7 +857,7 @@
      * @see offset
      */
     fun requireOffset(): Float {
-        check(!offset.isNaN()) {
+        checkPrecondition(!offset.isNaN()) {
             "The offset was read before being initialized. Did you access the offset in a phase " +
                 "before layout, like effects or composition?"
         }
@@ -989,7 +991,7 @@
      */
     @Deprecated(SettleWithVelocityDeprecated, level = DeprecationLevel.WARNING)
     suspend fun settle(velocity: Float): Float {
-        require(usePreModifierChangeBehavior) {
+        requirePrecondition(usePreModifierChangeBehavior) {
             "AnchoredDraggableState was configured through " +
                 "a constructor without providing positional and velocity threshold. This " +
                 "overload of settle has been deprecated. Please refer to " +
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt
index fb8247b..3e41151 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.gestures
 
 import androidx.compose.foundation.gestures.ContentInViewNode.Request
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.ui.geometry.Rect
 import kotlin.contracts.ExperimentalContracts
@@ -130,6 +131,6 @@
         // cancelled, so we need to make a copy of the list before iterating to avoid concurrent
         // mutation.
         requests.map { it.continuation }.forEach { it.cancel(cause) }
-        check(requests.isEmpty()) { "uncancelled requests present" }
+        checkPrecondition(requests.isEmpty()) { "uncancelled requests present" }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
index e1cd608..c8c9f71 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.gestures.Orientation.Horizontal
 import androidx.compose.foundation.gestures.Orientation.Vertical
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.relocation.BringIntoViewRequester
 import androidx.compose.foundation.relocation.BringIntoViewResponder
 import androidx.compose.ui.Modifier
@@ -111,7 +112,7 @@
     private var isAnimationRunning = false
 
     override fun calculateRectForParent(localRect: Rect): Rect {
-        check(viewportSize != IntSize.Zero) {
+        checkPrecondition(viewportSize != IntSize.Zero) {
             "Expected BringIntoViewRequester to not be used before parents are placed."
         }
         // size will only be zero before the initial measurement.
@@ -187,7 +188,9 @@
 
     private fun launchAnimation() {
         val bringIntoViewSpec = requireBringIntoViewSpec()
-        check(!isAnimationRunning) { "launchAnimation called when previous animation was running" }
+        checkPrecondition(!isAnimationRunning) {
+            "launchAnimation called when previous animation was running"
+        }
 
         if (DEBUG) println("[$TAG] launchAnimation")
         val animationState = UpdatableAnimationState(BringIntoViewSpec.DefaultScrollAnimationSpec)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
index aab1f19..dd941ee 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
@@ -33,6 +33,7 @@
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -136,7 +137,7 @@
     zoomFactor: Float,
     animationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)
 ) {
-    require(zoomFactor > 0) { "zoom value should be greater than 0" }
+    requirePrecondition(zoomFactor > 0) { "zoom value should be greater than 0" }
     var previous = 1f
     transform {
         AnimationState(initialValue = previous).animateTo(zoomFactor, animationSpec) {
@@ -214,7 +215,7 @@
     offsetAnimationSpec: AnimationSpec<Offset> = SpringSpec(stiffness = Spring.StiffnessLow),
     rotationAnimationSpec: AnimationSpec<Float> = SpringSpec(stiffness = Spring.StiffnessLow)
 ) {
-    require(zoomFactor > 0) { "zoom value should be greater than 0" }
+    requirePrecondition(zoomFactor > 0) { "zoom value should be greater than 0" }
     var previousState = AnimationData(zoom = 1f, offset = Offset.Zero, degrees = 0f)
     val targetState = AnimationData(zoomFactor, offset, degrees)
     val animationSpec =
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt
index e6890fd..83dd7fd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/UpdatableAnimationState.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.AnimationState
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.VectorConverter
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.MotionDurationScale
 import kotlin.contracts.ExperimentalContracts
@@ -88,7 +89,7 @@
         afterFrame: () -> Unit,
     ) {
         contract { callsInPlace(beforeFrame) }
-        check(!isRunning) { "animateToZero called while previous animation is running" }
+        checkPrecondition(!isRunning) { "animateToZero called while previous animation is running" }
 
         val durationScale = coroutineContext[MotionDurationScale]?.scaleFactor ?: 1f
         isRunning = true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
index 1f7188c..f1b6803 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/PagerSnapLayoutInfoProvider.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.gestures.snapping
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.pager.PagerDebugConfig
 import androidx.compose.foundation.pager.PagerLayoutInfo
 import androidx.compose.foundation.pager.PagerSnapDistance
@@ -48,7 +49,7 @@
             val finalDistance =
                 calculateFinalSnappingBound(velocity, lowerBoundOffset, upperBoundOffset)
 
-            check(
+            checkPrecondition(
                 finalDistance == lowerBoundOffset ||
                     finalDistance == upperBoundOffset ||
                     finalDistance == 0.0f
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
index e4e0ac9..60d9705 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
@@ -33,6 +33,7 @@
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.TargetedFlingBehavior
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalDensity
@@ -118,7 +119,7 @@
                 val initialOffset =
                     snapLayoutInfoProvider.calculateApproachOffset(initialVelocity, decayOffset)
 
-                check(!initialOffset.isNaN()) {
+                checkPrecondition(!initialOffset.isNaN()) {
                     "calculateApproachOffset returned NaN. Please use a valid value."
                 }
 
@@ -136,7 +137,7 @@
                 val finalSnapOffset =
                     snapLayoutInfoProvider.calculateSnapOffset(animationState.velocity)
 
-                check(!finalSnapOffset.isNaN()) {
+                checkPrecondition(!finalSnapOffset.isNaN()) {
                     "calculateSnapOffset returned NaN. Please use a valid value."
                 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/internal/InlineClassHelper.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/internal/InlineClassHelper.kt
new file mode 100644
index 0000000..4af862a
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/internal/InlineClassHelper.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.foundation.internal
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// This function exists so we do *not* inline the throw. It keeps
+// the call site much smaller and since it's the slow path anyway,
+// we don't mind the extra function call
+internal fun throwIllegalStateException(message: String) {
+    throw IllegalStateException(message)
+}
+
+internal fun throwIllegalStateExceptionForNullCheck(message: String): Nothing {
+    throw IllegalStateException(message)
+}
+
+internal fun throwIllegalArgumentException(message: String) {
+    throw IllegalArgumentException(message)
+}
+
+internal fun throwIllegalArgumentExceptionForNullCheck(message: String): Nothing {
+    throw IllegalArgumentException(message)
+}
+
+internal fun throwIndexOutOfBoundsException(message: String) {
+    throw IndexOutOfBoundsException(message)
+}
+
+// Like Kotlin's check() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean, lazyMessage: () -> String) {
+    contract { returns() implies value }
+    if (!value) {
+        throwIllegalStateException(lazyMessage())
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun checkPrecondition(value: Boolean) {
+    contract { returns() implies value }
+    if (!value) {
+        throwIllegalStateException("Check failed.")
+    }
+}
+
+// Like Kotlin's checkNotNull() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?, lazyMessage: () -> String): T {
+    contract { returns() implies (value != null) }
+
+    if (value == null) {
+        throwIllegalStateExceptionForNullCheck(lazyMessage())
+    }
+
+    return value
+}
+
+// Like Kotlin's checkNotNull() but with a non-inline throw
+@Suppress("NOTHING_TO_INLINE", "BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> checkPreconditionNotNull(value: T?): T {
+    contract { returns() implies (value != null) }
+
+    if (value == null) {
+        throwIllegalStateExceptionForNullCheck("Required value was null.")
+    }
+
+    return value
+}
+
+// Like Kotlin's require() but without the .toString() call
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class) // same opt-in as using Kotlin's require()
+internal inline fun requirePrecondition(value: Boolean, lazyMessage: () -> String) {
+    contract { returns() implies value }
+    if (!value) {
+        throwIllegalArgumentException(lazyMessage())
+    }
+}
+
+// Like Kotlin's checkNotNull() but without the .toString() call and
+// a non-inline throw
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T : Any> requirePreconditionNotNull(value: T?, lazyMessage: () -> String): T {
+    contract { returns() implies (value != null) }
+
+    if (value == null) {
+        throwIllegalArgumentExceptionForNullCheck(lazyMessage())
+    }
+
+    return value
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 5e8c9f5..aecfa7c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
@@ -243,12 +244,12 @@
 
             val spaceBetweenItemsDp =
                 if (isVertical) {
-                    requireNotNull(verticalArrangement) {
+                    requirePreconditionNotNull(verticalArrangement) {
                             "null verticalArrangement when isVertical == true"
                         }
                         .spacing
                 } else {
-                    requireNotNull(horizontalArrangement) {
+                    requirePreconditionNotNull(horizontalArrangement) {
                             "null horizontalAlignment when isVertical == false"
                         }
                         .spacing
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 7fc0988..b5728a0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -17,6 +17,9 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
@@ -71,8 +74,8 @@
     graphicsContext: GraphicsContext,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyListMeasureResult {
-    require(beforeContentPadding >= 0) { "invalid beforeContentPadding" }
-    require(afterContentPadding >= 0) { "invalid afterContentPadding" }
+    requirePrecondition(beforeContentPadding >= 0) { "invalid beforeContentPadding" }
+    requirePrecondition(afterContentPadding >= 0) { "invalid afterContentPadding" }
     if (itemsCount <= 0) {
         // empty data set. reset the current scroll and report zero size
         var layoutWidth = constraints.minWidth
@@ -278,7 +281,9 @@
             } else 0f
 
         // the initial offset for items from visibleItems list
-        require(currentFirstItemScrollOffset >= 0) { "negative currentFirstItemScrollOffset" }
+        requirePrecondition(currentFirstItemScrollOffset >= 0) {
+            "negative currentFirstItemScrollOffset"
+        }
         val visibleItemsScrollOffset = -currentFirstItemScrollOffset
         var firstItem = visibleItems.first()
 
@@ -583,14 +588,16 @@
     val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
     val hasSpareSpace = finalMainAxisOffset < minOf(mainAxisLayoutSize, maxOffset)
     if (hasSpareSpace) {
-        check(itemsScrollOffset == 0) { "non-zero itemsScrollOffset" }
+        checkPrecondition(itemsScrollOffset == 0) { "non-zero itemsScrollOffset" }
     }
 
     val positionedItems =
         ArrayList<LazyListMeasuredItem>(items.size + extraItemsBefore.size + extraItemsAfter.size)
 
     if (hasSpareSpace) {
-        require(extraItemsBefore.isEmpty() && extraItemsAfter.isEmpty()) { "no extra items" }
+        requirePrecondition(extraItemsBefore.isEmpty() && extraItemsAfter.isEmpty()) {
+            "no extra items"
+        }
 
         val itemsCount = items.size
         fun Int.reverseAware() = if (!reverseLayout) this else itemsCount - this - 1
@@ -599,7 +606,7 @@
         val offsets = IntArray(itemsCount) { 0 }
         if (isVertical) {
             with(
-                requireNotNull(verticalArrangement) {
+                requirePreconditionNotNull(verticalArrangement) {
                     "null verticalArrangement when isVertical == true"
                 }
             ) {
@@ -607,7 +614,7 @@
             }
         } else {
             with(
-                requireNotNull(horizontalArrangement) {
+                requirePreconditionNotNull(horizontalArrangement) {
                     "null horizontalArrangement when isVertical == false"
                 }
             ) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
index 73a509f..bcee59d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimation.Companion.NotInitialized
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItem
@@ -128,7 +130,7 @@
             val indexInArray = index * 2
             if (isVertical) {
                 placeableOffsets[indexInArray] =
-                    requireNotNull(horizontalAlignment) {
+                    requirePreconditionNotNull(horizontalAlignment) {
                             "null horizontalAlignment when isVertical == true"
                         }
                         .align(placeable.width, layoutWidth, layoutDirection)
@@ -137,7 +139,7 @@
             } else {
                 placeableOffsets[indexInArray] = mainAxisOffset
                 placeableOffsets[indexInArray + 1] =
-                    requireNotNull(verticalAlignment) {
+                    requirePreconditionNotNull(verticalAlignment) {
                             "null verticalAlignment when isVertical == false"
                         }
                         .align(placeable.height, layoutHeight)
@@ -184,7 +186,7 @@
 
     fun place(scope: Placeable.PlacementScope, isLookingAhead: Boolean) =
         with(scope) {
-            require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+            requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
             repeat(placeablesCount) { index ->
                 val placeable = placeables[index]
                 val minOffset = minMainAxisOffset - placeable.mainAxisSize
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
index d8d9515..57fd4ca 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
@@ -17,6 +17,8 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.LazyLayoutNearestRangeState
 import androidx.compose.foundation.lazy.layout.findIndexByKey
 import androidx.compose.runtime.getValue
@@ -54,7 +56,7 @@
         if (hadFirstNotEmptyLayout || measureResult.totalItemsCount > 0) {
             hadFirstNotEmptyLayout = true
             val scrollOffset = measureResult.firstVisibleItemScrollOffset
-            check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+            checkPrecondition(scrollOffset >= 0f) { "scrollOffset should be non-negative" }
 
             val firstIndex = measureResult.firstVisibleItem?.index ?: 0
             update(firstIndex, scrollOffset)
@@ -62,7 +64,7 @@
     }
 
     fun updateScrollOffset(scrollOffset: Int) {
-        check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+        checkPrecondition(scrollOffset >= 0f) { "scrollOffset should be non-negative" }
         this.scrollOffset = scrollOffset
     }
 
@@ -103,7 +105,7 @@
     }
 
     private fun update(index: Int, scrollOffset: Int) {
-        require(index >= 0f) { "Index should be non-negative ($index)" }
+        requirePrecondition(index >= 0f) { "Index should be non-negative ($index)" }
         this.index = index
         nearestRangeState.update(index)
         this.scrollOffset = scrollOffset
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 98a2b18..e008329 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -31,6 +31,7 @@
 import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.lazy.LazyListState.Companion.Saver
 import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
 import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
@@ -400,8 +401,8 @@
         if (distance < 0 && !canScrollForward || distance > 0 && !canScrollBackward) {
             return 0f
         }
-        check(abs(scrollToBeConsumed) <= 0.5f) {
-            "entered drag with non-zero pending scroll: $scrollToBeConsumed"
+        checkPrecondition(abs(scrollToBeConsumed) <= 0.5f) {
+            "entered drag with non-zero pending scroll"
         }
         scrollToBeConsumed += distance
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
index 6611091..784f123 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
@@ -225,12 +226,12 @@
 
             val spaceBetweenLinesDp =
                 if (isVertical) {
-                    requireNotNull(verticalArrangement) {
+                    requirePreconditionNotNull(verticalArrangement) {
                             "null verticalArrangement when isVertical == true"
                         }
                         .spacing
                 } else {
-                    requireNotNull(horizontalArrangement) {
+                    requirePreconditionNotNull(horizontalArrangement) {
                             "null horizontalArrangement when isVertical == false"
                         }
                         .spacing
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
index dd07ff2..b814ee9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
@@ -155,7 +156,7 @@
         contentPadding,
     ) {
         GridSlotCache { constraints ->
-            require(constraints.maxWidth != Constraints.Infinity) {
+            requirePrecondition(constraints.maxWidth != Constraints.Infinity) {
                 "LazyVerticalGrid's width should be bound by parent."
             }
             val horizontalPadding =
@@ -189,7 +190,7 @@
         contentPadding,
     ) {
         GridSlotCache { constraints ->
-            require(constraints.maxHeight != Constraints.Infinity) {
+            requirePrecondition(constraints.maxHeight != Constraints.Infinity) {
                 "LazyHorizontalGrid's height should be bound by parent."
             }
             val verticalPadding =
@@ -269,7 +270,7 @@
      */
     class Fixed(private val count: Int) : GridCells {
         init {
-            require(count > 0) { "Provided count $count should be larger than zero" }
+            requirePrecondition(count > 0) { "Provided count should be larger than zero" }
         }
 
         override fun Density.calculateCrossAxisCellSizes(
@@ -298,7 +299,7 @@
      */
     class Adaptive(private val minSize: Dp) : GridCells {
         init {
-            require(minSize > 0.dp) { "Provided min size $minSize should be larger than zero." }
+            requirePrecondition(minSize > 0.dp) { "Provided min size should be larger than zero." }
         }
 
         override fun Density.calculateCrossAxisCellSizes(
@@ -331,7 +332,7 @@
      */
     class FixedSize(private val size: Dp) : GridCells {
         init {
-            require(size > 0.dp) { "Provided size $size should be larger than zero." }
+            requirePrecondition(size > 0.dp) { "Provided size should be larger than zero." }
         }
 
         override fun Density.calculateCrossAxisCellSizes(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
index e533ffb..549b2a7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
@@ -17,6 +17,9 @@
 package androidx.compose.foundation.lazy.grid
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
@@ -69,8 +72,8 @@
     prefetchInfoRetriever: (line: Int) -> List<Pair<Int, Constraints>>,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyGridMeasureResult {
-    require(beforeContentPadding >= 0) { "negative beforeContentPadding" }
-    require(afterContentPadding >= 0) { "negative afterContentPadding" }
+    requirePrecondition(beforeContentPadding >= 0) { "negative beforeContentPadding" }
+    requirePrecondition(afterContentPadding >= 0) { "negative afterContentPadding" }
     if (itemsCount <= 0) {
         // empty data set. reset the current scroll and report zero size
         var layoutWidth = constraints.minWidth
@@ -257,7 +260,7 @@
             }
 
         // the initial offset for lines from visibleLines list
-        require(currentFirstLineScrollOffset >= 0) { "negative initial offset" }
+        requirePrecondition(currentFirstLineScrollOffset >= 0) { "negative initial offset" }
         val visibleLinesScrollOffset = -currentFirstLineScrollOffset
         var firstLine = visibleLines.first()
 
@@ -438,24 +441,26 @@
     val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
     val hasSpareSpace = finalMainAxisOffset < min(mainAxisLayoutSize, maxOffset)
     if (hasSpareSpace) {
-        check(firstLineScrollOffset == 0) { "non-zero firstLineScrollOffset" }
+        checkPrecondition(firstLineScrollOffset == 0) { "non-zero firstLineScrollOffset" }
     }
 
     val positionedItems = ArrayList<LazyGridMeasuredItem>(lines.fastSumBy { it.items.size })
 
     if (hasSpareSpace) {
-        require(itemsBefore.isEmpty() && itemsAfter.isEmpty()) { "no items" }
+        requirePrecondition(itemsBefore.isEmpty() && itemsAfter.isEmpty()) { "no items" }
         val linesCount = lines.size
         fun Int.reverseAware() = if (!reverseLayout) this else linesCount - this - 1
 
         val sizes = IntArray(linesCount) { index -> lines[index.reverseAware()].mainAxisSize }
         val offsets = IntArray(linesCount) { 0 }
         if (isVertical) {
-            with(requireNotNull(verticalArrangement) { "null verticalArrangement" }) {
+            with(requirePreconditionNotNull(verticalArrangement) { "null verticalArrangement" }) {
                 density.arrange(mainAxisLayoutSize, sizes, offsets)
             }
         } else {
-            with(requireNotNull(horizontalArrangement) { "null horizontalArrangement" }) {
+            with(
+                requirePreconditionNotNull(horizontalArrangement) { "null horizontalArrangement" }
+            ) {
                 // Enforces Ltr layout direction as it is mirrored with placeRelative later.
                 density.arrange(mainAxisLayoutSize, sizes, LayoutDirection.Ltr, offsets)
             }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
index 2e5cfcb..72c3a2b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.grid
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItem
 import androidx.compose.ui.graphics.layer.GraphicsLayer
@@ -181,7 +182,7 @@
         scope: Placeable.PlacementScope,
     ) =
         with(scope) {
-            require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+            requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
             repeat(placeablesCount) { index ->
                 val placeable = placeables[index]
                 val minOffset = minMainAxisOffset - placeable.mainAxisSize
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
index f4716a1..5c33a25 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.lazy.grid
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.LazyLayoutKeyIndexMap
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItemProvider
@@ -62,7 +63,7 @@
             if (constraints.hasFixedWidth) {
                 constraints.minWidth
             } else {
-                require(constraints.hasFixedHeight) { "does not have fixed height" }
+                requirePrecondition(constraints.hasFixedHeight) { "does not have fixed height" }
                 constraints.minHeight
             }
         return createItem(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
index 36a2a29..e58b89d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
@@ -17,6 +17,8 @@
 package androidx.compose.foundation.lazy.grid
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.LazyLayoutNearestRangeState
 import androidx.compose.foundation.lazy.layout.findIndexByKey
 import androidx.compose.runtime.getValue
@@ -55,7 +57,9 @@
         if (hadFirstNotEmptyLayout || measureResult.totalItemsCount > 0) {
             hadFirstNotEmptyLayout = true
             val scrollOffset = measureResult.firstVisibleLineScrollOffset
-            check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+            checkPrecondition(scrollOffset >= 0f) {
+                "scrollOffset should be non-negative ($scrollOffset)"
+            }
 
             val firstIndex = measureResult.firstVisibleLine?.items?.firstOrNull()?.index ?: 0
             update(firstIndex, scrollOffset)
@@ -63,7 +67,7 @@
     }
 
     fun updateScrollOffset(scrollOffset: Int) {
-        check(scrollOffset >= 0f) { "scrollOffset should be non-negative ($scrollOffset)" }
+        checkPrecondition(scrollOffset >= 0f) { "scrollOffset should be non-negative" }
         this.scrollOffset = scrollOffset
     }
 
@@ -104,7 +108,7 @@
     }
 
     private fun update(index: Int, scrollOffset: Int) {
-        require(index >= 0f) { "Index should be non-negative ($index)" }
+        requirePrecondition(index >= 0f) { "Index should be non-negative" }
         this.index = index
         nearestRangeState.update(index)
         this.scrollOffset = scrollOffset
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt
index 648f756..26b71f9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpan.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.lazy.grid
 
 import androidx.annotation.IntRange
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.Immutable
 
 /** Represents the span of an item in a [LazyVerticalGrid] or a [LazyHorizontalGrid]. */
@@ -36,7 +37,7 @@
  * an item of a [LazyVerticalGrid] and the vertical span for a [LazyHorizontalGrid].
  */
 fun GridItemSpan(@IntRange(from = 1) currentLineSpan: Int): GridItemSpan {
-    require(currentLineSpan > 0) { "The span value should be higher than 0" }
+    requirePrecondition(currentLineSpan > 0) { "The span value should be higher than 0" }
     return GridItemSpan(currentLineSpan.toLong())
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
index c369ae6..b9dcbb46 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridSpanLayoutProvider.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.lazy.grid
 
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
 import kotlin.math.min
 import kotlin.math.sqrt
 
@@ -111,7 +113,7 @@
             cachedBucket.clear()
         }
 
-        check(currentLine <= lineIndex) { "currentLine > lineIndex" }
+        checkPrecondition(currentLine <= lineIndex) { "currentLine > lineIndex" }
 
         while (currentLine < lineIndex && currentItemIndex < totalSize) {
             if (cacheThisBucket) {
@@ -138,7 +140,7 @@
             if (currentLine % bucketSize == 0 && currentItemIndex < totalSize) {
                 val currentLineBucket = currentLine / bucketSize
                 // This should happen, as otherwise this should have been used as starting point.
-                check(buckets.size == currentLineBucket) { "invalid starting point" }
+                checkPrecondition(buckets.size == currentLineBucket) { "invalid starting point" }
                 buckets.add(Bucket(currentItemIndex, knownCurrentItemSpan))
             }
         }
@@ -172,7 +174,7 @@
         if (totalSize <= 0) {
             return 0
         }
-        require(itemIndex < totalSize) { "ItemIndex > total count" }
+        requirePrecondition(itemIndex < totalSize) { "ItemIndex > total count" }
         if (!gridContent.hasCustomSpans) {
             return itemIndex / slotsPerLine
         }
@@ -184,7 +186,7 @@
         var currentLine = lowerBoundBucket * bucketSize
         var currentItemIndex = buckets[lowerBoundBucket].firstItemIndex
 
-        require(currentItemIndex <= itemIndex) { "currentItemIndex > itemIndex" }
+        requirePrecondition(currentItemIndex <= itemIndex) { "currentItemIndex > itemIndex" }
         var spansUsed = 0
         while (currentItemIndex < itemIndex) {
             val span = spanOf(currentItemIndex++, slotsPerLine - spansUsed)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
index eb14a92..fc0832a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.gestures.stopScroll
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
 import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
@@ -385,8 +386,8 @@
         if (distance < 0 && !canScrollForward || distance > 0 && !canScrollBackward) {
             return 0f
         }
-        check(abs(scrollToBeConsumed) <= 0.5f) {
-            "entered drag with non-zero pending scroll: $scrollToBeConsumed"
+        checkPrecondition(abs(scrollToBeConsumed) <= 0.5f) {
+            "entered drag with non-zero pending scroll"
         }
         scrollToBeConsumed += distance
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
index 2a5e21e..aa3db4c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.lazy.layout
 
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.throwIndexOutOfBoundsException
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
 
@@ -75,8 +77,8 @@
         val value: T
     ) {
         init {
-            require(startIndex >= 0) { "startIndex should be >= 0, but was $startIndex" }
-            require(size > 0) { "size should be >0, but was $size" }
+            requirePrecondition(startIndex >= 0) { "startIndex should be >= 0" }
+            requirePrecondition(size > 0) { "size should be > 0" }
         }
     }
 }
@@ -106,7 +108,7 @@
      * @param value the value representing this interval.
      */
     fun addInterval(size: Int, value: T) {
-        require(size >= 0) { "size should be >=0, but was $size" }
+        requirePrecondition(size >= 0) { "size should be >=0" }
         if (size == 0) {
             return
         }
@@ -128,7 +130,7 @@
     override fun forEach(fromIndex: Int, toIndex: Int, block: (IntervalList.Interval<T>) -> Unit) {
         checkIndexBounds(fromIndex)
         checkIndexBounds(toIndex)
-        require(toIndex >= fromIndex) {
+        requirePrecondition(toIndex >= fromIndex) {
             "toIndex ($toIndex) should be not smaller than fromIndex ($fromIndex)"
         }
 
@@ -156,9 +158,10 @@
         }
     }
 
-    private fun checkIndexBounds(index: Int) {
+    @Suppress("NOTHING_TO_INLINE")
+    private inline fun checkIndexBounds(index: Int) {
         if (index !in 0 until size) {
-            throw IndexOutOfBoundsException("Index $index, size $size")
+            throwIndexOutOfBoundsException("Index $index, size $size")
         }
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
index 43e7e4c..c0c8cdc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.layout
 
+import androidx.collection.mutableObjectIntMapOf
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -84,7 +85,7 @@
 
 private class LazyLayoutItemReusePolicy(private val factory: LazyLayoutItemContentFactory) :
     SubcomposeSlotReusePolicy {
-    private val countPerType = mutableMapOf<Any?, Int>()
+    private val countPerType = mutableObjectIntMapOf<Any?>()
 
     override fun getSlotsToRetain(slotIds: SubcomposeSlotReusePolicy.SlotIdsSet) {
         countPerType.clear()
@@ -92,7 +93,7 @@
             while (hasNext()) {
                 val slotId = next()
                 val type = factory.getContentType(slotId)
-                val currentCount = countPerType[type] ?: 0
+                val currentCount = countPerType.getOrDefault(type, 0)
                 if (currentCount == MaxItemsToRetainForReuse) {
                     remove()
                 } else {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
index 03125f9..c889773 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.animateTo
 import androidx.compose.animation.core.copy
 import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import kotlin.coroutines.cancellation.CancellationException
@@ -104,7 +105,7 @@
     density: Density,
     scrollScope: ScrollScope
 ) {
-    require(index >= 0f) { "Index should be non-negative ($index)" }
+    requirePrecondition(index >= 0f) { "Index should be non-negative" }
 
     with(scrollScope) {
         try {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt
index 5403fed..714c98e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.layout
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.collection.mutableVectorOf
 
 /**
@@ -76,7 +77,7 @@
                     minIndex = it.start
                 }
             }
-            require(minIndex >= 0) { "negative minIndex" }
+            requirePrecondition(minIndex >= 0) { "negative minIndex" }
             return minIndex
         }
 
@@ -101,8 +102,8 @@
         val end: Int
     ) {
         init {
-            require(start >= 0) { "negative start index" }
-            require(end >= start) { "end index greater than start" }
+            requirePrecondition(start >= 0) { "negative start index" }
+            requirePrecondition(end >= start) { "end index greater than start" }
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
index f5746d1..8ea613b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutKeyIndexMap.kt
@@ -19,6 +19,7 @@
 import androidx.collection.MutableObjectIntMap
 import androidx.collection.ObjectIntMap
 import androidx.collection.emptyObjectIntMap
+import androidx.compose.foundation.internal.checkPrecondition
 
 /**
  * A key-index mapping used inside the [LazyLayoutItemProvider]. It might not contain all items in
@@ -58,7 +59,7 @@
         // all the indexes in the passed [range].
         val list = intervalContent.intervals
         val first = nearestRange.first
-        check(first >= 0) { "negative nearestRange.first" }
+        checkPrecondition(first >= 0) { "negative nearestRange.first" }
         val last = minOf(nearestRange.last, list.size - 1)
         if (last < first) {
             map = emptyObjectIntMap()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
index d6c9b25..53aeae9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
@@ -16,7 +16,9 @@
 
 package androidx.compose.foundation.lazy.layout
 
+import androidx.collection.mutableIntObjectMapOf
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.isSpecified
@@ -65,7 +67,7 @@
 
     @Stable
     override fun TextUnit.toDp(): Dp {
-        check(type == TextUnitType.Sp) { "Only Sp can convert to Px" }
+        checkPrecondition(type == TextUnitType.Sp) { "Only Sp can convert to Px" }
         return Dp(value * fontScale)
     }
 
@@ -109,7 +111,7 @@
      * A cache of the previously composed items. It allows us to support [get] re-executions with
      * the same index during the same measure pass.
      */
-    private val placeablesCache = hashMapOf<Int, List<Placeable>>()
+    private val placeablesCache = mutableIntObjectMapOf<List<Placeable>>()
 
     override fun measure(index: Int, constraints: Constraints): List<Placeable> {
         val cachedPlaceable = placeablesCache[index]
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
index 3ad177c5..354e747 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.layout
 
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
@@ -141,7 +142,7 @@
     }
 
     override fun release() {
-        check(pinsCount > 0) { "Release should only be called once" }
+        checkPrecondition(pinsCount > 0) { "Release should only be called once" }
         pinsCount--
         if (pinsCount == 0) {
             pinnedItemList.release(this)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
index 42d7668..78b15e44 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
@@ -18,6 +18,9 @@
 
 import androidx.collection.mutableObjectLongMapOf
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.PrefetchHandle
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
@@ -363,11 +366,11 @@
         }
 
         private fun performComposition() {
-            require(isValid) {
+            requirePrecondition(isValid) {
                 "Callers should check whether the request is still valid before calling " +
                     "performComposition()"
             }
-            require(precomposeHandle == null) { "Request was already composed!" }
+            requirePrecondition(precomposeHandle == null) { "Request was already composed!" }
             val itemProvider = itemContentFactory.itemProvider()
             val key = itemProvider.getKey(index)
             val contentType = itemProvider.getContentType(index)
@@ -376,14 +379,14 @@
         }
 
         private fun performMeasure(constraints: Constraints) {
-            require(!isCanceled) {
+            requirePrecondition(!isCanceled) {
                 "Callers should check whether the request is still valid before calling " +
                     "performMeasure()"
             }
-            require(!isMeasured) { "Request was already measured!" }
+            requirePrecondition(!isMeasured) { "Request was already measured!" }
             isMeasured = true
             val handle =
-                requireNotNull(precomposeHandle) {
+                requirePreconditionNotNull(precomposeHandle) {
                     "performComposition() must be called before performMeasure()"
                 }
             repeat(handle.placeablesCount) { placeableIndex ->
@@ -393,7 +396,7 @@
 
         private fun resolveNestedPrefetchStates(): NestedPrefetchController? {
             val precomposedSlotHandle =
-                requireNotNull(precomposeHandle) {
+                requirePreconditionNotNull(precomposeHandle) {
                     "Should precompose before resolving nested prefetch states"
                 }
 
@@ -422,7 +425,7 @@
             private var requestIndex: Int = 0
 
             init {
-                require(states.isNotEmpty()) {
+                requirePrecondition(states.isNotEmpty()) {
                     "NestedPrefetchController shouldn't be created with no states"
                 }
             }
@@ -431,7 +434,9 @@
                 if (stateIndex >= states.size) {
                     return false
                 }
-                check(!isCanceled) { "Should not execute nested prefetch on canceled request" }
+                checkPrecondition(!isCanceled) {
+                    "Should not execute nested prefetch on canceled request"
+                }
 
                 trace("compose:lazy:prefetch:nested") {
                     while (stateIndex < states.size) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
index a373688..1fead77 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.ModifierNodeElement
@@ -205,7 +206,7 @@
             if (userScrollEnabled) {
                 { index ->
                     val itemProvider = itemProviderLambda()
-                    require(index >= 0 && index < itemProvider.itemCount) {
+                    requirePrecondition(index >= 0 && index < itemProvider.itemCount) {
                         "Can't scroll to index $index, it is out of " +
                             "bounds [0, ${itemProvider.itemCount})"
                     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt
index 266a68a..f9636a0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazySaveableStateHolder.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.layout
 
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
@@ -73,7 +74,8 @@
 
     @Composable
     override fun SaveableStateProvider(key: Any, content: @Composable () -> Unit) {
-        requireNotNull(wrappedHolder) { "null wrappedHolder" }.SaveableStateProvider(key, content)
+        requirePreconditionNotNull(wrappedHolder) { "null wrappedHolder" }
+            .SaveableStateProvider(key, content)
         DisposableEffect(key) {
             previouslyComposedKeys -= key
             onDispose { previouslyComposedKeys += key }
@@ -81,7 +83,7 @@
     }
 
     override fun removeState(key: Any) {
-        requireNotNull(wrappedHolder) { "null wrappedHolder" }.removeState(key)
+        requirePreconditionNotNull(wrappedHolder) { "null wrappedHolder" }.removeState(key)
     }
 
     companion object {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt
index 26e2c0d..d549cd6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridCells.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.staggeredgrid
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.unit.Density
@@ -54,7 +55,7 @@
      */
     class Fixed(private val count: Int) : StaggeredGridCells {
         init {
-            require(count > 0) { "grid with no rows/columns" }
+            requirePrecondition(count > 0) { "grid with no rows/columns" }
         }
 
         override fun Density.calculateCrossAxisCellSizes(
@@ -84,7 +85,7 @@
      */
     class Adaptive(private val minSize: Dp) : StaggeredGridCells {
         init {
-            require(minSize > 0.dp) { "invalid minSize" }
+            requirePrecondition(minSize > 0.dp) { "invalid minSize" }
         }
 
         override fun Density.calculateCrossAxisCellSizes(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
index 9606d9e..edb7f89 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
@@ -101,7 +102,7 @@
         contentPadding,
     ) {
         LazyStaggeredGridSlotCache { constraints ->
-            require(constraints.maxWidth != Constraints.Infinity) {
+            requirePrecondition(constraints.maxWidth != Constraints.Infinity) {
                 "LazyVerticalStaggeredGrid's width should be bound by parent."
             }
             val horizontalPadding =
@@ -192,7 +193,7 @@
         contentPadding,
     ) {
         LazyStaggeredGridSlotCache { constraints ->
-            require(constraints.maxHeight != Constraints.Infinity) {
+            requirePrecondition(constraints.maxHeight != Constraints.Infinity) {
                 "LazyHorizontalStaggeredGrid's height should be bound by parent."
             }
             val verticalPadding =
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
index 44826b1..6c029fe 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.lazy.staggeredgrid
 
+import androidx.compose.foundation.internal.requirePrecondition
+
 /**
  * Utility class to remember grid lane assignments in a sliding window relative to requested item
  * position (usually reflected by scroll position). Remembers the maximum range of remembered items
@@ -31,7 +33,7 @@
 
     /** Sets given lane for given item index. */
     fun setLane(itemIndex: Int, lane: Int) {
-        require(itemIndex >= 0) { "Negative lanes are not supported" }
+        requirePrecondition(itemIndex >= 0) { "Negative lanes are not supported" }
         ensureValidIndex(itemIndex)
         lanes[itemIndex - anchor] = lane + 1
     }
@@ -185,7 +187,7 @@
     }
 
     private fun ensureCapacity(capacity: Int, newOffset: Int = 0) {
-        require(capacity <= MaxCapacity) {
+        requirePrecondition(capacity <= MaxCapacity) {
             "Requested item capacity $capacity is larger than max supported: $MaxCapacity!"
         }
         if (lanes.size < capacity) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index 1c54460..20061ed 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.lazy.staggeredgrid
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemAnimator
 import androidx.compose.foundation.lazy.layout.LazyLayoutKeyIndexMap
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
@@ -1226,7 +1227,7 @@
 
     fun place(scope: Placeable.PlacementScope, context: LazyStaggeredGridMeasureContext) =
         with(context) {
-            require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+            requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
             with(scope) {
                 placeables.fastForEachIndexed { index, placeable ->
                     val minOffset = minMainAxisOffset - placeable.mainAxisSize
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index d824897..bba2028 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -25,6 +25,8 @@
 import androidx.compose.foundation.gestures.stopScroll
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
@@ -248,8 +250,8 @@
         if (distance < 0 && !canScrollForward || distance > 0 && !canScrollBackward) {
             return 0f
         }
-        check(abs(scrollToBeConsumed) <= 0.5f) {
-            "entered drag with non-zero pending scroll: $scrollToBeConsumed"
+        checkPrecondition(abs(scrollToBeConsumed) <= 0.5f) {
+            "entered drag with non-zero pending scroll"
         }
         scrollToBeConsumed += distance
 
@@ -540,7 +542,7 @@
                 FullSpan -> 0
                 // lane was previously set, keep item to the same lane
                 else -> {
-                    require(previousLane >= 0) {
+                    requirePrecondition(previousLane >= 0) {
                         "Expected positive lane number, got $previousLane instead."
                     }
                     minOf(previousLane, laneCount)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
index 6e46ec2..ad3daca 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
@@ -30,6 +30,7 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.snapping.SnapPosition
 import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.lazy.layout.IntervalList
 import androidx.compose.foundation.lazy.layout.LazyLayout
@@ -100,7 +101,7 @@
     /** The content of the pager */
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
-    require(beyondViewportPageCount >= 0) {
+    requirePrecondition(beyondViewportPageCount >= 0) {
         "beyondViewportPageCount should be greater than or equal to 0, " +
             "you selected $beyondViewportPageCount"
     }
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 fcbe242..2146096 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
@@ -17,6 +17,8 @@
 package androidx.compose.foundation.pager
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.IntOffset
@@ -67,14 +69,14 @@
             val indexInArray = index * 2
             if (isVertical) {
                 placeableOffsets[indexInArray] =
-                    requireNotNull(horizontalAlignment) { "null horizontalAlignment" }
+                    requirePreconditionNotNull(horizontalAlignment) { "null horizontalAlignment" }
                         .align(placeable.width, layoutWidth, layoutDirection)
                 placeableOffsets[indexInArray + 1] = mainAxisOffset
                 mainAxisOffset += placeable.height
             } else {
                 placeableOffsets[indexInArray] = mainAxisOffset
                 placeableOffsets[indexInArray + 1] =
-                    requireNotNull(verticalAlignment) { "null verticalAlignment" }
+                    requirePreconditionNotNull(verticalAlignment) { "null verticalAlignment" }
                         .align(placeable.height, layoutHeight)
                 mainAxisOffset += placeable.width
             }
@@ -83,7 +85,7 @@
 
     fun place(scope: Placeable.PlacementScope) =
         with(scope) {
-            require(mainAxisLayoutSize != Unset) { "position() should be called first" }
+            requirePrecondition(mainAxisLayoutSize != Unset) { "position() should be called first" }
             repeat(placeables.size) { index ->
                 val placeable = placeables[index]
                 var offset = getOffset(index)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
index 7cfd681..6d51907 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
@@ -30,6 +30,7 @@
 import androidx.compose.foundation.gestures.snapping.SnapPosition
 import androidx.compose.foundation.gestures.snapping.calculateFinalSnappingBound
 import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -293,7 +294,7 @@
             ),
         @FloatRange(from = 0.0, to = 1.0) snapPositionalThreshold: Float = 0.5f
     ): TargetedFlingBehavior {
-        require(snapPositionalThreshold in 0f..1f) {
+        requirePrecondition(snapPositionalThreshold in 0f..1f) {
             "snapPositionalThreshold should be a number between 0 and 1. " +
                 "You've specified $snapPositionalThreshold"
         }
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 5bdfc9b..1333583 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
@@ -20,6 +20,8 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.snapping.SnapPosition
 import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
@@ -62,8 +64,8 @@
     coroutineScope: CoroutineScope,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): PagerMeasureResult {
-    require(beforeContentPadding >= 0) { "negative beforeContentPadding" }
-    require(afterContentPadding >= 0) { "negative afterContentPadding" }
+    requirePrecondition(beforeContentPadding >= 0) { "negative beforeContentPadding" }
+    requirePrecondition(afterContentPadding >= 0) { "negative afterContentPadding" }
     val pageSizeWithSpacing = (pageAvailableSize + spaceBetweenPages).coerceAtLeast(0)
 
     debugLog {
@@ -296,7 +298,9 @@
         }
 
         // the initial offset for pages from visiblePages list
-        require(currentFirstPageScrollOffset >= 0) { "invalid currentFirstPageScrollOffset" }
+        requirePrecondition(currentFirstPageScrollOffset >= 0) {
+            "invalid currentFirstPageScrollOffset"
+        }
         val visiblePagesScrollOffset = -currentFirstPageScrollOffset
 
         var firstPage = visiblePages.first()
@@ -612,13 +616,17 @@
     val mainAxisLayoutSize = if (orientation == Orientation.Vertical) layoutHeight else layoutWidth
     val hasSpareSpace = finalMainAxisOffset < minOf(mainAxisLayoutSize, maxOffset)
     if (hasSpareSpace) {
-        check(pagesScrollOffset == 0) { "non-zero pagesScrollOffset=$pagesScrollOffset" }
+        checkPrecondition(pagesScrollOffset == 0) {
+            "non-zero pagesScrollOffset=$pagesScrollOffset"
+        }
     }
     val positionedPages =
         ArrayList<MeasuredPage>(pages.size + extraPagesBefore.size + extraPagesAfter.size)
 
     if (hasSpareSpace) {
-        require(extraPagesBefore.isEmpty() && extraPagesAfter.isEmpty()) { "No extra pages" }
+        requirePrecondition(extraPagesBefore.isEmpty() && extraPagesAfter.isEmpty()) {
+            "No extra pages"
+        }
 
         val pagesCount = pages.size
         fun Int.reverseAware() = if (!reverseLayout) this else pagesCount - this - 1
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
index 17932be..4063c19 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.pager
 
 import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.Stable
 
 /**
@@ -55,7 +56,7 @@
          * @param pages The maximum number of extra pages that can be flung at once.
          */
         fun atMost(pages: Int): PagerSnapDistance {
-            require(pages >= 0) {
+            requirePrecondition(pages >= 0) {
                 "pages should be greater than or equal to 0. You have used $pages."
             }
             return PagerSnapDistanceMaxPages(pages)
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 5e1de2b..cffd236 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
@@ -30,6 +30,7 @@
 import androidx.compose.foundation.gestures.stopScroll
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
@@ -168,7 +169,7 @@
     abstract val pageCount: Int
 
     init {
-        require(currentPageOffsetFraction in -0.5..0.5) {
+        requirePrecondition(currentPageOffsetFraction in -0.5..0.5) {
             "currentPageOffsetFraction $currentPageOffsetFraction is " +
                 "not within the range -0.5 to 0.5"
         }
@@ -478,7 +479,7 @@
     ) = scroll {
         debugLog { "Scroll from page=$currentPage to page=$page" }
         awaitScrollDependencies()
-        require(pageOffsetFraction in -0.5..0.5) {
+        requirePrecondition(pageOffsetFraction in -0.5..0.5) {
             "pageOffsetFraction $pageOffsetFraction is not within the range -0.5 to 0.5"
         }
         val targetPage = page.coerceInPageRange()
@@ -581,7 +582,7 @@
         )
             return
         awaitScrollDependencies()
-        require(pageOffsetFraction in -0.5..0.5) {
+        requirePrecondition(pageOffsetFraction in -0.5..0.5) {
             "pageOffsetFraction $pageOffsetFraction is not within the range -0.5 to 0.5"
         }
         val targetPage = page.coerceInPageRange()
@@ -787,7 +788,9 @@
      * @return The offset of [page] with respect to [currentPage].
      */
     fun getOffsetDistanceInPages(page: Int): Float {
-        require(page in 0..pageCount) { "page $page is not within the range 0 to $pageCount" }
+        requirePrecondition(page in 0..pageCount) {
+            "page $page is not within the range 0 to $pageCount"
+        }
         return page - currentPage - currentPageOffsetFraction
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
index 2650ff4..9e8e967 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerBasedShape.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.shape
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Shape
@@ -58,7 +59,9 @@
             topEnd *= scale
             bottomEnd *= scale
         }
-        require(topStart >= 0.0f && topEnd >= 0.0f && bottomEnd >= 0.0f && bottomStart >= 0.0f) {
+        requirePrecondition(
+            topStart >= 0.0f && topEnd >= 0.0f && bottomEnd >= 0.0f && bottomStart >= 0.0f
+        ) {
             "Corner size in Px can't be negative(topStart = $topStart, topEnd = $topEnd, " +
                 "bottomEnd = $bottomEnd, bottomStart = $bottomStart)!"
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt
index 3cd43d0..3522308 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/HeightInLinesModifier.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
@@ -119,10 +120,10 @@
     }
 
 internal fun validateMinMaxLines(minLines: Int, maxLines: Int) {
-    require(minLines > 0 && maxLines > 0) {
+    requirePrecondition(minLines > 0 && maxLines > 0) {
         "both minLines $minLines and maxLines $maxLines must be greater than zero"
     }
-    require(minLines <= maxLines) {
+    requirePrecondition(minLines <= maxLines) {
         "minLines $minLines must be less than or equal to maxLines $maxLines"
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt
index c4c4ee8..6cd8718 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InlineTextContent.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.text.AnnotatedString
@@ -48,7 +49,7 @@
     id: String,
     alternateText: String = REPLACEMENT_CHAR
 ) {
-    require(alternateText.isNotEmpty()) { "alternateText can't be an empty string." }
+    requirePrecondition(alternateText.isNotEmpty()) { "alternateText can't be an empty string." }
     pushStringAnnotation(INLINE_CONTENT_TAG, id)
     append(alternateText)
     pop()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
index 75f76f8..b3125c6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.text.TextDelegate.Companion.paint
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.graphics.Canvas
@@ -112,9 +113,9 @@
         get() = nonNullIntrinsics.maxIntrinsicWidth.ceilToIntPx()
 
     init {
-        require(maxLines > 0) { "no maxLines" }
-        require(minLines > 0) { "no minLines" }
-        require(minLines <= maxLines) { "minLines greater than maxLines" }
+        requirePrecondition(maxLines > 0) { "no maxLines" }
+        requirePrecondition(minLines > 0) { "no minLines" }
+        requirePrecondition(minLines <= maxLines) { "minLines greater than maxLines" }
     }
 
     fun layoutIntrinsics(layoutDirection: LayoutDirection) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt
index 3bcd0d1..b6261ae 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ValidatingOffsetMapping.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.text
 
 import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.input.OffsetMapping
 import androidx.compose.ui.text.input.TransformedText
@@ -113,7 +114,7 @@
 }
 
 private fun validateTransformedToOriginal(originalOffset: Int, originalLength: Int, offset: Int) {
-    check(originalOffset in 0..originalLength) {
+    checkPrecondition(originalOffset in 0..originalLength) {
         "OffsetMapping.transformedToOriginal returned invalid mapping: " +
             "$offset -> $originalOffset is not in range of original text " +
             "[0, $originalLength]"
@@ -125,7 +126,7 @@
     transformedLength: Int,
     offset: Int
 ) {
-    check(transformedOffset in 0..transformedLength) {
+    checkPrecondition(transformedOffset in 0..transformedLength) {
         "OffsetMapping.originalToTransformed returned invalid mapping: " +
             "$offset -> $transformedOffset is not in range of transformed text " +
             "[0, $transformedLength]"
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
index 7fc64b1..77ec773 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/InputTransformation.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.text.input
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
@@ -222,7 +223,7 @@
 private data class MaxLengthFilter(private val maxLength: Int) : InputTransformation {
 
     init {
-        require(maxLength >= 0) { "maxLength must be at least zero, was $maxLength" }
+        requirePrecondition(maxLength >= 0) { "maxLength must be at least zero" }
     }
 
     override fun SemanticsPropertyReceiver.applySemantics() {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
index 9867e6d..85cbe37 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.text.input
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.text.input.TextFieldBuffer.ChangeList
 import androidx.compose.foundation.text.input.internal.ChangeTracker
 import androidx.compose.foundation.text.input.internal.OffsetMappingCalculator
@@ -162,8 +163,10 @@
         textStart: Int = 0,
         textEnd: Int = text.length
     ) {
-        require(start <= end) { "Expected start=$start <= end=$end" }
-        require(textStart <= textEnd) { "Expected textStart=$textStart <= textEnd=$textEnd" }
+        requirePrecondition(start <= end) { "Expected start=$start <= end=$end" }
+        requirePrecondition(textStart <= textEnd) {
+            "Expected textStart=$textStart <= textEnd=$textEnd"
+        }
         onTextWillChange(start, end, textEnd - textStart)
         buffer.replace(start, end, text, textStart, textEnd)
     }
@@ -341,12 +344,12 @@
         val start = if (startExclusive) 0 else -1
         val end = if (endExclusive) length else length + 1
 
-        require(index in start until end) { "Expected $index to be in [$start, $end)" }
+        requirePrecondition(index in start until end) { "Expected $index to be in [$start, $end)" }
     }
 
     private fun requireValidRange(range: TextRange) {
         val validRange = TextRange(0, length)
-        require(range in validRange) { "Expected $range to be in $validRange" }
+        requirePrecondition(range in validRange) { "Expected $range to be in $validRange" }
     }
 
     /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
index 772b2bf..f7637e9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldLineLimits.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.input
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.text.input.TextFieldLineLimits.MultiLine
 import androidx.compose.foundation.text.input.TextFieldLineLimits.SingleLine
@@ -57,7 +58,7 @@
     class MultiLine(val minHeightInLines: Int = 1, val maxHeightInLines: Int = Int.MAX_VALUE) :
         TextFieldLineLimits {
         init {
-            require(minHeightInLines in 1..maxHeightInLines) {
+            requirePrecondition(minHeightInLines in 1..maxHeightInLines) {
                 "Expected 1 ≤ minHeightInLines ≤ maxHeightInLines, were " +
                     "$minHeightInLines, $maxHeightInLines"
             }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
index 5d9b251a..d23fdb7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
@@ -20,6 +20,7 @@
 
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.text.input.internal.EditingBuffer
 import androidx.compose.foundation.text.input.internal.undo.TextFieldEditUndoBehavior
 import androidx.compose.runtime.Composable
@@ -200,7 +201,9 @@
     @PublishedApi
     internal fun startEdit(): TextFieldBuffer {
         val isEditingFreeze = Snapshot.withoutReadObservation { isEditing }
-        check(!isEditingFreeze) { "TextFieldState does not support concurrent or nested editing." }
+        checkPrecondition(!isEditingFreeze) {
+            "TextFieldState does not support concurrent or nested editing."
+        }
         isEditing = true
         return TextFieldBuffer(value)
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
index 058ed63..970b93c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditCommand.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.input.internal
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.text.findFollowingBreak
 import androidx.compose.foundation.text.findPrecedingBreak
 import androidx.compose.foundation.text.input.PlacedAnnotation
@@ -148,7 +149,7 @@
  *   be non-negative.
  */
 internal fun EditingBuffer.deleteSurroundingText(lengthBeforeCursor: Int, lengthAfterCursor: Int) {
-    require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
+    requirePrecondition(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
         "Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
             "$lengthBeforeCursor and $lengthAfterCursor respectively."
     }
@@ -182,7 +183,7 @@
     lengthBeforeCursor: Int,
     lengthAfterCursor: Int
 ) {
-    require(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
+    requirePrecondition(lengthBeforeCursor >= 0 && lengthAfterCursor >= 0) {
         "Expected lengthBeforeCursor and lengthAfterCursor to be non-negative, were " +
             "$lengthBeforeCursor and $lengthAfterCursor respectively."
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
index 198f266..2c72cbd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/EditingBuffer.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.input.internal
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.text.input.PlacedAnnotation
 import androidx.compose.foundation.text.input.TextHighlightType
 import androidx.compose.runtime.collection.MutableVector
@@ -54,7 +55,7 @@
     /** The inclusive selection start offset */
     var selectionStart = selection.start
         private set(value) {
-            require(value >= 0) { "Cannot set selectionStart to a negative value: $value" }
+            requirePrecondition(value >= 0) { "Cannot set selectionStart to a negative value" }
             field = value
             highlight = null
         }
@@ -62,7 +63,7 @@
     /** The exclusive selection end offset */
     var selectionEnd = selection.end
         private set(value) {
-            require(value >= 0) { "Cannot set selectionEnd to a negative value: $value" }
+            requirePrecondition(value >= 0) { "Cannot set selectionEnd to a negative value" }
             field = value
             highlight = null
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
index 98e60df..cc6ef10 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/GapBuffer.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.text.input.internal
 
+import androidx.compose.foundation.internal.requirePrecondition
+
 /**
  * The gap buffer implementation
  *
@@ -226,10 +228,10 @@
         textStart: Int = 0,
         textEnd: Int = text.length
     ) {
-        require(start <= end) { "start=$start > end=$end" }
-        require(textStart <= textEnd) { "textStart=$textStart > textEnd=$textEnd" }
-        require(start >= 0) { "start must be non-negative, but was $start" }
-        require(textStart >= 0) { "textStart must be non-negative, but was $textStart" }
+        requirePrecondition(start <= end) { "start=$start > end=$end" }
+        requirePrecondition(textStart <= textEnd) { "textStart=$textStart > textEnd=$textEnd" }
+        requirePrecondition(start >= 0) { "start must be non-negative, but was $start" }
+        requirePrecondition(textStart >= 0) { "textStart must be non-negative, but was $textStart" }
 
         val buffer = buffer
         val textLength = textEnd - textStart
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
index dd325d1..dc5d45f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.foundation.text.input.internal
 
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.text.LegacyTextFieldState
 import androidx.compose.foundation.text.selection.TextFieldSelectionManager
 import androidx.compose.ui.layout.LayoutCoordinates
@@ -47,12 +48,14 @@
         private set
 
     fun registerModifier(node: LegacyPlatformTextInputNode) {
-        check(textInputModifierNode == null) { "Expected textInputModifierNode to be null" }
+        checkPrecondition(textInputModifierNode == null) {
+            "Expected textInputModifierNode to be null"
+        }
         textInputModifierNode = node
     }
 
     fun unregisterModifier(node: LegacyPlatformTextInputNode) {
-        check(textInputModifierNode === node) {
+        checkPrecondition(textInputModifierNode === node) {
             "Expected textInputModifierNode to be $node but was $textInputModifierNode"
         }
         textInputModifierNode = null
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
index 423d93a..b6fb607 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/OffsetMappingCalculator.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.input.internal
 
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.ui.text.TextRange
 
 /**
@@ -262,7 +263,7 @@
      * [sourceEnd] (exclusive) in the original text with some text with length [newLength].
      */
     fun recordEditOperation(sourceStart: Int, sourceEnd: Int, newLength: Int) {
-        require(newLength >= 0) { "Expected newLen to be ≥ 0, was $newLength" }
+        requirePrecondition(newLength >= 0) { "Expected newLen to be ≥ 0, was $newLength" }
         val sourceMin = minOf(sourceStart, sourceEnd)
         val sourceMax = maxOf(sourceMin, sourceEnd)
         val sourceLength = sourceMax - sourceMin
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
index 6373697..0ae2aae 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.input.internal
 
+import androidx.compose.foundation.internal.checkPreconditionNotNull
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text.TextDelegate
 import androidx.compose.foundation.text.input.PlacedAnnotation
@@ -143,7 +144,7 @@
             )
         this.measureInputs = measureInputs
         val nonMeasureInputs =
-            checkNotNull(nonMeasureInputs) {
+            checkPreconditionNotNull(nonMeasureInputs) {
                 "Called layoutWithNewMeasureInputs before updateNonMeasureInputs"
             }
         return getOrComputeLayout(nonMeasureInputs, measureInputs)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
index 1c50185..2e8c440 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.text.input.internal.undo
 
+import androidx.compose.foundation.internal.checkPrecondition
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.SaverScope
 import androidx.compose.runtime.snapshots.SnapshotStateList
@@ -50,10 +52,9 @@
         get() = undoStack.size + redoStack.size
 
     init {
-        require(capacity >= 0) { "Capacity must be a positive integer" }
-        require(size <= capacity) {
-            "Initial list of undo and redo operations have a size=($size) greater " +
-                "than the given capacity=($capacity)."
+        requirePrecondition(capacity >= 0) { "Capacity must be a positive integer" }
+        requirePrecondition(size <= capacity) {
+            "Initial list of undo and redo operations have a size greater than the given capacity."
         }
     }
 
@@ -74,7 +75,7 @@
      * returns, the given item has already been carried to the redo stack.
      */
     fun undo(): T {
-        check(canUndo) {
+        checkPrecondition(canUndo) {
             "It's an error to call undo while there is nothing to undo. " +
                 "Please first check `canUndo` value before calling the `undo` function."
         }
@@ -92,7 +93,7 @@
      * returns, the given item has already been carried back to the undo stack.
      */
     fun redo(): T {
-        check(canRedo) {
+        checkPrecondition(canRedo) {
             "It's an error to call redo while there is nothing to redo. " +
                 "Please first check `canRedo` value before calling the `redo` function."
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
index 03b6402..c0be0a5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.modifiers
 
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.text.DefaultMinLines
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.ColorProducer
@@ -80,7 +81,7 @@
         )
 
     init {
-        requireNotNull(selectionController) {
+        requirePreconditionNotNull(selectionController) {
             "Do not use SelectionCapableStaticTextModifier unless selectionController != null"
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
index 3fa4234..02a1f7d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.modifiers
 
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.text.DefaultMinLines
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -397,7 +398,7 @@
 
         val layoutCache = getLayoutCache(this)
         val localParagraph =
-            requireNotNull(layoutCache.paragraph) {
+            requirePreconditionNotNull(layoutCache.paragraph) {
                 "no paragraph (layoutCache=$_layoutCache, textSubstitution=$textSubstitution)"
             }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
index abf85bc..51810a3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
@@ -23,6 +23,7 @@
 import androidx.collection.longObjectMapOf
 import androidx.collection.mutableLongIntMapOf
 import androidx.collection.mutableLongObjectMapOf
+import androidx.compose.foundation.internal.checkPrecondition
 import androidx.compose.foundation.text.selection.Direction.AFTER
 import androidx.compose.foundation.text.selection.Direction.BEFORE
 import androidx.compose.foundation.text.selection.Direction.ON
@@ -144,7 +145,7 @@
     override val previousSelection: Selection?,
 ) : SelectionLayout {
     init {
-        check(infoList.size > 1) {
+        checkPrecondition(infoList.size > 1) {
             "MultiSelectionLayout requires an infoList size greater than 1, was ${infoList.size}."
         }
     }
@@ -221,7 +222,7 @@
         if (selection.start.selectableId == selection.end.selectableId) {
             // this check, if not passed, leads to exceptions when selection
             // highlighting is rendered, so check here instead.
-            check(
+            checkPrecondition(
                 (selection.handlesCrossed && selection.start.offset >= selection.end.offset) ||
                     (!selection.handlesCrossed && selection.start.offset <= selection.end.offset)
             ) {
@@ -261,7 +262,7 @@
 
         // this check, if not passed, leads to exceptions when selection
         // highlighting is rendered, so check here instead.
-        check(minOffset <= maxOffset) {
+        checkPrecondition(minOffset <= maxOffset) {
             "minOffset should be less than or equal to maxOffset: $subSelection"
         }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 8fc5cb3..e911293af 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -24,6 +24,9 @@
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.foundation.internal.checkPreconditionNotNull
+import androidx.compose.foundation.internal.requirePrecondition
+import androidx.compose.foundation.internal.requirePreconditionNotNull
 import androidx.compose.foundation.text.Handle
 import androidx.compose.foundation.text.TextDragObserver
 import androidx.compose.foundation.text.input.internal.coerceIn
@@ -385,8 +388,8 @@
     /** Returns non-nullable [containerLayoutCoordinates]. */
     internal fun requireContainerCoordinates(): LayoutCoordinates {
         val coordinates = containerLayoutCoordinates
-        requireNotNull(coordinates) { "null coordinates" }
-        require(coordinates.isAttached) { "unattached coordinates" }
+        requirePreconditionNotNull(coordinates) { "null coordinates" }
+        requirePrecondition(coordinates.isAttached) { "unattached coordinates" }
         return coordinates
     }
 
@@ -658,7 +661,9 @@
                 val selection = selection!!
                 val anchor = if (isStartHandle) selection.start else selection.end
                 val selectable =
-                    checkNotNull(selectionRegistrar.selectableMap[anchor.selectableId]) {
+                    checkPreconditionNotNull(
+                        selectionRegistrar.selectableMap[anchor.selectableId]
+                    ) {
                         "SelectionRegistrar should contain the current selection's selectableIds"
                     }
 
@@ -666,7 +671,7 @@
                 // is used to convert the position of the beginning of the drag gesture from the
                 // composable coordinates to selection container coordinates.
                 val beginLayoutCoordinates =
-                    checkNotNull(selectable.getLayoutCoordinates()) {
+                    checkPreconditionNotNull(selectable.getLayoutCoordinates()) {
                         "Current selectable should have layout coordinates."
                     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
index 95052e3..c681888 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
@@ -20,6 +20,7 @@
 import androidx.collection.emptyLongObjectMap
 import androidx.collection.mutableLongObjectMapOf
 import androidx.compose.foundation.AtomicLong
+import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.saveable.Saver
@@ -99,10 +100,10 @@
     override var subselections: LongObjectMap<Selection> by mutableStateOf(emptyLongObjectMap())
 
     override fun subscribe(selectable: Selectable): Selectable {
-        require(selectable.selectableId != SelectionRegistrar.InvalidSelectableId) {
+        requirePrecondition(selectable.selectableId != SelectionRegistrar.InvalidSelectableId) {
             "The selectable contains an invalid id: ${selectable.selectableId}"
         }
-        require(!_selectableMap.containsKey(selectable.selectableId)) {
+        requirePrecondition(!_selectableMap.containsKey(selectable.selectableId)) {
             "Another selectable with the id: $selectable.selectableId has already subscribed."
         }
         _selectableMap[selectable.selectableId] = selectable
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index 417619c..51fe754 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -44,7 +44,7 @@
                 api(project(":compose:foundation:foundation"))
                 api(project(":compose:runtime:runtime"))
 
-                implementation(project(":collection:collection"))
+                implementation("androidx.collection:collection:1.4.2")
                 implementation(project(":compose:animation:animation"))
                 implementation(project(":compose:ui:ui-util"))
             }
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt
index 86a552e..f971352 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationRailBenchmark.kt
@@ -21,16 +21,20 @@
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.MaterialExpressiveTheme
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalExpandedNavigationRail
+import androidx.compose.material3.ModalExpandedNavigationRailState
 import androidx.compose.material3.NavigationRail
 import androidx.compose.material3.NavigationRailItem
 import androidx.compose.material3.WideNavigationRail
 import androidx.compose.material3.WideNavigationRailItem
+import androidx.compose.material3.rememberModalExpandedNavigationRailState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableIntState
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.testutils.LayeredComposeTestCase
 import androidx.compose.testutils.ToggleableTestCase
 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
@@ -40,6 +44,8 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -52,6 +58,7 @@
     private val testCaseFactory = { NavigationRailTestCase() }
     private val collapsedWideRailTestCaseFactory = { NavigationRailTestCase(true) }
     private val expandedWideRailTestCaseFactory = { NavigationRailTestCase(true, true) }
+    private val modalExpandedRailTestCaseFactory = { ModalExpandedRailTestCase() }
 
     @Test
     fun firstPixel() {
@@ -113,6 +120,19 @@
             assertOneRecomposition = false,
         )
     }
+
+    @Test
+    fun modalExpandedNavigationRail_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel(modalExpandedRailTestCaseFactory)
+    }
+
+    @Test
+    fun modalExpandedNavigationRail_stateChange() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+            modalExpandedRailTestCaseFactory,
+            assertOneRecomposition = false,
+        )
+    }
 }
 
 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -181,3 +201,48 @@
         }
     }
 }
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+internal class ModalExpandedRailTestCase() : LayeredComposeTestCase(), ToggleableTestCase {
+    private lateinit var state: ModalExpandedNavigationRailState
+    private lateinit var scope: CoroutineScope
+
+    @Composable
+    override fun MeasuredContent() {
+        state = rememberModalExpandedNavigationRailState()
+        scope = rememberCoroutineScope()
+
+        ModalExpandedNavigationRail(
+            onDismissRequest = {},
+            railState = state,
+        ) {
+            WideNavigationRailItem(
+                selected = true,
+                onClick = {},
+                icon = { Spacer(Modifier.size(24.dp)) },
+                railExpanded = true,
+                label = { Spacer(Modifier.size(24.dp)) }
+            )
+            WideNavigationRailItem(
+                selected = false,
+                onClick = {},
+                icon = { Spacer(Modifier.size(24.dp)) },
+                railExpanded = true,
+                label = { Spacer(Modifier.size(24.dp)) }
+            )
+        }
+    }
+
+    @Composable
+    override fun ContentWrappers(content: @Composable () -> Unit) {
+        MaterialExpressiveTheme { content() }
+    }
+
+    override fun toggleState() {
+        if (state.isOpen) {
+            scope.launch { state.close() }
+        } else {
+            scope.launch { state.open() }
+        }
+    }
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 2c99698..39be560 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -235,6 +235,22 @@
   @androidx.compose.runtime.Stable public final class ButtonElevation {
   }
 
+  public final class ButtonGroupDefaults {
+    method public float getAnimateFraction();
+    method public float getSpaceBetween();
+    property public final float animateFraction;
+    property public final float spaceBetween;
+    field public static final androidx.compose.material3.ButtonGroupDefaults INSTANCE;
+  }
+
+  public final class ButtonGroupKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ButtonGroup(optional androidx.compose.ui.Modifier modifier, optional @FloatRange(from=0.0) float animateFraction, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1<? super androidx.compose.material3.ButtonGroupScope,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public interface ButtonGroupScope {
+    method public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight, optional boolean fill);
+  }
+
   public final class ButtonKt {
     method @androidx.compose.runtime.Composable public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void ElevatedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
@@ -244,21 +260,15 @@
   }
 
   public final class ButtonShapes {
-    ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
     method public androidx.compose.ui.graphics.Shape component1();
     method public androidx.compose.ui.graphics.Shape component2();
     method public androidx.compose.ui.graphics.Shape component3();
-    method public androidx.compose.ui.graphics.Shape component4();
-    method public androidx.compose.ui.graphics.Shape component5();
-    method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
     method public androidx.compose.ui.graphics.Shape getCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getHoveredShape();
     method public androidx.compose.ui.graphics.Shape getPressedShape();
     method public androidx.compose.ui.graphics.Shape getShape();
     property public final androidx.compose.ui.graphics.Shape checkedShape;
-    property public final androidx.compose.ui.graphics.Shape focusedShape;
-    property public final androidx.compose.ui.graphics.Shape hoveredShape;
     property public final androidx.compose.ui.graphics.Shape pressedShape;
     property public final androidx.compose.ui.graphics.Shape shape;
   }
@@ -957,36 +967,72 @@
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getLargeIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSquareShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getMediumIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRoundShape();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallContainerSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getSmallIconSize();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallNarrowContainerSize();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallWideContainerSize();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getStandardShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXLargeIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSquareShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXSmallIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long largeContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long mediumContainerSize(optional int widthOption);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedIconButtonBorder(boolean enabled);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonBorder(boolean enabled, boolean checked);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallContainerSize;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float SmallIconSize;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallNarrowContainerSize;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallWideContainerSize;
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xSmallContainerSize(optional int widthOption);
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape filledShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float largeIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSquareShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float mediumIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape roundShape;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float smallIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape standardShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xLargeIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSquareShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xSmallIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSquareShape;
     field public static final androidx.compose.material3.IconButtonDefaults INSTANCE;
   }
 
+  @kotlin.jvm.JvmInline public static final value class IconButtonDefaults.IconButtonWidthOption {
+    field public static final androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion Companion;
+  }
+
+  public static final class IconButtonDefaults.IconButtonWidthOption.Companion {
+    method public int getNarrow();
+    method public int getUniform();
+    method public int getWide();
+    property public final int Narrow;
+    property public final int Uniform;
+    property public final int Wide;
+  }
+
   public final class IconButtonKt {
     method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -2516,18 +2562,12 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getCheckedShape();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getElevatedFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getElevatedHoveredShape();
     method public androidx.compose.ui.graphics.Shape getElevatedPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
-    method public androidx.compose.ui.graphics.Shape getFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getHoveredShape();
     method public float getIconSize();
     method public float getIconSpacing();
     method public float getMinHeight();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getOutlinedFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getOutlinedHoveredShape();
     method public androidx.compose.ui.graphics.Shape getOutlinedPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
     method public androidx.compose.ui.graphics.Shape getPressedShape();
@@ -2535,13 +2575,11 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getTonalFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getTonalHoveredShape();
     method public androidx.compose.ui.graphics.Shape getTonalPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoverShape, androidx.compose.ui.graphics.Shape focusShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors tonalToggleButtonColors();
@@ -2552,15 +2590,9 @@
     property public final float MinHeight;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape checkedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape elevatedFocusedShape;
-    property public final androidx.compose.ui.graphics.Shape elevatedHoveredShape;
     property public final androidx.compose.ui.graphics.Shape elevatedPressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedShape;
-    property public final androidx.compose.ui.graphics.Shape focusedShape;
-    property public final androidx.compose.ui.graphics.Shape hoveredShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape outlinedFocusedShape;
-    property public final androidx.compose.ui.graphics.Shape outlinedHoveredShape;
     property public final androidx.compose.ui.graphics.Shape outlinedPressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
     property public final androidx.compose.ui.graphics.Shape pressedShape;
@@ -2568,8 +2600,6 @@
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape tonalFocusedShape;
-    property public final androidx.compose.ui.graphics.Shape tonalHoveredShape;
     property public final androidx.compose.ui.graphics.Shape tonalPressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalShape;
     field public static final androidx.compose.material3.ToggleButtonDefaults INSTANCE;
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 2c99698..39be560 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -235,6 +235,22 @@
   @androidx.compose.runtime.Stable public final class ButtonElevation {
   }
 
+  public final class ButtonGroupDefaults {
+    method public float getAnimateFraction();
+    method public float getSpaceBetween();
+    property public final float animateFraction;
+    property public final float spaceBetween;
+    field public static final androidx.compose.material3.ButtonGroupDefaults INSTANCE;
+  }
+
+  public final class ButtonGroupKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public static void ButtonGroup(optional androidx.compose.ui.Modifier modifier, optional @FloatRange(from=0.0) float animateFraction, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1<? super androidx.compose.material3.ButtonGroupScope,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public interface ButtonGroupScope {
+    method public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, @FloatRange(from=0.0, fromInclusive=false) float weight, optional boolean fill);
+  }
+
   public final class ButtonKt {
     method @androidx.compose.runtime.Composable public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void ElevatedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ButtonColors colors, optional androidx.compose.material3.ButtonElevation? elevation, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
@@ -244,21 +260,15 @@
   }
 
   public final class ButtonShapes {
-    ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
     method public androidx.compose.ui.graphics.Shape component1();
     method public androidx.compose.ui.graphics.Shape component2();
     method public androidx.compose.ui.graphics.Shape component3();
-    method public androidx.compose.ui.graphics.Shape component4();
-    method public androidx.compose.ui.graphics.Shape component5();
-    method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoveredShape, androidx.compose.ui.graphics.Shape focusedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.ButtonShapes copy(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
     method public androidx.compose.ui.graphics.Shape getCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getHoveredShape();
     method public androidx.compose.ui.graphics.Shape getPressedShape();
     method public androidx.compose.ui.graphics.Shape getShape();
     property public final androidx.compose.ui.graphics.Shape checkedShape;
-    property public final androidx.compose.ui.graphics.Shape focusedShape;
-    property public final androidx.compose.ui.graphics.Shape hoveredShape;
     property public final androidx.compose.ui.graphics.Shape pressedShape;
     property public final androidx.compose.ui.graphics.Shape shape;
   }
@@ -957,36 +967,72 @@
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors filledTonalIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getLargeIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getLargeSquareShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getMediumIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMediumSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRoundShape();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallContainerSize();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getSmallIconSize();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallNarrowContainerSize();
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long getSmallWideContainerSize();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getStandardShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXLargeIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXLargeSquareShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public float getXSmallIconSize();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallRoundShape();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getXSmallSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors iconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long largeContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long mediumContainerSize(optional int widthOption);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke outlinedIconButtonBorder(boolean enabled);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconButtonColors outlinedIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.BorderStroke? outlinedIconToggleButtonBorder(boolean enabled, boolean checked);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.IconToggleButtonColors outlinedIconToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallContainerSize;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float SmallIconSize;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallNarrowContainerSize;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final long SmallWideContainerSize;
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long smallContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xLargeContainerSize(optional int widthOption);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public long xSmallContainerSize(optional int widthOption);
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape filledShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float largeIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape largeSquareShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float mediumIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape mediumSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape roundShape;
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float smallIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape smallSquareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape standardShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xLargeIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xLargeSquareShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final float xSmallIconSize;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallRoundShape;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape xSmallSquareShape;
     field public static final androidx.compose.material3.IconButtonDefaults INSTANCE;
   }
 
+  @kotlin.jvm.JvmInline public static final value class IconButtonDefaults.IconButtonWidthOption {
+    field public static final androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion Companion;
+  }
+
+  public static final class IconButtonDefaults.IconButtonWidthOption.Companion {
+    method public int getNarrow();
+    method public int getUniform();
+    method public int getWide();
+    property public final int Narrow;
+    property public final int Uniform;
+    property public final int Wide;
+  }
+
   public final class IconButtonKt {
     method @androidx.compose.runtime.Composable public static void FilledIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void FilledIconToggleButton(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.IconToggleButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
@@ -2516,18 +2562,12 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getCheckedShape();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getElevatedFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getElevatedHoveredShape();
     method public androidx.compose.ui.graphics.Shape getElevatedPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
-    method public androidx.compose.ui.graphics.Shape getFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getHoveredShape();
     method public float getIconSize();
     method public float getIconSpacing();
     method public float getMinHeight();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getOutlinedFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getOutlinedHoveredShape();
     method public androidx.compose.ui.graphics.Shape getOutlinedPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
     method public androidx.compose.ui.graphics.Shape getPressedShape();
@@ -2535,13 +2575,11 @@
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getTonalFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getTonalHoveredShape();
     method public androidx.compose.ui.graphics.Shape getTonalPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape hoverShape, androidx.compose.ui.graphics.Shape focusShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors tonalToggleButtonColors();
@@ -2552,15 +2590,9 @@
     property public final float MinHeight;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape checkedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape elevatedFocusedShape;
-    property public final androidx.compose.ui.graphics.Shape elevatedHoveredShape;
     property public final androidx.compose.ui.graphics.Shape elevatedPressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedShape;
-    property public final androidx.compose.ui.graphics.Shape focusedShape;
-    property public final androidx.compose.ui.graphics.Shape hoveredShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape outlinedFocusedShape;
-    property public final androidx.compose.ui.graphics.Shape outlinedHoveredShape;
     property public final androidx.compose.ui.graphics.Shape outlinedPressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
     property public final androidx.compose.ui.graphics.Shape pressedShape;
@@ -2568,8 +2600,6 @@
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape tonalFocusedShape;
-    property public final androidx.compose.ui.graphics.Shape tonalHoveredShape;
     property public final androidx.compose.ui.graphics.Shape tonalPressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalShape;
     field public static final androidx.compose.material3.ToggleButtonDefaults INSTANCE;
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
index 815ed00..895f22c 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
@@ -117,6 +117,18 @@
         examples = ButtonsExamples,
     )
 
+private val ButtonGroups =
+    Component(
+        id = nextId(),
+        name = "Button Groups",
+        description =
+            "button groups is a container for material components that adds an animation on press",
+        guidelinesUrl = "$ComponentGuidelinesUrl/button-groups",
+        docsUrl = "$PackageSummaryUrl#buttongroups",
+        sourceUrl = "$Material3SourceUrl/ButtonGroup.kt",
+        examples = ButtonGroupsExamples,
+    )
+
 private val Card =
     Component(
         id = nextId(),
@@ -556,6 +568,7 @@
         BottomAppBars,
         BottomSheets,
         Buttons,
+        ButtonGroups,
         Card,
         Carousel,
         Checkboxes,
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index 79e0183..61d227f 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -35,6 +35,7 @@
 import androidx.compose.material3.samples.BasicAlertDialogSample
 import androidx.compose.material3.samples.BottomAppBarWithFAB
 import androidx.compose.material3.samples.BottomSheetScaffoldNestedScrollSample
+import androidx.compose.material3.samples.ButtonGroupSample
 import androidx.compose.material3.samples.ButtonSample
 import androidx.compose.material3.samples.ButtonWithIconSample
 import androidx.compose.material3.samples.CardSample
@@ -108,6 +109,7 @@
 import androidx.compose.material3.samples.LargeExtendedFloatingActionButtonSample
 import androidx.compose.material3.samples.LargeExtendedFloatingActionButtonTextSample
 import androidx.compose.material3.samples.LargeFloatingActionButtonSample
+import androidx.compose.material3.samples.LargeRoundUniformOutlinedIconButtonSample
 import androidx.compose.material3.samples.LeadingIconTabs
 import androidx.compose.material3.samples.LinearProgressIndicatorSample
 import androidx.compose.material3.samples.LinearWavyProgressIndicatorSample
@@ -117,6 +119,7 @@
 import androidx.compose.material3.samples.MediumExtendedFloatingActionButtonSample
 import androidx.compose.material3.samples.MediumExtendedFloatingActionButtonTextSample
 import androidx.compose.material3.samples.MediumFloatingActionButtonSample
+import androidx.compose.material3.samples.MediumRoundWideIconButtonSample
 import androidx.compose.material3.samples.MenuSample
 import androidx.compose.material3.samples.MenuWithScrollStateSample
 import androidx.compose.material3.samples.ModalBottomSheetSample
@@ -229,6 +232,7 @@
 import androidx.compose.material3.samples.WideNavigationRailCollapsedSample
 import androidx.compose.material3.samples.WideNavigationRailExpandedSample
 import androidx.compose.material3.samples.WideNavigationRailResponsiveSample
+import androidx.compose.material3.samples.XSmallNarrowSquareIconButtonsSample
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
@@ -369,6 +373,19 @@
         }
     )
 
+private const val ButtonGroupsExampleDescription = "ButtonGroup examples"
+private const val ButtonGroupsExampleSourceUrl = "$SampleSourceUrl/ButtonGroupSamples.kt"
+val ButtonGroupsExamples =
+    listOf(
+        Example(
+            name = "ButtonGroupSample",
+            description = ButtonGroupsExampleDescription,
+            sourceUrl = ButtonGroupsExampleSourceUrl,
+        ) {
+            ButtonGroupSample()
+        }
+    )
+
 private const val CardsExampleDescription = "Cards examples"
 private const val CardsExampleSourceUrl = "$SampleSourceUrl/CardSamples.kt"
 val CardExamples =
@@ -1013,6 +1030,27 @@
             sourceUrl = IconButtonExampleSourceUrl,
         ) {
             OutlinedIconToggleButtonSample()
+        },
+        Example(
+            name = "XSmallNarrowSquareIconButtonsSample",
+            description = IconButtonExampleDescription,
+            sourceUrl = IconButtonExampleSourceUrl,
+        ) {
+            XSmallNarrowSquareIconButtonsSample()
+        },
+        Example(
+            name = "MediumRoundWideIconButtonSample",
+            description = IconButtonExampleDescription,
+            sourceUrl = IconButtonExampleSourceUrl,
+        ) {
+            MediumRoundWideIconButtonSample()
+        },
+        Example(
+            name = "LargeRoundUniformOutlinedIconButtonSample",
+            description = IconButtonExampleDescription,
+            sourceUrl = IconButtonExampleSourceUrl,
+        ) {
+            LargeRoundUniformOutlinedIconButtonSample()
         }
     )
 
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
new file mode 100644
index 0000000..ef12156
--- /dev/null
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/IconButtonDemos.kt
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2024 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.demos
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Lock
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.FilledIconButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Narrow
+import androidx.compose.material3.IconButtonDefaults.IconButtonWidthOption.Companion.Wide
+import androidx.compose.material3.OutlinedIconButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.minimumInteractiveComponentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun IconButtonMeasurementsDemo() {
+    val rowScrollState = rememberScrollState()
+    Row(modifier = Modifier.horizontalScroll(rowScrollState)) {
+        val columnScrollState = rememberScrollState()
+        val padding = 16.dp
+        Column(
+            modifier = Modifier.padding(horizontal = padding).verticalScroll(columnScrollState),
+        ) {
+            Spacer(modifier = Modifier.height(padding + 48.dp))
+            Text("XSmall", modifier = Modifier.height(48.dp + padding))
+            Text("Small", modifier = Modifier.height(48.dp + padding))
+            Text("Medium", modifier = Modifier.height(56.dp + padding))
+            Text("Large", modifier = Modifier.height(96.dp + padding))
+            Text("XLarge", modifier = Modifier.height(136.dp + padding))
+        }
+
+        // Default
+        Column(
+            modifier =
+                Modifier.padding(horizontal = padding)
+                    .width(136.dp)
+                    .verticalScroll(columnScrollState),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(padding)
+        ) {
+            Text("Default", modifier = Modifier.height(48.dp))
+            // XSmall uniform round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.xSmallContainerSize()),
+                shape = IconButtonDefaults.xSmallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+                )
+            }
+
+            // Small uniform round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.smallContainerSize()),
+                shape = IconButtonDefaults.smallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+                )
+            }
+
+            // Medium uniform round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.mediumContainerSize()),
+                shape = IconButtonDefaults.mediumRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+                )
+            }
+
+            // Large uniform round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.largeContainerSize()),
+                shape = IconButtonDefaults.largeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+                )
+            }
+
+            // XLarge uniform round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.xLargeContainerSize()),
+                shape = IconButtonDefaults.xLargeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+                )
+            }
+        }
+
+        // Narrow
+        Column(
+            modifier =
+                Modifier.padding(horizontal = padding)
+                    .width(104.dp)
+                    .verticalScroll(columnScrollState),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(padding)
+        ) {
+            Text("Narrow", modifier = Modifier.height(48.dp))
+
+            // XSmall narrow round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.xSmallContainerSize(Narrow)),
+                shape = IconButtonDefaults.xSmallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+                )
+            }
+
+            // Small narrow round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.smallContainerSize(Narrow)),
+                shape = IconButtonDefaults.smallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+                )
+            }
+
+            // Medium narrow round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.mediumContainerSize(Narrow)),
+                shape = IconButtonDefaults.mediumRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+                )
+            }
+
+            // Large narrow round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.largeContainerSize(Narrow)),
+                shape = IconButtonDefaults.largeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+                )
+            }
+
+            // XLarge narrow round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.xLargeContainerSize(Narrow)),
+                shape = IconButtonDefaults.xLargeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+                )
+            }
+        }
+
+        // Wide
+        Column(
+            modifier =
+                Modifier.padding(horizontal = padding)
+                    .width(184.dp)
+                    .verticalScroll(columnScrollState),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(padding)
+        ) {
+            Text("Wide", modifier = Modifier.height(48.dp))
+
+            // XSmall wide round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.xSmallContainerSize(Wide)),
+                shape = IconButtonDefaults.xSmallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+                )
+            }
+            // Small wide round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.smallContainerSize(Wide)),
+                shape = IconButtonDefaults.smallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+                )
+            }
+
+            // medium wide round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.mediumContainerSize(Wide)),
+                shape = IconButtonDefaults.mediumRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+                )
+            }
+
+            // Large wide round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.largeContainerSize(Wide)),
+                shape = IconButtonDefaults.largeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+                )
+            }
+
+            // XLarge wide round icon button
+            FilledIconButton(
+                onClick = { /* doSomething() */ },
+                modifier = Modifier.size(IconButtonDefaults.xLargeContainerSize(Wide)),
+                shape = IconButtonDefaults.xLargeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+                )
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun IconButtonCornerRadiusDemo() {
+    Column {
+        val rowScrollState = rememberScrollState()
+        val padding = 16.dp
+        // uniform round row
+        Row(
+            modifier =
+                Modifier.height(150.dp)
+                    .horizontalScroll(rowScrollState)
+                    .padding(horizontal = padding),
+            horizontalArrangement = Arrangement.spacedBy(padding),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            // xsmall round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier
+                        // .minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.xSmallContainerSize()),
+                shape = IconButtonDefaults.xSmallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+                )
+            }
+
+            // Small round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier
+                        // .minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.smallContainerSize()),
+                shape = IconButtonDefaults.smallRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+                )
+            }
+
+            // Medium round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier.minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.mediumContainerSize()),
+                shape = IconButtonDefaults.mediumRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+                )
+            }
+
+            // Large uniform round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier.minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.largeContainerSize()),
+                shape = IconButtonDefaults.largeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+                )
+            }
+
+            // XLarge uniform round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier.minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.xLargeContainerSize()),
+                shape = IconButtonDefaults.xLargeRoundShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+                )
+            }
+        }
+
+        // uniform square row
+        Row(
+            modifier =
+                Modifier.height(150.dp)
+                    .horizontalScroll(rowScrollState)
+                    .padding(horizontal = padding),
+            horizontalArrangement = Arrangement.spacedBy(padding),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            // xsmall square icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier
+                        // .minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.xSmallContainerSize()),
+                shape = IconButtonDefaults.xSmallSquareShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
+                )
+            }
+
+            // Small round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier
+                        // .minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.smallContainerSize()),
+                shape = IconButtonDefaults.smallSquareShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.smallIconSize)
+                )
+            }
+
+            // Medium round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier.minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.mediumContainerSize()),
+                shape = IconButtonDefaults.mediumSquareShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
+                )
+            }
+
+            // Large uniform round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier.minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.largeContainerSize()),
+                shape = IconButtonDefaults.largeSquareShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+                )
+            }
+
+            // XLarge uniform round icon button
+            OutlinedIconButton(
+                onClick = { /* doSomething() */ },
+                modifier =
+                    Modifier.minimumInteractiveComponentSize()
+                        .size(IconButtonDefaults.xLargeContainerSize()),
+                shape = IconButtonDefaults.xLargeSquareShape
+            ) {
+                Icon(
+                    Icons.Outlined.Lock,
+                    contentDescription = "Localized description",
+                    modifier = Modifier.size(IconButtonDefaults.xLargeIconSize)
+                )
+            }
+        }
+    }
+}
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
index 22b439f..bf06566 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/Material3Demos.kt
@@ -30,5 +30,12 @@
             ComposableDemo("Swipe To Dismiss") { SwipeToDismissDemo() },
             ComposableDemo("Tooltip") { TooltipDemo() },
             ComposableDemo("Text fields") { MaterialTextFieldDemo() },
+            DemoCategory(
+                "Icon Buttons",
+                listOf(
+                    ComposableDemo("Sizes") { IconButtonMeasurementsDemo() },
+                    ComposableDemo("Corners") { IconButtonCornerRadiusDemo() }
+                )
+            ),
         ),
     )
diff --git a/compose/material3/material3/lint-baseline.xml b/compose/material3/material3/lint-baseline.xml
index 57cf2ac..6270830 100644
--- a/compose/material3/material3/lint-baseline.xml
+++ b/compose/material3/material3/lint-baseline.xml
@@ -7,6 +7,51 @@
         errorLine1="        Thread.sleep(300)"
         errorLine2="               ~~~~~">
         <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+    </issue>
+
+    <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="        Thread.sleep(300)"
+        errorLine2="               ~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+    </issue>
+
+    <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="        Thread.sleep(300)"
+        errorLine2="               ~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+    </issue>
+
+    <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="        Thread.sleep(300)"
+        errorLine2="               ~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+    </issue>
+
+    <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="        Thread.sleep(300)"
+        errorLine2="               ~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt"/>
+    </issue>
+
+    <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="        Thread.sleep(300)"
+        errorLine2="               ~~~~~">
+        <location
             file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/CardScreenshotTest.kt"/>
     </issue>
 
@@ -245,24 +290,6 @@
     </issue>
 
     <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material3/ToggleButtonScreenshotTest.kt"/>
-    </issue>
-
-    <issue
         id="PrimitiveInCollection"
         message="variable crossAxisSizes with type List&lt;Integer>: replace with IntList"
         errorLine1="        val crossAxisSizes = mutableListOf&lt;Int>()"
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt
new file mode 100644
index 0000000..6ba6bd5
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 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.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonGroup
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun ButtonGroupSample() {
+    ButtonGroup {
+        Button(modifier = Modifier.weight(1.5f), onClick = {}) { Text("A") }
+        Button(modifier = Modifier.weight(1f), onClick = {}) { Text("B") }
+        Button(modifier = Modifier.width(90.dp), onClick = {}) { Text("C") }
+        Button(modifier = Modifier.weight(1f), onClick = {}) { Text("D") }
+    }
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
index fac8536..f159640 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/IconButtonSamples.kt
@@ -21,7 +21,6 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Lock
 import androidx.compose.material.icons.outlined.Lock
-import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.FilledIconButton
 import androidx.compose.material3.FilledIconToggleButton
@@ -33,6 +32,7 @@
 import androidx.compose.material3.IconToggleButton
 import androidx.compose.material3.OutlinedIconButton
 import androidx.compose.material3.OutlinedIconToggleButton
+import androidx.compose.material3.minimumInteractiveComponentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -56,16 +56,23 @@
 @Preview
 @Sampled
 @Composable
-fun SmallSquareNarrowIconButtonSample() {
-    IconButton(
+fun XSmallNarrowSquareIconButtonsSample() {
+    // Small narrow round icon button
+    FilledIconButton(
         onClick = { /* doSomething() */ },
-        modifier = Modifier.size(IconButtonDefaults.SmallNarrowContainerSize),
-        shape = IconButtonDefaults.squareShape
+        modifier =
+            Modifier.minimumInteractiveComponentSize()
+                .size(
+                    IconButtonDefaults.xSmallContainerSize(
+                        IconButtonDefaults.IconButtonWidthOption.Narrow
+                    )
+                ),
+        shape = IconButtonDefaults.xSmallSquareShape
     ) {
         Icon(
             Icons.Outlined.Lock,
             contentDescription = "Localized description",
-            modifier = Modifier.size(IconButtonDefaults.SmallIconSize)
+            modifier = Modifier.size(IconButtonDefaults.xSmallIconSize)
         )
     }
 }
@@ -74,21 +81,43 @@
 @Preview
 @Sampled
 @Composable
-fun SmallRoundWideIconButtonSample() {
+fun MediumRoundWideIconButtonSample() {
     IconButton(
         onClick = { /* doSomething() */ },
-        modifier = Modifier.size(IconButtonDefaults.SmallWideContainerSize),
-        shape = IconButtonDefaults.roundShape
+        modifier =
+            Modifier.size(
+                IconButtonDefaults.mediumContainerSize(
+                    IconButtonDefaults.IconButtonWidthOption.Wide
+                )
+            ),
+        shape = IconButtonDefaults.mediumRoundShape
     ) {
         Icon(
             Icons.Outlined.Lock,
             contentDescription = "Localized description",
-            modifier = Modifier.size(IconButtonDefaults.SmallIconSize)
+            modifier = Modifier.size(IconButtonDefaults.mediumIconSize)
         )
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Sampled
+@Composable
+fun LargeRoundUniformOutlinedIconButtonSample() {
+    OutlinedIconButton(
+        onClick = { /* doSomething() */ },
+        modifier = Modifier.size(IconButtonDefaults.largeContainerSize()),
+        shape = IconButtonDefaults.largeRoundShape
+    ) {
+        Icon(
+            Icons.Outlined.Lock,
+            contentDescription = "Localized description",
+            modifier = Modifier.size(IconButtonDefaults.largeIconSize)
+        )
+    }
+}
+
 @Preview
 @Sampled
 @Composable
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt
index 8460484..b215df5 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ToggleButtonSamples.kt
@@ -57,8 +57,6 @@
         ButtonShapes(
             shape = ToggleButtonDefaults.roundShape,
             pressedShape = ToggleButtonDefaults.pressedShape,
-            hoveredShape = ToggleButtonDefaults.hoveredShape,
-            focusedShape = ToggleButtonDefaults.focusedShape,
             checkedShape = ToggleButtonDefaults.squareShape
         )
     ToggleButton(checked = checked, onCheckedChange = { checked = it }, shapes = shapes) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt
new file mode 100644
index 0000000..43f5f50
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupScreenshotTest.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2024 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 android.os.Build
+import androidx.compose.foundation.layout.Box
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O, maxSdkVersion = 32)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+class ButtonGroupScreenshotTest {
+    @get:Rule val rule = createComposeRule()
+
+    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
+
+    private val wrapperTestTag = "WrapperTestTag"
+    private val aButton = "AButton"
+    private val bButton = "BButton"
+    private val cButton = "CButton"
+    private val dButton = "DButton"
+    private val eButton = "EButton"
+
+    @Test
+    fun buttonGroup_lightTheme() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(onClick = {}) { Text("A") }
+                    Button(onClick = {}) { Text("B") }
+                    Button(onClick = {}) { Text("C") }
+                    Button(onClick = {}) { Text("D") }
+                    Button(onClick = {}) { Text("E") }
+                }
+            }
+        }
+
+        assertAgainstGolden("buttonGroup_lightTheme")
+    }
+
+    @Test
+    fun buttonGroup_darkTheme() {
+        rule.setMaterialContent(darkColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(onClick = {}) { Text("A") }
+                    Button(onClick = {}) { Text("B") }
+                    Button(onClick = {}) { Text("C") }
+                    Button(onClick = {}) { Text("D") }
+                    Button(onClick = {}) { Text("E") }
+                }
+            }
+        }
+
+        assertAgainstGolden("buttonGroup_darkTheme")
+    }
+
+    @Ignore
+    @Test
+    fun buttonGroup_firstPressed_lightTheme() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+                    Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+                    Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+                    Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+                    Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(aButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+        // synchronization. Instead just wait until after the ripples are finished animating.
+        Thread.sleep(300)
+
+        assertAgainstGolden("buttonGroup_firstPressed_lightTheme")
+    }
+
+    @Ignore
+    @Test
+    fun buttonGroup_secondPressed_lightTheme() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+                    Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+                    Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+                    Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+                    Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(bButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+        // synchronization. Instead just wait until after the ripples are finished animating.
+        Thread.sleep(300)
+
+        assertAgainstGolden("buttonGroup_secondPressed_lightTheme")
+    }
+
+    @Ignore
+    @Test
+    fun buttonGroup_thirdPressed_lightTheme() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+                    Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+                    Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+                    Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+                    Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(cButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+        // synchronization. Instead just wait until after the ripples are finished animating.
+        Thread.sleep(300)
+
+        assertAgainstGolden("buttonGroup_thirdPressed_lightTheme")
+    }
+
+    @Ignore
+    @Test
+    fun buttonGroup_fourthPressed_lightTheme() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+                    Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+                    Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+                    Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+                    Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(dButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+        // synchronization. Instead just wait until after the ripples are finished animating.
+        Thread.sleep(300)
+
+        assertAgainstGolden("buttonGroup_fourthPressed_lightTheme")
+    }
+
+    @Ignore
+    @Test
+    fun buttonGroup_fifthPressed_lightTheme() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+                    Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+                    Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+                    Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+                    Button(modifier = Modifier.testTag(eButton), onClick = {}) { Text("E") }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(eButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+        // synchronization. Instead just wait until after the ripples are finished animating.
+        Thread.sleep(300)
+
+        assertAgainstGolden("buttonGroup_fifthPressed_lightTheme")
+    }
+
+    private fun assertAgainstGolden(goldenName: String) {
+        rule
+            .onNodeWithTag(wrapperTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenName)
+    }
+}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupTest.kt
new file mode 100644
index 0000000..fb0b3e3
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ButtonGroupTest.kt
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2024 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.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.width
+import androidx.compose.testutils.assertIsEqualTo
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+class ButtonGroupTest {
+    @get:Rule val rule = createComposeRule()
+
+    private val wrapperTestTag = "WrapperTestTag"
+    private val aButton = "AButton"
+    private val bButton = "BButton"
+    private val cButton = "CButton"
+    private val dButton = "DButton"
+
+    @Test
+    fun default_positioning() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+                    Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+                    Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+                    Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+                }
+            }
+        }
+
+        val wrapperBounds = rule.onNodeWithTag(wrapperTestTag).getUnclippedBoundsInRoot()
+        val aButtonBounds = rule.onNodeWithTag(aButton).getUnclippedBoundsInRoot()
+        val bButtonBounds = rule.onNodeWithTag(bButton).getUnclippedBoundsInRoot()
+        val cButtonBounds = rule.onNodeWithTag(cButton).getUnclippedBoundsInRoot()
+        val dButtonBounds = rule.onNodeWithTag(dButton).getUnclippedBoundsInRoot()
+
+        (aButtonBounds.left - wrapperBounds.left).assertIsEqualTo(0.dp)
+        (bButtonBounds.left - aButtonBounds.right).assertIsEqualTo(12.dp)
+        (cButtonBounds.left - bButtonBounds.right).assertIsEqualTo(12.dp)
+        (dButtonBounds.left - cButtonBounds.right).assertIsEqualTo(12.dp)
+        (wrapperBounds.right - dButtonBounds.right).assertIsEqualTo(0.dp)
+    }
+
+    @Test
+    fun differentHorizontalSpacing_positioning() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+                    Button(modifier = Modifier.testTag(aButton), onClick = {}) { Text("A") }
+                    Button(modifier = Modifier.testTag(bButton), onClick = {}) { Text("B") }
+                    Button(modifier = Modifier.testTag(cButton), onClick = {}) { Text("C") }
+                    Button(modifier = Modifier.testTag(dButton), onClick = {}) { Text("D") }
+                }
+            }
+        }
+
+        val wrapperBounds = rule.onNodeWithTag(wrapperTestTag).getUnclippedBoundsInRoot()
+        val aButtonBounds = rule.onNodeWithTag(aButton).getUnclippedBoundsInRoot()
+        val bButtonBounds = rule.onNodeWithTag(bButton).getUnclippedBoundsInRoot()
+        val cButtonBounds = rule.onNodeWithTag(cButton).getUnclippedBoundsInRoot()
+        val dButtonBounds = rule.onNodeWithTag(dButton).getUnclippedBoundsInRoot()
+
+        (aButtonBounds.left - wrapperBounds.left).assertIsEqualTo(0.dp)
+        (bButtonBounds.left - aButtonBounds.right).assertIsEqualTo(4.dp)
+        (cButtonBounds.left - bButtonBounds.right).assertIsEqualTo(4.dp)
+        (dButtonBounds.left - cButtonBounds.right).assertIsEqualTo(4.dp)
+        (wrapperBounds.right - dButtonBounds.right).assertIsEqualTo(0.dp)
+    }
+
+    @Test
+    fun default_firstPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.15f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * animateFraction)
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(aButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(expectedExpandWidth)
+        bButton.assertWidthIsEqualTo(expectedCompressWidth)
+        cButton.assertWidthIsEqualTo(width)
+        dButton.assertWidthIsEqualTo(width)
+    }
+
+    @Test
+    fun default_secondPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.15f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * (animateFraction / 2f))
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(bButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(expectedCompressWidth)
+        bButton.assertWidthIsEqualTo(expectedExpandWidth)
+        cButton.assertWidthIsEqualTo(expectedCompressWidth)
+        dButton.assertWidthIsEqualTo(width)
+    }
+
+    @Test
+    fun default_thirdPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.15f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * (animateFraction / 2f))
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(cButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(width)
+        bButton.assertWidthIsEqualTo(expectedCompressWidth)
+        cButton.assertWidthIsEqualTo(expectedExpandWidth)
+        dButton.assertWidthIsEqualTo(expectedCompressWidth)
+    }
+
+    @Test
+    fun default_fourthPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.15f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * animateFraction)
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(dButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(width)
+        bButton.assertWidthIsEqualTo(width)
+        cButton.assertWidthIsEqualTo(expectedCompressWidth)
+        dButton.assertWidthIsEqualTo(expectedExpandWidth)
+    }
+
+    @Test
+    fun customAnimateFraction_firstPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.3f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * animateFraction)
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup(animateFraction = animateFraction) {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(aButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(expectedExpandWidth)
+        bButton.assertWidthIsEqualTo(expectedCompressWidth)
+        cButton.assertWidthIsEqualTo(width)
+        dButton.assertWidthIsEqualTo(width)
+    }
+
+    @Test
+    fun customAnimateFraction_secondPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.3f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * animateFraction / 2f)
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup(animateFraction = animateFraction) {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(bButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(expectedCompressWidth)
+        bButton.assertWidthIsEqualTo(expectedExpandWidth)
+        cButton.assertWidthIsEqualTo(expectedCompressWidth)
+        dButton.assertWidthIsEqualTo(width)
+    }
+
+    @Test
+    fun customAnimateFraction_thirdPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.3f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * animateFraction / 2f)
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup(animateFraction = animateFraction) {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(cButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(width)
+        bButton.assertWidthIsEqualTo(expectedCompressWidth)
+        cButton.assertWidthIsEqualTo(expectedExpandWidth)
+        dButton.assertWidthIsEqualTo(expectedCompressWidth)
+    }
+
+    @Test
+    fun customAnimateFraction_fourthPressed_buttonSizing() {
+        val width = 75.dp
+        val animateFraction = 0.3f
+        val expectedExpandWidth = width + (width * animateFraction)
+        val expectedCompressWidth = width - (width * animateFraction)
+
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(wrapperTestTag)) {
+                ButtonGroup(animateFraction = animateFraction) {
+                    Button(modifier = Modifier.width(width).testTag(aButton), onClick = {}) {
+                        Text("A")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(bButton), onClick = {}) {
+                        Text("B")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(cButton), onClick = {}) {
+                        Text("C")
+                    }
+                    Button(modifier = Modifier.width(width).testTag(dButton), onClick = {}) {
+                        Text("D")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(dButton).performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        rule.waitForIdle()
+
+        val aButton = rule.onNodeWithTag(aButton)
+        val bButton = rule.onNodeWithTag(bButton)
+        val cButton = rule.onNodeWithTag(cButton)
+        val dButton = rule.onNodeWithTag(dButton)
+
+        aButton.assertWidthIsEqualTo(width)
+        bButton.assertWidthIsEqualTo(width)
+        cButton.assertWidthIsEqualTo(expectedCompressWidth)
+        dButton.assertWidthIsEqualTo(expectedExpandWidth)
+    }
+}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
index 2a8c1db..99e18f1 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.outlined.FavoriteBorder
@@ -27,6 +28,7 @@
 import androidx.compose.material3.tokens.FilledTonalIconButtonTokens
 import androidx.compose.material3.tokens.IconButtonTokens
 import androidx.compose.material3.tokens.OutlinedIconButtonTokens
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -37,6 +39,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -62,16 +65,19 @@
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth
+import org.junit.Assert.assertEquals
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 /** Tests for icon buttons. */
@@ -79,7 +85,93 @@
     @get:Rule val rule = createComposeRule()
 
     @Test
-    fun iconButton_size() {
+    fun iconButton_xsmall_visualBounds() {
+        val expectedWidth =
+            with(rule.density) { IconButtonDefaults.xSmallContainerSize().width.roundToPx() }
+        val expectedHeight =
+            with(rule.density) { IconButtonDefaults.xSmallContainerSize().height.roundToPx() }
+        val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+        assertVisualBounds(
+            {
+                IconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(IconButtonDefaults.xSmallContainerSize())
+                            .testTag(IconButtonTestTag),
+                    shape = IconButtonDefaults.smallRoundShape
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            },
+            expectedSize
+        )
+    }
+
+    @Test
+    fun iconButton_xSmall_semantic_bounds() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                IconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(IconButtonDefaults.xSmallContainerSize())
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+            .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+    }
+
+    @Test
+    fun iconButton_small_visualBounds() {
+        val expectedWidth =
+            with(rule.density) { IconButtonDefaults.smallContainerSize().width.roundToPx() }
+        val expectedHeight =
+            with(rule.density) { IconButtonDefaults.smallContainerSize().height.roundToPx() }
+        val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+        val size = IconButtonDefaults.smallContainerSize()
+
+        assertVisualBounds(
+            {
+                IconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(size)
+                            .testTag(IconButtonTestTag),
+                    shape = IconButtonDefaults.smallRoundShape
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            },
+            expectedSize
+        )
+    }
+
+    @Test
+    fun iconButton_small_semantic_bounds() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                IconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(IconButtonDefaults.smallContainerSize())
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+            .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+    }
+
+    @Test
+    fun iconButton_medium_size() {
         rule
             .setMaterialContentForSizeAssertions {
                 IconButton(onClick = { /* doSomething() */ }) {
@@ -92,42 +184,38 @@
             .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
-    fun iconButton_wideShape() {
-        val shape = ShapeDefaults.Medium
-        val background = Color.Yellow
-        val iconButtonColor = Color.Blue
-        rule.setMaterialContent(lightColorScheme()) {
-            Surface(color = background) {
-                Box {
-                    IconButton(
-                        onClick = { /* doSomething() */ },
-                        modifier =
-                            Modifier.semantics(mergeDescendants = true) {}
-                                .testTag(IconTestTag)
-                                .size(50.dp),
-                        shape = shape,
-                        colors =
-                            IconButtonDefaults.iconButtonColors(containerColor = iconButtonColor)
-                    ) {}
+    fun iconButton_large_size() {
+        var size = DpSize.Zero
+        rule
+            .setMaterialContentForSizeAssertions {
+                size = IconButtonDefaults.largeContainerSize()
+                IconButton(onClick = { /* doSomething() */ }, modifier = Modifier.size(size)) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
                 }
             }
-        }
-
-        rule
-            .onNodeWithTag(IconTestTag)
-            .captureToImage()
-            .assertShape(
-                density = rule.density,
-                shape = shape,
-                shapeColor = iconButtonColor,
-                backgroundColor = background,
-                shapeOverlapPixelCount = with(rule.density) { 1.dp.toPx() }
-            )
+            .assertWidthIsEqualTo(size.width)
+            .assertHeightIsEqualTo(size.height)
+            .assertTouchWidthIsEqualTo(size.width)
+            .assertTouchHeightIsEqualTo(size.height)
     }
 
-    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun iconButton_xlarge_size() {
+        var size = DpSize.Zero
+        rule
+            .setMaterialContentForSizeAssertions {
+                size = IconButtonDefaults.xLargeContainerSize()
+                IconButton(onClick = { /* doSomething() */ }, modifier = Modifier.size(size)) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            .assertWidthIsEqualTo(size.width)
+            .assertHeightIsEqualTo(size.height)
+            .assertTouchWidthIsEqualTo(size.width)
+            .assertTouchHeightIsEqualTo(size.height)
+    }
+
     @Test
     fun iconButton_sizeWithoutMinTargetEnforcement() {
         rule
@@ -390,7 +478,6 @@
     @Test
     fun iconToggleButton_clickInMinimumTouchTarget(): Unit =
         with(rule.density) {
-            val tag = "iconToggleButton"
             var checked by mutableStateOf(false)
             rule.setMaterialContent(lightColorScheme()) {
                 // Box is needed because otherwise the control will be expanded to fill its parent
@@ -398,14 +485,17 @@
                     IconToggleButton(
                         checked = checked,
                         onCheckedChange = { checked = it },
-                        modifier = Modifier.align(Alignment.Center).requiredSize(2.dp).testTag(tag)
+                        modifier =
+                            Modifier.align(Alignment.Center)
+                                .requiredSize(2.dp)
+                                .testTag(IconButtonTestTag)
                     ) {
                         Box(Modifier.size(2.dp))
                     }
                 }
             }
             rule
-                .onNodeWithTag(tag)
+                .onNodeWithTag(IconButtonTestTag)
                 .assertIsOff()
                 .assertWidthIsEqualTo(2.dp)
                 .assertHeightIsEqualTo(2.dp)
@@ -435,7 +525,98 @@
     }
 
     @Test
-    fun filledIconButton_size() {
+    fun filledIconButton_xsmall_visualBounds() {
+        val expectedWidth =
+            with(rule.density) { IconButtonDefaults.xSmallContainerSize().width.roundToPx() }
+        val expectedHeight =
+            with(rule.density) { IconButtonDefaults.xSmallContainerSize().height.roundToPx() }
+        val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+        // The bounds of a testTag on a box that contains the progress indicator are not affected
+        // by the padding added on the layout of the progress bar.
+        assertVisualBounds(
+            {
+                val size = IconButtonDefaults.xSmallContainerSize()
+                FilledIconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(size)
+                            .testTag(IconButtonTestTag),
+                    shape = IconButtonDefaults.xSmallRoundShape
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            },
+            expectedSize
+        )
+    }
+
+    @Test
+    fun filledIconButton_xSmall_semantic_bounds() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                FilledIconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(IconButtonDefaults.xSmallContainerSize()),
+                    shape = IconButtonDefaults.xSmallRoundShape
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+            .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+    }
+
+    @Test
+    fun filledIconButton_small_visualBounds() {
+        val expectedWidth =
+            with(rule.density) { IconButtonDefaults.smallContainerSize().width.roundToPx() }
+        val expectedHeight =
+            with(rule.density) { IconButtonDefaults.smallContainerSize().height.roundToPx() }
+        val expectedSize = IntSize(expectedWidth, expectedHeight)
+
+        // The bounds of a testTag on a box that contains the progress indicator are not affected
+        // by the padding added on the layout of the progress bar.
+        assertVisualBounds(
+            {
+                val size = IconButtonDefaults.smallContainerSize()
+                FilledIconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(size)
+                            .testTag(IconButtonTestTag),
+                    shape = IconButtonDefaults.smallRoundShape
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            },
+            expectedSize
+        )
+    }
+
+    @Test
+    fun filledIconButton_small_semantic_bounds() {
+        rule
+            .setMaterialContentForSizeAssertions {
+                FilledIconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier =
+                        Modifier.minimumInteractiveComponentSize()
+                            .size(IconButtonDefaults.smallContainerSize())
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            .assertTouchWidthIsEqualTo(IconButtonAccessibilitySize)
+            .assertTouchHeightIsEqualTo(IconButtonAccessibilitySize)
+    }
+
+    @Test
+    fun filledIconButton_medium_size() {
         rule
             .setMaterialContentForSizeAssertions {
                 FilledIconButton(onClick = { /* doSomething() */ }) {
@@ -449,6 +630,80 @@
     }
 
     @Test
+    fun filledIconButton_large_size() {
+        var size = DpSize.Zero
+        rule
+            .setMaterialContentForSizeAssertions {
+                size = IconButtonDefaults.largeContainerSize()
+                FilledIconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier = Modifier.size(size)
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            .assertWidthIsEqualTo(size.width)
+            .assertHeightIsEqualTo(size.height)
+            .assertTouchWidthIsEqualTo(size.width)
+            .assertTouchHeightIsEqualTo(size.height)
+    }
+
+    @Test
+    fun filledIconButton_xlarge_size() {
+        var size = DpSize.Zero
+        rule
+            .setMaterialContentForSizeAssertions {
+                size = IconButtonDefaults.xLargeContainerSize()
+                FilledIconButton(
+                    onClick = { /* doSomething() */ },
+                    modifier = Modifier.size(size)
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+            .assertWidthIsEqualTo(size.width)
+            .assertHeightIsEqualTo(size.height)
+            .assertTouchWidthIsEqualTo(size.width)
+            .assertTouchHeightIsEqualTo(size.height)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun filledIconButton_medium_squareShape() {
+        var shape: Shape = CircleShape
+        val background = Color.Yellow
+        val iconButtonColor = Color.Blue
+        rule.setMaterialContent(lightColorScheme()) {
+            shape = IconButtonDefaults.mediumSquareShape
+            Surface(color = background) {
+                Box {
+                    FilledIconButton(
+                        onClick = { /* doSomething() */ },
+                        modifier =
+                            Modifier.semantics(mergeDescendants = true) {}
+                                .testTag(IconTestTag)
+                                .size(IconButtonDefaults.mediumContainerSize()),
+                        shape = shape,
+                        colors =
+                            IconButtonDefaults.iconButtonColors(containerColor = iconButtonColor)
+                    ) {}
+                }
+            }
+        }
+
+        rule
+            .onNodeWithTag(IconTestTag)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = shape,
+                shapeColor = iconButtonColor,
+                backgroundColor = background,
+                shapeOverlapPixelCount = with(rule.density) { 1.dp.toPx() }
+            )
+    }
+
+    @Test
     fun filledIconButton_sizeWithoutMinTargetEnforcement() {
         rule
             .setMaterialContentForSizeAssertions {
@@ -972,7 +1227,7 @@
     }
 
     @Test
-    fun outlinedIconToggleButton_defaualtColors() {
+    fun outlinedIconToggleButton_defaultColors() {
         rule.setMaterialContent(lightColorScheme()) {
             val localContentColor = LocalContentColor.current
             Truth.assertThat(IconButtonDefaults.outlinedIconToggleButtonColors())
@@ -994,8 +1249,27 @@
         }
     }
 
+    private fun assertVisualBounds(composable: @Composable () -> Unit, expectedSize: IntSize) {
+        // The bounds of a testTag on a box that contains the progress indicator are not affected
+        // by the padding added on the layout of the progress bar.
+        rule.setContent { composable() }
+
+        val node =
+            rule
+                .onNodeWithTag(IconButtonTestTag)
+                .fetchSemanticsNode(
+                    errorMessageOnFail = "couldn't find node with tag $IconButtonTestTag"
+                )
+        val nodeBounds = node.boundsInRoot
+
+        // Check that the visual bounds of an xsmall icon button are the expected visual size.
+        assertEquals(expectedSize.width.toFloat(), nodeBounds.width)
+        assertEquals(expectedSize.height.toFloat(), nodeBounds.height)
+    }
+
     private val IconButtonAccessibilitySize = 48.0.dp
     private val IconButtonSize = 40.0.dp
     private val IconSize = 24.0.dp
     private val IconTestTag = "icon"
+    private val IconButtonTestTag = "iconButton"
 }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/InteractionSourceModifierNodeTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/InteractionSourceModifierNodeTest.kt
new file mode 100644
index 0000000..ca6ced6
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/InteractionSourceModifierNodeTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 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.foundation.clickable
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.util.fastForEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import junit.framework.TestCase.assertTrue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InteractionSourceModifierNodeTest {
+
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun interactionSourceData_findInteractionSources() {
+        lateinit var sources: MutableState<List<InteractionSource>>
+        lateinit var scope: CoroutineScope
+        var childElementPressed = false
+
+        rule.setContent {
+            sources = remember { mutableStateOf(emptyList()) }
+            scope = rememberCoroutineScope()
+            val interactionSource = remember { MutableInteractionSource() }
+            Box(
+                modifier =
+                    Modifier.onChildrenInteractionSourceChange { interactionSources ->
+                        if (sources.value != interactionSources) {
+                            sources.value = interactionSources
+                        }
+                    }
+            ) {
+                Box(
+                    modifier =
+                        Modifier.clickable(
+                                interactionSource = interactionSource,
+                                indication = ripple(),
+                                onClick = {}
+                            )
+                            .interactionSourceData(interactionSource)
+                )
+            }
+        }
+
+        rule.waitForIdle()
+        // Set up observer and change childElementPressd to true
+        sources.value.fastForEach { interactionSource ->
+            scope.launch {
+                interactionSource.interactions.collectLatest { interaction ->
+                    when (interaction) {
+                        is PressInteraction.Press -> {
+                            childElementPressed = true
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction()).performClick()
+
+        assertTrue(childElementPressed)
+    }
+}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
index 34e6423..33d5274 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalExpandedNavigationRailTest.kt
@@ -18,19 +18,25 @@
 
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.internal.Strings
+import androidx.compose.material3.internal.getString
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsProperties
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.isDisplayed
 import androidx.compose.ui.test.isNotDisplayed
 import androidx.compose.ui.test.junit4.StateRestorationTester
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onParent
+import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.unit.dp
@@ -203,6 +209,73 @@
     }
 
     @Test
+    fun modalRail_closes_byScrimClick() {
+        lateinit var closeRail: String
+        lateinit var railState: ModalExpandedNavigationRailState
+        rule.setMaterialContentForSizeAssertions {
+            closeRail = getString(Strings.CloseRail)
+            railState = rememberModalExpandedNavigationRailState()
+
+            ModalExpandedNavigationRail(
+                gesturesEnabled = false,
+                onDismissRequest = {},
+                railState = railState,
+            ) {
+                WideNavigationRailItem(
+                    modifier = Modifier.testTag("item"),
+                    railExpanded = true,
+                    icon = { Icon(Icons.Filled.Favorite, null) },
+                    label = { Text("ItemText") },
+                    selected = true,
+                    onClick = {}
+                )
+            }
+        }
+
+        // The rail should be open.
+        assertThat(railState.isOpen).isTrue()
+
+        rule
+            .onNodeWithContentDescription(closeRail)
+            .assertHasClickAction()
+            .performSemanticsAction(SemanticsActions.OnClick)
+        rule.waitForIdle()
+
+        // Assert rail is not open.
+        assertThat(railState.isOpen).isFalse()
+        // Assert rail is not displayed.
+        rule.onNodeWithTag("item").onParent().isNotDisplayed()
+    }
+
+    @Test
+    fun modalRail_hasPaneTitle() {
+        lateinit var paneTitle: String
+
+        rule.setMaterialContentForSizeAssertions {
+            paneTitle = getString(Strings.WideNavigationRailPaneTitle)
+            ModalExpandedNavigationRail(
+                onDismissRequest = {},
+            ) {
+                WideNavigationRailItem(
+                    modifier = Modifier.testTag("item"),
+                    railExpanded = true,
+                    icon = { Icon(Icons.Filled.Favorite, null) },
+                    label = { Text("ItemText") },
+                    selected = true,
+                    onClick = {}
+                )
+            }
+        }
+
+        rule
+            .onNodeWithTag("item")
+            .onParent() // rail.
+            .onParent() // dialog window.
+            .onParent() // parent container that holds dialog and scrim.
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.PaneTitle, paneTitle))
+    }
+
+    @Test
     fun modalRailState_savesAndRestores() {
         lateinit var railState: ModalExpandedNavigationRailState
 
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt
index aa629c2..0ca2a35 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/internal/Strings.android.kt
@@ -239,7 +239,13 @@
         actual inline val CloseDrawer
             get() = Strings(R.string.close_drawer)
 
+        actual inline val CloseRail
+            get() = Strings(MaterialR.string.m3c_wide_navigation_rail_close_rail)
+
         actual inline val CloseSheet
             get() = Strings(R.string.close_sheet)
+
+        actual inline val WideNavigationRailPaneTitle
+            get() = Strings(MaterialR.string.m3c_wide_navigation_rail_pane_title)
     }
 }
diff --git a/compose/material3/material3/src/androidMain/res/values/strings.xml b/compose/material3/material3/src/androidMain/res/values/strings.xml
index 371e90f..9b2e983 100644
--- a/compose/material3/material3/src/androidMain/res/values/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values/strings.xml
@@ -113,4 +113,8 @@
     <string name="m3c_time_picker_minute_text_field">for minutes</string>
     <!-- A label for a textfield that allows the user to input hours. It reads: Edit Box, for hour -->
     <string name="m3c_time_picker_hour_text_field">for hour</string>
+    <!-- Spoken content description of an element which will close the modal rail when clicked. -->
+    <string name="m3c_wide_navigation_rail_close_rail">"Close rail"</string>
+    <!-- Accessibility pane title for the modal rail. -->
+    <string name="m3c_wide_navigation_rail_pane_title">"Navigation rail"</string>
 </resources>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index e15c050..8c63470 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -1587,7 +1587,6 @@
      * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the top
      *   app bar when the user flings the app bar itself, or the content below it
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @ExperimentalMaterial3Api
     @Composable
     fun enterAlwaysScrollBehavior(
@@ -1622,7 +1621,6 @@
      * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the top
      *   app bar when the user flings the app bar itself, or the content below it
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @ExperimentalMaterial3Api
     @Composable
     fun exitUntilCollapsedScrollBehavior(
@@ -1994,7 +1992,6 @@
      * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the
      *   bottom app bar when the user flings the app bar itself, or the content below it
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @ExperimentalMaterial3Api
     @Composable
     fun exitAlwaysScrollBehavior(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ButtonGroup.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ButtonGroup.kt
new file mode 100644
index 0000000..5de40c3
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ButtonGroup.kt
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2024 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.annotation.FloatRange
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.material3.tokens.ButtonGroupSmallTokens
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ParentDataModifierNode
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastMapIndexed
+import androidx.compose.ui.util.fastMaxBy
+import androidx.compose.ui.util.fastRoundToInt
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+import kotlin.math.sign
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * TODO link to mio page when available.
+ *
+ * A layout composable that places its children in a horizontal sequence. When a child uses
+ * [Modifier.interactionSourceData] with a relevant [MutableInteractionSource], this button group
+ * can listen to the interactions and expand the width of the pressed child element as well as
+ * compress the neighboring child elements. Material3 components already use
+ * [Modifier.interactionSourceData] and will behave as expected.
+ *
+ * TODO link to an image when available
+ *
+ * @sample androidx.compose.material3.samples.ButtonGroupSample
+ * @param modifier the [Modifier] to be applied to the button group.
+ * @param animateFraction the percentage, represented by a float, of the width of the interacted
+ *   child element that will be used to expand the interacted child element as well as compress the
+ *   neighboring children.
+ * @param horizontalArrangement The horizontal arrangement of the button group's children.
+ * @param content the content displayed in the button group, expected to use a Material3 component
+ *   or a composable that is tagged with [Modifier.interactionSourceData].
+ */
+@Composable
+@ExperimentalMaterial3ExpressiveApi
+fun ButtonGroup(
+    modifier: Modifier = Modifier,
+    @FloatRange(0.0) animateFraction: Float = ButtonGroupDefaults.animateFraction,
+    horizontalArrangement: Arrangement.Horizontal =
+        Arrangement.spacedBy(ButtonGroupDefaults.spaceBetween),
+    content: @Composable ButtonGroupScope.() -> Unit
+) {
+    val anim = remember { Animatable(0f) }
+    val coroutineScope = rememberCoroutineScope()
+    var pressedIndex by remember { mutableIntStateOf(-1) }
+    val scope = remember {
+        object : ButtonGroupScope {
+            override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
+                require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
+                return this.then(
+                    LayoutWeightElement(
+                        // Coerce Float.POSITIVE_INFINITY to Float.MAX_VALUE to avoid errors
+                        weight = weight.coerceAtMost(Float.MAX_VALUE),
+                        fill = fill
+                    )
+                )
+            }
+        }
+    }
+
+    val interactionSourceFlow: MutableStateFlow<List<InteractionSource>> = remember {
+        MutableStateFlow(emptyList())
+    }
+
+    LaunchedEffect(Unit) {
+        interactionSourceFlow.collectLatest { sources ->
+            sources.fastForEachIndexed { index, interactionSource ->
+                launch {
+                    interactionSource.interactions.collectLatest { interaction ->
+                        when (interaction) {
+                            is PressInteraction.Press -> {
+                                pressedIndex = index
+                                coroutineScope.launch { anim.animateTo(animateFraction) }
+                            }
+                            is PressInteraction.Release,
+                            is PressInteraction.Cancel -> {
+                                coroutineScope.launch {
+                                    anim.animateTo(0f)
+                                    pressedIndex = -1
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    val measurePolicy =
+        remember(horizontalArrangement) {
+            ButtonGroupMeasurePolicy(
+                horizontalArrangement = horizontalArrangement,
+                anim = anim,
+                pressedIndex = { pressedIndex }
+            )
+        }
+
+    Layout(
+        measurePolicy = measurePolicy,
+        modifier =
+            modifier.onChildrenInteractionSourceChange { interactionSource ->
+                if (interactionSourceFlow.value != interactionSource) {
+                    coroutineScope.launch { interactionSourceFlow.emit(interactionSource) }
+                }
+            },
+        content = { scope.content() }
+    )
+}
+
+/** Default values used by [ButtonGroup] */
+object ButtonGroupDefaults {
+    /**
+     * The default percentage, represented as a float, of the width of the interacted child element
+     * that will be used to expand the interacted child element as well as compress the neighboring
+     * children.
+     */
+    val animateFraction = 0.15f
+
+    /** The default spacing used between children. */
+    val spaceBetween = ButtonGroupSmallTokens.BetweenSpace
+}
+
+private class ButtonGroupMeasurePolicy(
+    val horizontalArrangement: Arrangement.Horizontal,
+    val anim: Animatable<Float, AnimationVector1D>,
+    val pressedIndex: () -> Int,
+) : MeasurePolicy {
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ): MeasureResult {
+        val arrangementSpacingInt = horizontalArrangement.spacing.roundToPx()
+        val arrangementSpacingPx = arrangementSpacingInt.toLong()
+        val size = measurables.size
+        var totalWeight = 0f
+        var fixedSpace = 0
+        var weightChildrenCount = 0
+        val placeables: List<Placeable>
+        val childrenMainAxisSize = IntArray(size)
+        val childrenConstraints: Array<Constraints?> = arrayOfNulls(size)
+
+        val mainAxisMin = constraints.minWidth
+        val mainAxisMax = constraints.maxWidth
+
+        // First obtain constraints of children with zero weight
+        var spaceAfterLastNoWeight = 0
+        for (i in 0 until size) {
+            val child = measurables[i]
+            val parentData = child.buttonGroupParentData
+            val weight = parentData.weight
+
+            if (weight > 0f) {
+                totalWeight += weight
+                ++weightChildrenCount
+            } else {
+                val remaining = mainAxisMax - fixedSpace
+                val desiredWidth = child.maxIntrinsicWidth(constraints.maxHeight)
+                childrenConstraints[i] =
+                    constraints.copy(
+                        minWidth = 0,
+                        maxWidth =
+                            if (mainAxisMax == Constraints.Infinity) {
+                                Constraints.Infinity
+                            } else {
+                                desiredWidth.coerceAtLeast(0)
+                            }
+                    )
+
+                childrenMainAxisSize[i] = desiredWidth
+
+                spaceAfterLastNoWeight =
+                    min(arrangementSpacingInt, (remaining - desiredWidth).coerceAtLeast(0))
+
+                fixedSpace += desiredWidth + spaceAfterLastNoWeight
+            }
+        }
+
+        var weightedSpace = 0
+        if (weightChildrenCount == 0) {
+            // fixedSpace contains an extra spacing after the last non-weight child.
+            fixedSpace -= spaceAfterLastNoWeight
+        } else {
+            // obtain the constraints of the rest according to their weights.
+            val targetSpace =
+                if (mainAxisMax != Constraints.Infinity) {
+                    mainAxisMax
+                } else {
+                    mainAxisMin
+                }
+
+            val arrangementSpacingTotal = arrangementSpacingPx * (weightChildrenCount - 1)
+            val remainingToTarget =
+                (targetSpace - fixedSpace - arrangementSpacingTotal).coerceAtLeast(0)
+
+            val weightUnitSpace = remainingToTarget / totalWeight
+            var remainder = remainingToTarget
+            for (i in 0 until size) {
+                val measurable = measurables[i]
+                val itemWeight = measurable.buttonGroupParentData.weight
+                val weightedSize = (weightUnitSpace * itemWeight)
+                remainder -= weightedSize.fastRoundToInt()
+            }
+
+            for (i in 0 until size) {
+                if (childrenConstraints[i] == null) {
+                    val child = measurables[i]
+                    val parentData = child.buttonGroupParentData
+                    val weight = parentData.weight
+
+                    // After the weightUnitSpace rounding, the total space going to be occupied
+                    // can be smaller or larger than remainingToTarget. Here we distribute the
+                    // loss or gain remainder evenly to the first children.
+                    val remainderUnit = remainder.sign
+                    remainder -= remainderUnit
+                    val weightedSize = (weightUnitSpace * weight)
+
+                    val childMainAxisSize = max(0, weightedSize.fastRoundToInt() + remainderUnit)
+
+                    childrenConstraints[i] =
+                        constraints.copy(
+                            minWidth =
+                                if (parentData.fill && childMainAxisSize != Constraints.Infinity) {
+                                    childMainAxisSize
+                                } else {
+                                    0
+                                },
+                            maxWidth = childMainAxisSize
+                        )
+
+                    childrenMainAxisSize[i] = childMainAxisSize
+                    weightedSpace += childMainAxisSize
+                }
+                weightedSpace =
+                    (weightedSpace + arrangementSpacingTotal)
+                        .toInt()
+                        .coerceIn(0, mainAxisMax - fixedSpace)
+            }
+        }
+
+        val pressedIdx = pressedIndex.invoke()
+        if (pressedIdx == -1 || pressedIdx >= measurables.size) {
+            placeables =
+                measurables.fastMapIndexed { index, measurable ->
+                    measurable.measure(childrenConstraints[index] ?: constraints)
+                }
+        } else {
+            val adjacent = buildList {
+                measurables.getOrNull(pressedIdx - 1)?.let { add(it) }
+                measurables.getOrNull(pressedIdx + 1)?.let { add(it) }
+            }
+
+            val pressedMeasurable = measurables[pressedIdx]
+            val pressedWidth = (childrenConstraints[pressedIdx] ?: constraints).maxWidth
+            val additionFactor = anim.value
+            val subtractFactor =
+                if (pressedIdx == 0 || pressedIdx == size - 1) anim.value else anim.value / 2f
+
+            placeables =
+                measurables.fastMapIndexed { index, measurable ->
+                    val desiredWidth = (childrenConstraints[index] ?: constraints).maxWidth
+                    if (measurable == pressedMeasurable) {
+                        measurable.measure(
+                            Constraints.fixedWidth(
+                                (desiredWidth + (pressedWidth * additionFactor)).roundToInt()
+                            )
+                        )
+                    } else if (measurable in adjacent) {
+                        measurable.measure(
+                            Constraints.fixedWidth(
+                                (desiredWidth - (pressedWidth * subtractFactor)).roundToInt()
+                            )
+                        )
+                    } else {
+                        measurable.measure(childrenConstraints[index] ?: constraints)
+                    }
+                }
+        }
+
+        // Compute the row size and position the children.
+        val mainAxisLayoutSize = max((fixedSpace + weightedSpace).coerceAtLeast(0), mainAxisMin)
+        val mainAxisPositions = IntArray(size) { 0 }
+        val measureScope = this
+        with(horizontalArrangement) {
+            measureScope.arrange(
+                mainAxisLayoutSize,
+                childrenMainAxisSize,
+                measureScope.layoutDirection,
+                mainAxisPositions
+            )
+        }
+
+        val height = placeables.fastMaxBy { it.height }?.height ?: constraints.minHeight
+        return layout(mainAxisLayoutSize, height) {
+            var currentX = 0
+            placeables.fastForEach {
+                it.place(currentX, 0)
+                currentX += it.width + arrangementSpacingInt
+            }
+        }
+    }
+}
+
+/** Button group scope used to indicate a [Modifier.weight] of a child element. */
+@ExperimentalMaterial3ExpressiveApi
+interface ButtonGroupScope {
+    /**
+     * Size the element's width proportional to its [weight] relative to other weighted sibling
+     * elements in the [ButtonGroup]. The parent will divide the horizontal space remaining after
+     * measuring unweighted child elements and distribute it according to this weight. When [fill]
+     * is true, the element will be forced to occupy the whole width allocated to it. Otherwise, the
+     * element is allowed to be smaller - this will result in [ButtonGroup] being smaller, as the
+     * unused allocated width will not be redistributed to other siblings.
+     *
+     * @param weight The proportional width to give to this element, as related to the total of all
+     *   weighted siblings. Must be positive.
+     * @param fill When `true`, the element will occupy the whole width allocated.
+     */
+    fun Modifier.weight(
+        @FloatRange(from = 0.0, fromInclusive = false) weight: Float,
+        fill: Boolean = true
+    ): Modifier
+}
+
+internal val IntrinsicMeasurable.buttonGroupParentData: ButtonGroupParentData?
+    get() = parentData as? ButtonGroupParentData
+
+internal val ButtonGroupParentData?.fill: Boolean
+    get() = this?.fill ?: true
+
+internal val ButtonGroupParentData?.weight: Float
+    get() = this?.weight ?: 0f
+
+internal data class ButtonGroupParentData(var weight: Float = 0f, var fill: Boolean = true)
+
+internal class LayoutWeightElement(
+    val weight: Float,
+    val fill: Boolean,
+) : ModifierNodeElement<LayoutWeightNode>() {
+    override fun create(): LayoutWeightNode {
+        return LayoutWeightNode(weight, fill)
+    }
+
+    override fun update(node: LayoutWeightNode) {
+        node.weight = weight
+        node.fill = fill
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "weight"
+        value = weight
+        properties["weight"] = weight
+        properties["fill"] = fill
+    }
+
+    override fun hashCode(): Int {
+        var result = weight.hashCode()
+        result = 31 * result + fill.hashCode()
+        return result
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        val otherModifier = other as? LayoutWeightElement ?: return false
+        return weight == otherModifier.weight && fill == otherModifier.fill
+    }
+}
+
+internal class LayoutWeightNode(
+    var weight: Float,
+    var fill: Boolean,
+) : ParentDataModifierNode, Modifier.Node() {
+    override fun Density.modifyParentData(parentData: Any?) =
+        ((parentData as? ButtonGroupParentData) ?: ButtonGroupParentData()).also {
+            it.weight = weight
+            it.fill = fill
+        }
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
index 84c8d9c..0fd3f81 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
@@ -257,7 +257,6 @@
         }
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun CheckboxImpl(
     enabled: Boolean,
@@ -266,15 +265,15 @@
     colors: CheckboxColors
 ) {
     val transition = updateTransition(value)
+    val defaultAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
     val checkDrawFraction =
         transition.animateFloat(
             transitionSpec = {
                 when {
                     // TODO Load the motionScheme tokens from the component tokens file
-                    initialState == ToggleableState.Off ->
-                        MotionSchemeKeyTokens.DefaultSpatial.value()
+                    initialState == ToggleableState.Off -> defaultAnimationSpec
                     targetState == ToggleableState.Off -> snap(delayMillis = SnapAnimationDelay)
-                    else -> MotionSchemeKeyTokens.DefaultSpatial.value()
+                    else -> defaultAnimationSpec
                 }
             }
         ) {
@@ -292,7 +291,7 @@
                     // TODO Load the motionScheme tokens from the component tokens file
                     initialState == ToggleableState.Off -> snap()
                     targetState == ToggleableState.Off -> snap(delayMillis = SnapAnimationDelay)
-                    else -> MotionSchemeKeyTokens.DefaultSpatial.value()
+                    else -> defaultAnimationSpec
                 }
             }
         ) {
@@ -554,7 +553,6 @@
     }
 
     /** Returns the color [AnimationSpec] for the given state. */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @Composable
     private fun colorAnimationSpecForState(state: ToggleableState): AnimationSpec<Color> {
         // TODO Load the motionScheme tokens from the component tokens file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
index fc3c261..3857026 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
@@ -690,7 +690,6 @@
      * @param lazyListState a [LazyListState]
      * @param decayAnimationSpec the decay to use
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @Composable
     internal fun rememberSnapFlingBehavior(
         lazyListState: LazyListState,
@@ -787,7 +786,6 @@
  * @constructor create an instance with arbitrary colors, see [DatePickerDefaults.colors] for the
  *   default implementation that follows Material specifications.
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @ExperimentalMaterial3Api
 @Immutable
 class DatePickerColors
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
index d0d23f8..0fc77f6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
@@ -903,18 +903,12 @@
     ) {
         val expandTransition = updateTransition(if (expanded) 1f else 0f, label = "expanded state")
         // TODO Load the motionScheme tokens from the component tokens file
+        val sizeAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
+        val opacityAnimationSpec = MotionSchemeKeyTokens.FastEffects.value<Float>()
         val expandedWidthProgress =
-            expandTransition.animateFloat(
-                transitionSpec = { MotionSchemeKeyTokens.FastSpatial.value() }
-            ) {
-                it
-            }
+            expandTransition.animateFloat(transitionSpec = { sizeAnimationSpec }) { it }
         val expandedAlphaProgress =
-            expandTransition.animateFloat(
-                transitionSpec = { MotionSchemeKeyTokens.FastEffects.value() }
-            ) {
-                it
-            }
+            expandTransition.animateFloat(transitionSpec = { opacityAnimationSpec }) { it }
         Row(
             modifier =
                 Modifier.layout { measurable, constraints ->
@@ -1264,7 +1258,6 @@
 
 private val ExtendedFabMinimumWidth = 80.dp
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun extendedFabCollapseAnimation() =
     fadeOut(
@@ -1276,7 +1269,6 @@
             shrinkTowards = Alignment.Start,
         )
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun extendedFabExpandAnimation() =
     fadeIn(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
index 1f15609..439bf25 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingAppBar.kt
@@ -398,6 +398,7 @@
      * @param flingAnimationSpec an [DecayAnimationSpec] that defines how to fling the floating app
      *   bar when the user flings the app bar itself, or the content below it
      */
+    // TODO Load the motionScheme tokens from the component tokens file
     @ExperimentalMaterial3ExpressiveApi
     @Composable
     fun exitAlwaysScrollBehavior(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
index 3dae87d..35c1914 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
@@ -26,9 +26,13 @@
 import androidx.compose.material3.internal.childSemantics
 import androidx.compose.material3.tokens.FilledIconButtonTokens
 import androidx.compose.material3.tokens.FilledTonalIconButtonTokens
-import androidx.compose.material3.tokens.IconButtonSmallTokens
 import androidx.compose.material3.tokens.IconButtonTokens
+import androidx.compose.material3.tokens.LargeIconButtonTokens
+import androidx.compose.material3.tokens.MediumIconButtonTokens
 import androidx.compose.material3.tokens.OutlinedIconButtonTokens
+import androidx.compose.material3.tokens.SmallIconButtonTokens
+import androidx.compose.material3.tokens.XLargeIconButtonTokens
+import androidx.compose.material3.tokens.XSmallIconButtonTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -47,6 +51,8 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import kotlin.jvm.JvmInline
 
 /**
  * <a href="https://m3.material.io/components/icon-button/overview" class="external"
@@ -64,8 +70,19 @@
  *
  * Simple Usage
  *
- * @sample androidx.compose.material3.samples.IconButtonSample IconButton with a color tint
+ * @sample androidx.compose.material3.samples.IconButtonSample
+ *
+ * IconButton with a color tint
+ *
  * @sample androidx.compose.material3.samples.TintedIconButtonSample
+ *
+ * Small-sized narrow round shape IconButton
+ *
+ * @sample androidx.compose.material3.samples.XSmallNarrowSquareIconButtonsSample
+ *
+ * Medium / default size round-shaped icon button
+ *
+ * @sample androidx.compose.material3.samples.MediumRoundWideIconButtonSample
  * @param onClick called when this icon button is clicked
  * @param modifier the [Modifier] to be applied to this icon button
  * @param enabled controls the enabled state of this icon button. When `false`, this component will
@@ -128,14 +145,6 @@
  * IconButton with a color tint
  *
  * @sample androidx.compose.material3.samples.TintedIconButtonSample
- *
- * IconButton with smaller square narrow shape
- *
- * @sample androidx.compose.material3.samples.SmallSquareNarrowIconButtonSample
- *
- * IconButton with smaller square narrow shape
- *
- * @sample androidx.compose.material3.samples.SmallRoundWideIconButtonSample
  * @param onClick called when this icon button is clicked
  * @param modifier the [Modifier] to be applied to this icon button
  * @param enabled controls the enabled state of this icon button. When `false`, this component will
@@ -160,6 +169,8 @@
     shape: Shape = IconButtonDefaults.standardShape,
     content: @Composable () -> Unit
 ) {
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
     Box(
         modifier =
             modifier
@@ -174,7 +185,8 @@
                     interactionSource = interactionSource,
                     indication = ripple()
                 )
-                .childSemantics(),
+                .childSemantics()
+                .interactionSourceData(interactionSource),
         contentAlignment = Alignment.Center
     ) {
         val contentColor = colors.contentColor(enabled)
@@ -283,6 +295,8 @@
     shape: Shape = IconButtonDefaults.standardShape,
     content: @Composable () -> Unit
 ) {
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
     Box(
         modifier =
             modifier
@@ -297,7 +311,8 @@
                     role = Role.Checkbox,
                     interactionSource = interactionSource,
                     indication = ripple()
-                ),
+                )
+                .interactionSourceData(interactionSource),
         contentAlignment = Alignment.Center
     ) {
         val contentColor = colors.contentColor(enabled, checked).value
@@ -346,26 +361,16 @@
     interactionSource: MutableInteractionSource? = null,
     content: @Composable () -> Unit
 ) =
-    Surface(
+    SurfaceIconButton(
         onClick = onClick,
-        modifier = modifier.semantics { role = Role.Button },
+        modifier = modifier,
         enabled = enabled,
         shape = shape,
-        color = colors.containerColor(enabled),
-        contentColor = colors.contentColor(enabled),
-        interactionSource = interactionSource
-    ) {
-        Box(
-            modifier =
-                Modifier.size(
-                    width = FilledIconButtonTokens.ContainerWidth,
-                    height = FilledIconButtonTokens.ContainerHeight
-                ),
-            contentAlignment = Alignment.Center
-        ) {
-            content()
-        }
-    }
+        colors = colors,
+        border = null,
+        interactionSource = interactionSource,
+        content = content
+    )
 
 /**
  * <a href="https://m3.material.io/components/icon-button/overview" class="external"
@@ -412,26 +417,16 @@
     interactionSource: MutableInteractionSource? = null,
     content: @Composable () -> Unit
 ) =
-    Surface(
+    SurfaceIconButton(
         onClick = onClick,
-        modifier = modifier.semantics { role = Role.Button },
+        modifier = modifier,
         enabled = enabled,
         shape = shape,
-        color = colors.containerColor(enabled),
-        contentColor = colors.contentColor(enabled),
-        interactionSource = interactionSource
-    ) {
-        Box(
-            modifier =
-                Modifier.size(
-                    width = FilledTonalIconButtonTokens.ContainerWidth,
-                    height = FilledTonalIconButtonTokens.ContainerHeight
-                ),
-            contentAlignment = Alignment.Center
-        ) {
-            content()
-        }
-    }
+        colors = colors,
+        border = null,
+        interactionSource = interactionSource,
+        content = content
+    )
 
 /**
  * <a href="https://m3.material.io/components/icon-button/overview" class="external"
@@ -589,6 +584,10 @@
  * button has an overall minimum touch target size of 48 x 48dp, to meet accessibility guidelines.
  *
  * @sample androidx.compose.material3.samples.OutlinedIconButtonSample
+ *
+ * Large-sized uniform rounded shape
+ *
+ * @sample androidx.compose.material3.samples.LargeRoundUniformOutlinedIconButtonSample
  * @param onClick called when this icon button is clicked
  * @param modifier the [Modifier] to be applied to this icon button
  * @param enabled controls the enabled state of this icon button. When `false`, this component will
@@ -617,6 +616,28 @@
     interactionSource: MutableInteractionSource? = null,
     content: @Composable () -> Unit
 ) =
+    SurfaceIconButton(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        shape = shape,
+        colors = colors,
+        border = border,
+        interactionSource = interactionSource,
+        content = content
+    )
+
+@Composable
+private fun SurfaceIconButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean,
+    shape: Shape,
+    colors: IconButtonColors,
+    border: BorderStroke?,
+    interactionSource: MutableInteractionSource?,
+    content: @Composable () -> Unit
+) =
     Surface(
         onClick = onClick,
         modifier = modifier.semantics { role = Role.Button },
@@ -628,7 +649,11 @@
         interactionSource = interactionSource
     ) {
         Box(
-            modifier = Modifier.size(OutlinedIconButtonTokens.ContainerSize),
+            modifier =
+                Modifier.size(
+                    width = FilledTonalIconButtonTokens.ContainerWidth,
+                    height = FilledTonalIconButtonTokens.ContainerHeight
+                ),
             contentAlignment = Alignment.Center
         ) {
             content()
@@ -704,69 +729,6 @@
 
 /** Contains the default values used by all icon button types. */
 object IconButtonDefaults {
-    /** Default ripple shape for a standard icon button. */
-    val standardShape: Shape
-        @Composable get() = IconButtonTokens.StateLayerShape.value
-
-    /** Default shape for a filled icon button. */
-    val filledShape: Shape
-        @Composable get() = FilledIconButtonTokens.ContainerShape.value
-
-    /** Default shape for an outlined icon button. */
-    val outlinedShape: Shape
-        @Composable get() = OutlinedIconButtonTokens.ContainerShape.value
-
-    @ExperimentalMaterial3ExpressiveApi
-    /** Default round shape for any icon button. */
-    val roundShape: Shape
-        @Composable get() = IconButtonSmallTokens.ContainerShapeRound.value
-
-    @ExperimentalMaterial3ExpressiveApi
-    /** Default square shape for any icon button. */
-    val squareShape: Shape
-        @Composable get() = IconButtonSmallTokens.ContainerShapeSquare.value
-
-    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-    @get:ExperimentalMaterial3ExpressiveApi
-    @ExperimentalMaterial3ExpressiveApi
-    /** Default small narrow container for any icon button. */
-    val SmallIconSize: Dp = IconButtonSmallTokens.IconSize
-
-    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-    @get:ExperimentalMaterial3ExpressiveApi
-    @ExperimentalMaterial3ExpressiveApi
-    /** Default small narrow container for any icon button. */
-    val SmallNarrowContainerSize: DpSize =
-        DpSize(
-            IconButtonSmallTokens.IconSize +
-                IconButtonSmallTokens.NarrowLeadingSpace +
-                IconButtonSmallTokens.NarrowTrailingSpace,
-            IconButtonSmallTokens.ContainerHeight
-        )
-
-    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-    @get:ExperimentalMaterial3ExpressiveApi
-    @ExperimentalMaterial3ExpressiveApi
-    /** Default small narrow container for any icon button. */
-    val SmallContainerSize: DpSize =
-        DpSize(
-            IconButtonSmallTokens.IconSize +
-                IconButtonSmallTokens.UniformLeadingSpace +
-                IconButtonSmallTokens.UniformLeadingSpace,
-            IconButtonSmallTokens.ContainerHeight
-        )
-
-    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-    @get:ExperimentalMaterial3ExpressiveApi
-    @ExperimentalMaterial3ExpressiveApi
-    /** Default small narrow container for any icon button. */
-    val SmallWideContainerSize: DpSize =
-        DpSize(
-            IconButtonSmallTokens.IconSize +
-                IconButtonSmallTokens.WideLeadingSpace +
-                IconButtonSmallTokens.WideTrailingSpace,
-            IconButtonSmallTokens.ContainerHeight
-        )
 
     /** Creates a [IconButtonColors] that represents the default colors used in a [IconButton]. */
     @Composable
@@ -1282,6 +1244,286 @@
             BorderStroke(OutlinedIconButtonTokens.UnselectedOutlineWidth, color)
         }
     }
+
+    /** Default ripple shape for a standard icon button. */
+    val standardShape: Shape
+        @Composable get() = IconButtonTokens.StateLayerShape.value
+
+    /** Default shape for a filled icon button. */
+    val filledShape: Shape
+        @Composable get() = FilledIconButtonTokens.ContainerShape.value
+
+    /** Default shape for an outlined icon button. */
+    val outlinedShape: Shape
+        @Composable get() = OutlinedIconButtonTokens.ContainerShape.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default round shape for any extra small icon button. */
+    val xSmallRoundShape: Shape
+        @Composable get() = XSmallIconButtonTokens.ContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default square shape for any extra small icon button. */
+    val xSmallSquareShape: Shape
+        @Composable get() = XSmallIconButtonTokens.ContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any small icon button. */
+    val smallRoundShape: Shape
+        @Composable get() = SmallIconButtonTokens.ContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any small icon button. */
+    val smallSquareShape: Shape
+        @Composable get() = SmallIconButtonTokens.ContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any medium icon button. */
+    val mediumRoundShape: Shape
+        @Composable get() = MediumIconButtonTokens.ContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any medium icon button. */
+    val mediumSquareShape: Shape
+        @Composable get() = MediumIconButtonTokens.ContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any large icon button. */
+    val largeRoundShape: Shape
+        @Composable get() = LargeIconButtonTokens.ContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any large icon button. */
+    val largeSquareShape: Shape
+        @Composable get() = LargeIconButtonTokens.ContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any xlarge icon button. */
+    val xLargeRoundShape: Shape
+        @Composable get() = XLargeIconButtonTokens.ContainerShapeRound.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default shape for any xlarge icon button. */
+    val xLargeSquareShape: Shape
+        @Composable get() = XLargeIconButtonTokens.ContainerShapeSquare.value
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default container for any extra small icon button. */
+    val xSmallIconSize: Dp = XSmallIconButtonTokens.IconSize
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default size for any small icon button. */
+    val smallIconSize: Dp = SmallIconButtonTokens.IconSize
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default container size for any medium icon button. */
+    val mediumIconSize: Dp = MediumIconButtonTokens.IconSize
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Default size for any large icon button. */
+    val largeIconSize: Dp = LargeIconButtonTokens.IconSize
+
+    /** Default size for any xlarge icon button. */
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    val xLargeIconSize: Dp = XLargeIconButtonTokens.IconSize
+
+    /**
+     * Default container size for any extra small icon button.
+     *
+     * @param widthOption the width of the container
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    fun xSmallContainerSize(
+        widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+    ): DpSize {
+        val horizontalSpace =
+            when (widthOption) {
+                IconButtonWidthOption.Narrow ->
+                    XSmallIconButtonTokens.NarrowLeadingSpace +
+                        XSmallIconButtonTokens.NarrowTrailingSpace
+                IconButtonWidthOption.Uniform ->
+                    XSmallIconButtonTokens.UniformLeadingSpace +
+                        XSmallIconButtonTokens.UniformLeadingSpace
+                IconButtonWidthOption.Wide ->
+                    XSmallIconButtonTokens.WideLeadingSpace +
+                        XSmallIconButtonTokens.WideTrailingSpace
+                else -> 0.dp
+            }
+        return DpSize(
+            XSmallIconButtonTokens.IconSize + horizontalSpace,
+            XSmallIconButtonTokens.ContainerHeight
+        )
+    }
+
+    /**
+     * Default container size for any small icon button.
+     *
+     * @param widthOption the width of the container
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    fun smallContainerSize(
+        widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+    ): DpSize {
+        val horizontalSpace =
+            when (widthOption) {
+                IconButtonWidthOption.Narrow ->
+                    SmallIconButtonTokens.NarrowLeadingSpace +
+                        SmallIconButtonTokens.NarrowTrailingSpace
+                IconButtonWidthOption.Uniform ->
+                    SmallIconButtonTokens.UniformLeadingSpace +
+                        SmallIconButtonTokens.UniformLeadingSpace
+                IconButtonWidthOption.Wide ->
+                    SmallIconButtonTokens.WideLeadingSpace + SmallIconButtonTokens.WideTrailingSpace
+                else -> 0.dp
+            }
+        return DpSize(
+            SmallIconButtonTokens.IconSize + horizontalSpace,
+            SmallIconButtonTokens.ContainerHeight
+        )
+    }
+
+    /**
+     * Default container size for any medium icon button.
+     *
+     * @param widthOption the width of the container
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    fun mediumContainerSize(
+        widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+    ): DpSize {
+        val horizontalSpace =
+            when (widthOption) {
+                IconButtonWidthOption.Narrow ->
+                    MediumIconButtonTokens.NarrowLeadingSpace +
+                        MediumIconButtonTokens.NarrowTrailingSpace
+                IconButtonWidthOption.Uniform ->
+                    MediumIconButtonTokens.UniformLeadingSpace +
+                        MediumIconButtonTokens.UniformLeadingSpace
+                IconButtonWidthOption.Wide ->
+                    MediumIconButtonTokens.WideLeadingSpace +
+                        MediumIconButtonTokens.WideTrailingSpace
+                else -> 0.dp
+            }
+        return DpSize(
+            MediumIconButtonTokens.IconSize + horizontalSpace,
+            MediumIconButtonTokens.ContainerHeight
+        )
+    }
+
+    /**
+     * Default container size for any large icon button.
+     *
+     * @param widthOption the width of the container
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    fun largeContainerSize(
+        widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+    ): DpSize {
+        val horizontalSpace =
+            when (widthOption) {
+                IconButtonWidthOption.Narrow ->
+                    LargeIconButtonTokens.NarrowLeadingSpace +
+                        LargeIconButtonTokens.NarrowTrailingSpace
+                IconButtonWidthOption.Uniform ->
+                    LargeIconButtonTokens.UniformLeadingSpace +
+                        LargeIconButtonTokens.UniformLeadingSpace
+                IconButtonWidthOption.Wide ->
+                    LargeIconButtonTokens.WideLeadingSpace + LargeIconButtonTokens.WideTrailingSpace
+                else -> 0.dp
+            }
+        return DpSize(
+            LargeIconButtonTokens.IconSize + horizontalSpace,
+            LargeIconButtonTokens.ContainerHeight
+        )
+    }
+
+    /**
+     * Default container size for any extra large icon button.
+     *
+     * @param widthOption the width of the container
+     */
+    @ExperimentalMaterial3ExpressiveApi
+    fun xLargeContainerSize(
+        widthOption: IconButtonWidthOption = IconButtonWidthOption.Uniform
+    ): DpSize {
+        val horizontalSpace =
+            when (widthOption) {
+                IconButtonWidthOption.Narrow ->
+                    XLargeIconButtonTokens.NarrowLeadingSpace +
+                        XLargeIconButtonTokens.NarrowTrailingSpace
+                IconButtonWidthOption.Uniform ->
+                    XLargeIconButtonTokens.UniformLeadingSpace +
+                        XLargeIconButtonTokens.UniformLeadingSpace
+                IconButtonWidthOption.Wide ->
+                    XLargeIconButtonTokens.WideLeadingSpace +
+                        XLargeIconButtonTokens.WideTrailingSpace
+                else -> 0.dp
+            }
+        return DpSize(
+            XLargeIconButtonTokens.IconSize + horizontalSpace,
+            XLargeIconButtonTokens.ContainerHeight
+        )
+    }
+
+    /** Class that describes the different supported widths of the [IconButton]. */
+    @JvmInline
+    value class IconButtonWidthOption private constructor(private val value: Int) {
+        companion object {
+            // TODO(b/342666275): update this kdoc with spec guidance
+            /*
+             * This configuration is recommended for small screens.
+             */
+            val Narrow = IconButtonWidthOption(0)
+
+            /*
+             * This configuration is recommended for medium width screens.
+             */
+            val Uniform = IconButtonWidthOption(1)
+
+            /*
+             * This configuration is recommended for wide screens.
+             */
+            val Wide = IconButtonWidthOption(2)
+        }
+
+        override fun toString() =
+            when (this) {
+                Narrow -> "Narrow"
+                Uniform -> "Uniform"
+                Wide -> "Wide"
+                else -> "Unknown"
+            }
+    }
 }
 
 /**
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractionSourceModifierNode.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractionSourceModifierNode.kt
new file mode 100644
index 0000000..14a0f0c
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractionSourceModifierNode.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2024 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.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction
+import androidx.compose.ui.node.traverseDescendants
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * Traversable node that holds the interaction source of the current node and provides the ability
+ * to obtain the interaction sources of its descendants.
+ *
+ * @property interactionSource the [MutableInteractionSource] associated with this current node.
+ */
+private class InteractionSourceModifierNode(var interactionSource: MutableInteractionSource) :
+    Modifier.Node(), TraversableNode {
+    override val traverseKey: Any = InteractionSourceModifierNodeTraverseKey
+}
+
+/**
+ * Node that calls [onChildrenInteractionSourceChange] when there is a remeasure due to child
+ * elements changing.
+ *
+ * @property onChildrenInteractionSourceChange callback that is invoked on remeasure.
+ */
+private class OnChildrenInteractionSourceChangeModifierNode(
+    var onChildrenInteractionSourceChange: (List<InteractionSource>) -> Unit
+) : LayoutAwareModifierNode, Modifier.Node(), DelegatableNode {
+    override fun onRemeasured(size: IntSize) {
+        super.onRemeasured(size)
+        onChildrenInteractionSourceChange(findInteractionSources())
+    }
+}
+
+/**
+ * Finds the interaction sources of the descendants of this node that have the same traverse key.
+ */
+internal fun DelegatableNode.findInteractionSources(): List<MutableInteractionSource> {
+    val interactionSources = mutableListOf<MutableInteractionSource>()
+    traverseDescendants(InteractionSourceModifierNodeTraverseKey) {
+        if (it is InteractionSourceModifierNode) {
+            interactionSources.add(it.interactionSource)
+        }
+        TraverseDescendantsAction.SkipSubtreeAndContinueTraversal
+    }
+    return interactionSources
+}
+
+/**
+ * Modifier used to expose an interaction source to a parent.
+ *
+ * @param interactionSource the [MutableInteractionSource] associated with this current node.
+ */
+internal fun Modifier.interactionSourceData(
+    interactionSource: MutableInteractionSource? = null
+): Modifier =
+    this then InteractionSourceModifierElement(interactionSource ?: MutableInteractionSource())
+
+/**
+ * Modifier used to observe interaction sources that are exposed by child elements, child elements
+ * can provide interaction sources using [Modifier.interactionSourceData].
+ *
+ * @param onChildrenInteractionSourceChange callback invoked when children update their interaction
+ *   sources.
+ */
+internal fun Modifier.onChildrenInteractionSourceChange(
+    onChildrenInteractionSourceChange: (List<InteractionSource>) -> Unit
+): Modifier =
+    this then OnChildrenInteractionSourceChangeModifierElement(onChildrenInteractionSourceChange)
+
+private data class InteractionSourceModifierElement(
+    private val interactionSource: MutableInteractionSource
+) : ModifierNodeElement<InteractionSourceModifierNode>() {
+    override fun create() = InteractionSourceModifierNode(interactionSource)
+
+    override fun update(node: InteractionSourceModifierNode) {
+        node.interactionSource = interactionSource
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "interactionSourceModifierNode"
+        properties["interactionSource"] = interactionSource
+    }
+}
+
+private data class OnChildrenInteractionSourceChangeModifierElement(
+    private val onChildrenInteractionSourceChange: (List<InteractionSource>) -> Unit
+) : ModifierNodeElement<OnChildrenInteractionSourceChangeModifierNode>() {
+    override fun create() =
+        OnChildrenInteractionSourceChangeModifierNode(onChildrenInteractionSourceChange)
+
+    override fun update(node: OnChildrenInteractionSourceChangeModifierNode) {
+        node.onChildrenInteractionSourceChange = onChildrenInteractionSourceChange
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "onChildrenInteractionSourceChangeModifierNode"
+        properties["onChildrenInteractionSourceChange"] = onChildrenInteractionSourceChange
+    }
+}
+
+private object InteractionSourceModifierNodeTraverseKey
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
index 310d6c6..76152f8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
@@ -357,7 +357,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 internal fun DropdownMenuContent(
     modifier: Modifier,
@@ -373,18 +372,16 @@
 ) {
     // Menu open/close animation.
     @Suppress("DEPRECATION") val transition = updateTransition(expandedState, "DropDownMenu")
-
+    // TODO Load the motionScheme tokens from the component tokens file
+    val scaleAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
+    val alphaAnimationSpec = MotionSchemeKeyTokens.FastEffects.value<Float>()
     val scale by
-        transition.animateFloat(
-            // TODO Load the motionScheme tokens from the component tokens file
-            transitionSpec = { MotionSchemeKeyTokens.FastSpatial.value() }
-        ) { expanded ->
+        transition.animateFloat(transitionSpec = { scaleAnimationSpec }) { expanded ->
             if (expanded) ExpandedScaleTarget else ClosedScaleTarget
         }
 
     val alpha by
-        transition.animateFloat(transitionSpec = { MotionSchemeKeyTokens.FastEffects.value() }) {
-            expanded ->
+        transition.animateFloat(transitionSpec = { alphaAnimationSpec }) { expanded ->
             if (expanded) ExpandedAlphaTarget else ClosedAlphaTarget
         }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
index 91cb8fa..e144c31 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
@@ -113,7 +113,6 @@
  *   sheet's window behavior.
  * @param content The content to be displayed inside the bottom sheet.
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 @ExperimentalMaterial3Api
 fun ModalBottomSheet(
@@ -429,7 +428,6 @@
         initialValue = Hidden,
     )
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun Scrim(color: Color, onDismissRequest: () -> Unit, visible: Boolean) {
     // TODO Load the motionScheme tokens from the component tokens file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
index f172805..d1f3576 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationBar.kt
@@ -168,7 +168,6 @@
  *   preview the item in different states. Note that if `null` is provided, interactions will still
  *   happen internally.
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun RowScope.NavigationBarItem(
     selected: Boolean,
@@ -183,13 +182,14 @@
 ) {
     @Suppress("NAME_SHADOWING")
     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+    // TODO Load the motionScheme tokens from the component tokens file
+    val colorAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Color>()
     val styledIcon =
         @Composable {
             val iconColor by
                 animateColorAsState(
                     targetValue = colors.iconColor(selected = selected, enabled = enabled),
-                    // TODO Load the motionScheme tokens from the component tokens file
-                    animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+                    animationSpec = colorAnimationSpec
                 )
             // If there's a label, don't have a11y services repeat the icon description.
             val clearSemantics = label != null && (alwaysShowLabel || selected)
@@ -205,8 +205,7 @@
                 val textColor by
                     animateColorAsState(
                         targetValue = colors.textColor(selected = selected, enabled = enabled),
-                        // TODO Load the motionScheme tokens from the component tokens file
-                        animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+                        animationSpec = colorAnimationSpec
                     )
                 ProvideContentColorTextStyle(
                     contentColor = textColor,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index d1e5dba..6f5dfbc 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -321,7 +321,6 @@
  * @param scrimColor color of the scrim that obscures content when the drawer is open
  * @param content content of the rest of the UI
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun ModalNavigationDrawer(
     drawerContent: @Composable () -> Unit,
@@ -450,7 +449,6 @@
  * @param gesturesEnabled whether or not the drawer can be interacted by gestures
  * @param content content of the rest of the UI
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun DismissibleNavigationDrawer(
     drawerContent: @Composable () -> Unit,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
index 72bff97..76bdd73 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
@@ -373,7 +373,7 @@
     ) {
         val isIconPositionTop = iconPosition == NavigationItemIconPosition.Top
         val indicatorAnimationProgress = animateIndicatorProgressAsState(selected)
-        val iconPositionProgress =
+        val iconPositionProgress by
             animateFloatAsState(
                 targetValue = if (isIconPositionTop) 0f else 1f,
                 // TODO Load the motionScheme tokens from the component tokens file
@@ -382,34 +382,47 @@
 
         // We'll always display only one label, but for the animation to be correct we need two
         // separate composables that will fade in/out appropriately.
-        val labelTopIconAlphaProgress = animateLabelAlphaProgressAsState(isIconPositionTop)
-        val labelStartIconAlphaProgress = animateLabelAlphaProgressAsState(!isIconPositionTop)
-        val labelTopIconModifier =
-            if (!isIconPositionTop) {
-                Modifier.graphicsLayer { alpha = labelTopIconAlphaProgress.value }
-                    // If this label is not being displayed, remove semantics so item's label isn't
-                    // announced twice.
-                    .clearAndSetSemantics {}
-            } else {
-                Modifier.graphicsLayer { alpha = labelTopIconAlphaProgress.value }
-            }
+        val labelTopIconAlphaProgress by
+            animateFloatAsState(
+                targetValue = if (isIconPositionTop) 1f else 0f,
+                // TODO Load the motionScheme tokens from the component tokens file
+                animationSpec = MotionSchemeKeyTokens.DefaultEffects.value(),
+                visibilityThreshold =
+                    if (isIconPositionTop) Spring.DefaultDisplacementThreshold
+                    else LabelAnimationVisibilityThreshold
+            )
         val labelTopIcon: @Composable (() -> Unit) = {
-            Box(modifier = labelTopIconModifier) {
+            Box(
+                modifier =
+                    Modifier.graphicsLayer { alpha = labelTopIconAlphaProgress }
+                        .then(
+                            if (isIconPositionTop) {
+                                Modifier
+                            } else {
+                                // If this label is not being displayed, remove semantics so item's
+                                // label isn't announced twice.
+                                Modifier.clearAndSetSemantics {}
+                            }
+                        )
+            ) {
                 StyledLabel(selected, topIconLabelTextStyle, colors, enabled, label)
             }
         }
-        val labelStartIconModifier =
-            if (isIconPositionTop) {
-                Modifier.graphicsLayer { alpha = labelStartIconAlphaProgress.value }
-                    // If this label is not being displayed, remove semantics so item's label isn't
-                    // announced twice.
-                    .clearAndSetSemantics {}
-            } else {
-                Modifier.graphicsLayer { alpha = labelStartIconAlphaProgress.value }
-            }
         val labelStartIcon =
             @Composable {
-                Box(modifier = labelStartIconModifier) {
+                Box(
+                    modifier =
+                        Modifier.graphicsLayer { alpha = 1f - labelTopIconAlphaProgress }
+                            .then(
+                                if (isIconPositionTop) {
+                                    // If this label is not being displayed, remove semantics so
+                                    // item's label isn't announced twice.
+                                    Modifier.clearAndSetSemantics {}
+                                } else {
+                                    Modifier
+                                }
+                            )
+                ) {
                     StyledLabel(selected, startIconLabelTextStyle, colors, enabled, label)
                 }
             }
@@ -440,7 +453,7 @@
             indicatorAnimationProgress = { indicatorAnimationProgress.value.coerceAtLeast(0f) },
             icon = iconWithBadge,
             iconPosition = iconPosition,
-            iconPositionProgress = { iconPositionProgress.value.coerceAtLeast(0f) },
+            iconPositionProgress = { iconPositionProgress.coerceAtLeast(0f) },
             labelTopIcon = labelTopIcon,
             labelStartIcon = labelStartIcon,
             topIconIndicatorHorizontalPadding = topIconIndicatorHorizontalPadding,
@@ -1115,7 +1128,6 @@
     )
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun animateIndicatorProgressAsState(selected: Boolean) =
     animateFloatAsState(
@@ -1124,18 +1136,6 @@
         animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
     )
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
-@Composable
-private fun animateLabelAlphaProgressAsState(isTargetValue: Boolean) =
-    animateFloatAsState(
-        targetValue = if (isTargetValue) 1f else 0f,
-        // TODO Load the motionScheme tokens from the component tokens file
-        animationSpec = MotionSchemeKeyTokens.DefaultEffects.value(),
-        visibilityThreshold =
-            if (isTargetValue) Spring.DefaultDisplacementThreshold
-            else LabelAnimationVisibilityThreshold
-    )
-
 @Composable
 private fun IndicatorRipple(interactionSource: InteractionSource, indicatorShape: Shape) {
     Box(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
index b518c36..4caab04 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationRail.kt
@@ -168,7 +168,6 @@
  *   preview the item in different states. Note that if `null` is provided, interactions will still
  *   happen internally.
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun NavigationRailItem(
     selected: Boolean,
@@ -183,13 +182,14 @@
 ) {
     @Suppress("NAME_SHADOWING")
     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
+    // TODO Load the motionScheme tokens from the component tokens file
+    val colorAnimationSpec = MotionSchemeKeyTokens.DefaultEffects.value<Color>()
     val styledIcon =
         @Composable {
             val iconColor by
                 animateColorAsState(
                     targetValue = colors.iconColor(selected = selected, enabled = enabled),
-                    // TODO Load the motionScheme tokens from the component tokens file
-                    animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+                    animationSpec = colorAnimationSpec
                 )
             // If there's a label, don't have a11y services repeat the icon description.
             val clearSemantics = label != null && (alwaysShowLabel || selected)
@@ -205,8 +205,7 @@
                 val textColor by
                     animateColorAsState(
                         targetValue = colors.textColor(selected = selected, enabled = enabled),
-                        // TODO Load the motionScheme tokens from the component tokens file
-                        animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
+                        animationSpec = colorAnimationSpec
                     )
                 ProvideContentColorTextStyle(
                     contentColor = textColor,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
index 9241096..84963d6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/RadioButton.kt
@@ -71,7 +71,6 @@
  *   appearance or preview the radio button in different states. Note that if `null` is provided,
  *   interactions will still happen internally.
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun RadioButton(
     selected: Boolean,
@@ -222,7 +221,6 @@
      * @param enabled whether the [RadioButton] is enabled
      * @param selected whether the [RadioButton] is selected
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @Composable
     internal fun radioColor(enabled: Boolean, selected: Boolean): State<Color> {
         val target =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
index 476c2bf..320653d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
@@ -22,7 +22,6 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.scaleIn
@@ -314,7 +313,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun SegmentedButtonContent(
     icon: @Composable () -> Unit,
@@ -326,7 +324,7 @@
     ) {
         val typography = OutlinedSegmentedButtonTokens.LabelTextFont.value
         // TODO Load the motionScheme tokens from the component tokens file
-        val animationSpec: FiniteAnimationSpec<Int> = MotionSchemeKeyTokens.FastSpatial.value()
+        val animationSpec = MotionSchemeKeyTokens.FastSpatial.value<Int>()
         ProvideTextStyle(typography) {
             val scope = rememberCoroutineScope()
             val measurePolicy = remember {
@@ -560,7 +558,6 @@
      * @param inactiveContent typically an icon of [IconSize]. It shows only when the button is not
      *   checked.
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @Composable
     fun Icon(
         active: Boolean,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt
index 01b9fdf..38b6762 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SnackbarHost.kt
@@ -319,7 +319,6 @@
 
 // TODO: to be replaced with the public customizable implementation
 // it's basically tweaked nullable version of Crossfade
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun FadeInFadeOutWithScale(
     current: SnackbarData?,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
index 718a66d..8ff038a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Surface.kt
@@ -31,6 +31,7 @@
 import androidx.compose.runtime.NonRestartableComposable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
@@ -205,6 +206,8 @@
     interactionSource: MutableInteractionSource? = null,
     content: @Composable () -> Unit
 ) {
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
     val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
     CompositionLocalProvider(
         LocalContentColor provides contentColor,
@@ -227,7 +230,8 @@
                         enabled = enabled,
                         onClick = onClick
                     )
-                    .childSemantics(),
+                    .childSemantics()
+                    .interactionSourceData(interactionSource),
             propagateMinConstraints = true
         ) {
             content()
@@ -309,6 +313,8 @@
     interactionSource: MutableInteractionSource? = null,
     content: @Composable () -> Unit
 ) {
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
     val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
     CompositionLocalProvider(
         LocalContentColor provides contentColor,
@@ -332,7 +338,8 @@
                         enabled = enabled,
                         onClick = onClick
                     )
-                    .childSemantics(),
+                    .childSemantics()
+                    .interactionSourceData(interactionSource),
             propagateMinConstraints = true
         ) {
             content()
@@ -414,6 +421,8 @@
     interactionSource: MutableInteractionSource? = null,
     content: @Composable () -> Unit
 ) {
+    @Suppress("NAME_SHADOWING")
+    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
     val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
     CompositionLocalProvider(
         LocalContentColor provides contentColor,
@@ -437,7 +446,8 @@
                         enabled = enabled,
                         onValueChange = onCheckedChange
                     )
-                    .childSemantics(),
+                    .childSemantics()
+                    .interactionSourceData(interactionSource),
             propagateMinConstraints = true
         ) {
             content()
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
index a65c503..249920ec 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Switch.kt
@@ -132,7 +132,6 @@
     )
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 @Suppress("ComposableLambdaParameterNaming", "ComposableLambdaParameterPosition")
 private fun SwitchImpl(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
index a6263eb..724b82a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tab.kt
@@ -269,7 +269,6 @@
  * component uses [LocalContentColor] to provide an interpolated value between [activeColor] and
  * [inactiveColor] depending on the animation status.
  */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun TabTransition(
     activeColor: Color,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
index b1ebe95..0b191f1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
@@ -555,7 +555,7 @@
     fun setTabPositions(positions: List<TabPosition>)
 }
 
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun TabRowImpl(
     modifier: Modifier,
@@ -571,8 +571,7 @@
         contentColor = contentColor
     ) {
         // TODO Load the motionScheme tokens from the component tokens file
-        val tabIndicatorAnimationSpec: FiniteAnimationSpec<Dp> =
-            MotionSchemeKeyTokens.DefaultSpatial.value()
+        val tabIndicatorAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Dp>()
         val scope = remember {
             object : TabIndicatorScope, TabPositionsHolder {
 
@@ -688,7 +687,7 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun ScrollableTabRowImpl(
     selectedTabIndex: Int,
@@ -714,8 +713,7 @@
     ) {
         val coroutineScope = rememberCoroutineScope()
         // TODO Load the motionScheme tokens from the component tokens file
-        val scrollAnimationSpec: FiniteAnimationSpec<Float> =
-            MotionSchemeKeyTokens.DefaultSpatial.value()
+        val scrollAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
         val tabIndicatorAnimationSpec: FiniteAnimationSpec<Dp> =
             MotionSchemeKeyTokens.DefaultSpatial.value()
         val scrollableTabData =
@@ -1023,7 +1021,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun ScrollableTabRowWithSubcomposeImpl(
     selectedTabIndex: Int,
@@ -1039,8 +1036,7 @@
     Surface(modifier = modifier, color = containerColor, contentColor = contentColor) {
         val coroutineScope = rememberCoroutineScope()
         // TODO Load the motionScheme tokens from the component tokens file
-        val scrollAnimationSpec: FiniteAnimationSpec<Float> =
-            MotionSchemeKeyTokens.DefaultSpatial.value()
+        val scrollAnimationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
         val scrollableTabData =
             remember(scrollState, coroutineScope) {
                 ScrollableTabData(
@@ -1285,7 +1281,6 @@
      * @param currentTabPosition [TabPosition] of the currently selected tab. This is used to
      *   calculate the offset of the indicator this modifier is applied to, as well as its width.
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     fun Modifier.tabIndicatorOffset(currentTabPosition: TabPosition): Modifier =
         composed(
             inspectorInfo =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
index fc8f61f..73b4a097 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
@@ -230,7 +230,6 @@
      * @param unfocusedIndicatorLineThickness thickness of the indicator line when the text field is
      *   not focused
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @ExperimentalMaterial3Api
     @Composable
     fun Container(
@@ -1044,7 +1043,6 @@
      * @param focusedBorderThickness thickness of the border when the text field is focused
      * @param unfocusedBorderThickness thickness of the border when the text field is not focused
      */
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @ExperimentalMaterial3Api
     @Composable
     fun Container(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
index f341edbc..bc9c449 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
@@ -1535,7 +1535,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 internal fun ClockFace(
     state: AnalogTimePickerState,
@@ -1669,7 +1668,6 @@
         )
     }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun ClockText(
     modifier: Modifier,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
index ed0c3b7..8ce3633 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
@@ -21,11 +21,9 @@
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.spring
 import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.interaction.FocusInteraction
-import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.interaction.collectIsPressedAsState
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
@@ -45,7 +43,6 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
@@ -63,15 +60,14 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 
 /**
  * TODO link to mio page when available.
  *
  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
  * toggle between the [ButtonShapes] according to user interaction.
  *
@@ -120,8 +116,6 @@
         ToggleButtonDefaults.shapes(
             ToggleButtonDefaults.shape,
             ToggleButtonDefaults.pressedShape,
-            ToggleButtonDefaults.hoveredShape,
-            ToggleButtonDefaults.focusedShape,
             ToggleButtonDefaults.checkedShape
         ),
     colors: ToggleButtonColors = ToggleButtonDefaults.toggleButtonColors(),
@@ -136,66 +130,31 @@
     val isCornerBasedShape =
         shapes.shape is CornerBasedShape &&
             shapes.checkedShape is CornerBasedShape &&
-            shapes.pressedShape is CornerBasedShape &&
-            shapes.hoveredShape is CornerBasedShape &&
-            shapes.focusedShape is CornerBasedShape
+            shapes.pressedShape is CornerBasedShape
 
-    var pressed by remember { mutableStateOf(false) }
-    var hovered by remember { mutableStateOf(false) }
-    var focused by remember { mutableStateOf(false) }
+    val pressed by interactionSource.collectIsPressedAsState()
 
     val state: AnimatedShapeState? =
         if (isCornerBasedShape) {
             val defaultShape = shapes.shape as CornerBasedShape
             val pressedShape = shapes.pressedShape as CornerBasedShape
-            val hoveredShape = shapes.hoveredShape as CornerBasedShape
-            val focusedShape = shapes.focusedShape as CornerBasedShape
             val checkedShape = shapes.checkedShape as CornerBasedShape
             remember {
                 AnimatedShapeState(
                     startShape = if (checked) checkedShape else defaultShape,
                     defaultShape = defaultShape,
                     pressedShape = pressedShape,
-                    hoveredShape = hoveredShape,
-                    focusedShape = focusedShape,
                     checkedShape = checkedShape,
                     spring(),
                 )
             }
         } else null
 
-    LaunchedEffect(interactionSource) {
-        interactionSource.interactions.collectLatest { interaction ->
-            when (interaction) {
-                is PressInteraction.Press -> {
-                    pressed = true
-                }
-                is HoverInteraction.Enter -> {
-                    hovered = true
-                }
-                is FocusInteraction.Focus -> {
-                    focused = true
-                }
-                is PressInteraction.Release,
-                is PressInteraction.Cancel -> {
-                    pressed = false
-                }
-                is HoverInteraction.Exit -> {
-                    hovered = false
-                }
-                is FocusInteraction.Unfocus -> {
-                    focused = false
-                }
-            }
-        }
-    }
-
     val containerColor = colors.containerColor(enabled, checked)
     val contentColor = colors.contentColor(enabled, checked)
     val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
 
-    val buttonShape =
-        shapeByInteraction(isCornerBasedShape, state, shapes, pressed, hovered, focused, checked)
+    val buttonShape = shapeByInteraction(isCornerBasedShape, state, shapes, pressed, checked)
 
     Surface(
         checked = checked,
@@ -234,8 +193,8 @@
  * TODO link to mio page when available.
  *
  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
  * toggle between the [ButtonShapes] according to user interaction.
  *
@@ -282,8 +241,6 @@
         ToggleButtonDefaults.shapes(
             ToggleButtonDefaults.elevatedShape,
             ToggleButtonDefaults.elevatedPressedShape,
-            ToggleButtonDefaults.elevatedHoveredShape,
-            ToggleButtonDefaults.elevatedFocusedShape,
             ToggleButtonDefaults.elevatedCheckedShape
         ),
     colors: ToggleButtonColors = ToggleButtonDefaults.elevatedToggleButtonColors(),
@@ -311,8 +268,8 @@
  * TODO link to mio page when available.
  *
  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
  * toggle between the [ButtonShapes] according to user interaction.
  *
@@ -362,8 +319,6 @@
         ToggleButtonDefaults.shapes(
             ToggleButtonDefaults.tonalShape,
             ToggleButtonDefaults.tonalPressedShape,
-            ToggleButtonDefaults.tonalHoveredShape,
-            ToggleButtonDefaults.tonalFocusedShape,
             ToggleButtonDefaults.tonalCheckedShape
         ),
     colors: ToggleButtonColors = ToggleButtonDefaults.tonalToggleButtonColors(),
@@ -391,8 +346,8 @@
  * TODO link to mio page when available.
  *
  * Toggle button is a toggleable button that switches between primary and tonal colors depending on
- * [checked]'s value. It also morphs between the five shapes provided in [shapes] depending on the
- * state of the interaction with the toggle button as long as the five shapes provided our
+ * [checked]'s value. It also morphs between the three shapes provided in [shapes] depending on the
+ * state of the interaction with the toggle button as long as the three shapes provided our
  * [CornerBasedShape]s. If a shape in [shapes] isn't a [CornerBasedShape], then toggle button will
  * toggle between the [ButtonShapes] according to user interaction.
  *
@@ -440,8 +395,6 @@
         ToggleButtonDefaults.shapes(
             ToggleButtonDefaults.outlinedShape,
             ToggleButtonDefaults.outlinedPressedShape,
-            ToggleButtonDefaults.outlinedHoveredShape,
-            ToggleButtonDefaults.outlinedFocusedShape,
             ToggleButtonDefaults.outlinedCheckedShape
         ),
     colors: ToggleButtonColors = ToggleButtonDefaults.outlinedToggleButtonColors(),
@@ -506,17 +459,10 @@
      *
      * @param shape the unchecked shape for [ButtonShapes]
      * @param pressedShape the unchecked shape for [ButtonShapes]
-     * @param hoverShape the unchecked shape for [ButtonShapes]
-     * @param focusShape the unchecked shape for [ButtonShapes]
      * @param checkedShape the unchecked shape for [ButtonShapes]
      */
-    fun shapes(
-        shape: Shape,
-        pressedShape: Shape,
-        hoverShape: Shape,
-        focusShape: Shape,
-        checkedShape: Shape
-    ): ButtonShapes = ButtonShapes(shape, pressedShape, hoverShape, focusShape, checkedShape)
+    fun shapes(shape: Shape, pressedShape: Shape, checkedShape: Shape): ButtonShapes =
+        ButtonShapes(shape, pressedShape, checkedShape)
 
     /** A round shape that can be used for all [ToggleButton]s and its variants */
     val roundShape: Shape
@@ -554,30 +500,6 @@
     /** The default pressed shape for [OutlinedToggleButton] */
     val outlinedPressedShape: Shape = RoundedCornerShape(6.dp)
 
-    /** The default hovered shape for [ToggleButton] */
-    val hoveredShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default hovered shape for [ElevatedToggleButton] */
-    val elevatedHoveredShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default hovered shape for [TonalToggleButton] */
-    val tonalHoveredShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default hovered shape for [OutlinedToggleButton] */
-    val outlinedHoveredShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default focused shape for [ToggleButton] */
-    val focusedShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default focused shape for [ElevatedToggleButton] */
-    val elevatedFocusedShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default focused shape for [TonalToggleButton] */
-    val tonalFocusedShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default focused shape for [OutlinedToggleButton] */
-    val outlinedFocusedShape: Shape = RoundedCornerShape(6.dp)
-
     // TODO: Change this to the new ButtonSmallTokens.SelectedShape when available
     /** The default checked shape for [ToggleButton] */
     val checkedShape: Shape
@@ -939,17 +861,9 @@
  *
  * @property shape is the unchecked shape.
  * @property pressedShape is the pressed shape.
- * @property hoveredShape is the hovered shape.
- * @property focusedShape is the focused shape.
  * @property checkedShape is the checked shape.
  */
-data class ButtonShapes(
-    val shape: Shape,
-    val pressedShape: Shape,
-    val hoveredShape: Shape,
-    val focusedShape: Shape,
-    val checkedShape: Shape
-)
+data class ButtonShapes(val shape: Shape, val pressedShape: Shape, val checkedShape: Shape)
 
 @Composable
 private fun shapeByInteraction(
@@ -957,19 +871,13 @@
     state: AnimatedShapeState?,
     shapes: ButtonShapes,
     pressed: Boolean,
-    hovered: Boolean,
-    focused: Boolean,
     checked: Boolean
 ): Shape {
     return if (isCornerBasedShape) {
         if (state != null) {
-            LaunchedEffect(pressed, hovered, focused, checked) {
+            LaunchedEffect(pressed, checked) {
                 if (pressed) {
                     state.animateToPressed()
-                } else if (hovered) {
-                    state.animateToHovered()
-                } else if (focused) {
-                    state.animateToFocused()
                 } else if (checked) {
                     state.animateToChecked()
                 } else {
@@ -982,10 +890,6 @@
         }
     } else if (pressed) {
         shapes.pressedShape
-    } else if (hovered) {
-        shapes.hoveredShape
-    } else if (focused) {
-        shapes.focusedShape
     } else if (checked) {
         shapes.checkedShape
     } else {
@@ -997,6 +901,7 @@
 private fun rememberAnimatedShape(state: AnimatedShapeState): Shape {
     val density = LocalDensity.current
     state.density = density
+
     return remember(density) {
         object : Shape {
 
@@ -1028,8 +933,6 @@
     val startShape: CornerBasedShape,
     val defaultShape: CornerBasedShape,
     val pressedShape: CornerBasedShape,
-    val hoveredShape: CornerBasedShape,
-    val focusedShape: CornerBasedShape,
     val checkedShape: CornerBasedShape,
     val spec: SpringSpec<Float>,
 ) {
@@ -1063,10 +966,6 @@
 
     suspend fun animateToDefault() = animateToShape(defaultShape)
 
-    suspend fun animateToHovered() = animateToShape(hoveredShape)
-
-    suspend fun animateToFocused() = animateToShape(focusedShape)
-
     private suspend fun animateToShape(shape: CornerBasedShape) = coroutineScope {
         launch { topStart?.animateTo(shape.topStart.toPx(size, density), spec) }
         launch { topEnd?.animateTo(shape.topEnd.toPx(size, density), spec) }
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 e176caf..e8b1726 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
@@ -16,7 +16,6 @@
 
 package androidx.compose.material3
 
-import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.core.Transition
 import androidx.compose.animation.core.animateFloat
@@ -571,7 +570,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 internal fun Modifier.animateTooltip(transition: Transition<Boolean>): Modifier =
     composed(
         inspectorInfo =
@@ -581,10 +579,8 @@
             }
     ) {
         // TODO Load the motionScheme tokens from the component tokens file
-        val inOutScaleAnimationSpec: FiniteAnimationSpec<Float> =
-            MotionSchemeKeyTokens.FastSpatial.value()
-        val inOutAlphaAnimationSpec: FiniteAnimationSpec<Float> =
-            MotionSchemeKeyTokens.FastEffects.value()
+        val inOutScaleAnimationSpec = MotionSchemeKeyTokens.FastSpatial.value<Float>()
+        val inOutAlphaAnimationSpec = MotionSchemeKeyTokens.FastEffects.value<Float>()
         val scale by
             transition.animateFloat(
                 transitionSpec = { inOutScaleAnimationSpec },
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
index c90d013..f3e8264 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRail.kt
@@ -37,7 +37,9 @@
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.foundation.selection.selectableGroup
 import androidx.compose.material3.internal.DraggableAnchors
+import androidx.compose.material3.internal.Strings
 import androidx.compose.material3.internal.draggableAnchors
+import androidx.compose.material3.internal.getString
 import androidx.compose.material3.internal.systemBarsForVisualComponents
 import androidx.compose.material3.tokens.ColorSchemeKeyTokens
 import androidx.compose.material3.tokens.MotionSchemeKeyTokens
@@ -70,6 +72,7 @@
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
@@ -178,29 +181,27 @@
             LocalMinimumInteractiveComponentSize.current
         }
 
+    // TODO: Load the motionScheme tokens from the component tokens file.
+    val animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Dp>()
     val minWidth by
         animateDpAsState(
             targetValue = if (!expanded) CollapsedRailWidth else ExpandedRailMinWidth,
-            // TODO: Load the motionScheme tokens from the component tokens file.
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+            animationSpec = animationSpec
         )
     val widthFullRange by
         animateDpAsState(
             targetValue = if (!expanded) CollapsedRailWidth else ExpandedRailMaxWidth,
-            // TODO: Load the motionScheme tokens from the component tokens file.
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+            animationSpec = animationSpec
         )
     val itemVerticalSpacedBy by
         animateDpAsState(
             targetValue = if (!expanded) VerticalPaddingBetweenTopIconItems else 0.dp,
-            // TODO: Load the motionScheme tokens from the component tokens file.
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+            animationSpec = animationSpec
         )
     val itemMarginStart by
         animateDpAsState(
             targetValue = if (!expanded) 0.dp else ExpandedRailHorizontalItemPadding,
-            // TODO: Load the motionScheme tokens from the component tokens file.
-            animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value()
+            animationSpec = animationSpec
         )
 
     Surface(
@@ -787,12 +788,14 @@
     content: @Composable () -> Unit
 ) {
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+    val railPaneTitle = getString(string = Strings.WideNavigationRailPaneTitle)
 
     Box(
         modifier =
             modifier
                 .fillMaxHeight()
                 .widthIn(max = openModalRailMaxWidth)
+                .semantics { paneTitle = railPaneTitle }
                 .graphicsLayer {
                     // TODO: Implement predictive back behavior.
                 }
@@ -839,8 +842,7 @@
                 animationSpec = MotionSchemeKeyTokens.DefaultEffects.value()
             )
         var dismiss by remember { mutableStateOf(false) }
-        // TODO: Add this string in Strings.
-        val closeModalRail = "Close modal navigation rail."
+        val closeModalRail = getString(Strings.CloseRail)
         val dismissModalRail =
             if (visible) {
                 Modifier.pointerInput(onDismissRequest) { detectTapGestures { dismiss = true } }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
index 59627d5..865f380 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WideNavigationRailState.kt
@@ -185,7 +185,7 @@
 ): ModalExpandedNavigationRailState {
     val density = LocalDensity.current
     // TODO: Load the motionScheme tokens from the component tokens file.
-    val animationSpec: AnimationSpec<Float> = MotionSchemeKeyTokens.DefaultSpatial.value()
+    val animationSpec = MotionSchemeKeyTokens.DefaultSpatial.value<Float>()
     return rememberSaveable(
         saver = ModalExpandedNavigationRailState.Saver(density, animationSpec, confirmValueChange)
     ) {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt
index d0e4575..db4d0be 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/Strings.kt
@@ -27,6 +27,7 @@
     companion object {
         val NavigationMenu: Strings
         val CloseDrawer: Strings
+        val CloseRail: Strings
         val CloseSheet: Strings
         val DefaultErrorMessage: Strings
         val ExposedDropdownMenu: Strings
@@ -90,6 +91,7 @@
         val TimePickerHourTextField: Strings
         val TimePickerMinuteTextField: Strings
         val TooltipPaneDescription: Strings
+        val WideNavigationRailPaneTitle: Strings
     }
 }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
index 1ad1dcc..cb141ccd 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
@@ -27,7 +27,6 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextFieldLayout
@@ -427,7 +426,6 @@
     )
 }
 
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 internal fun animateBorderStrokeAsState(
     enabled: Boolean,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
index 9e697a1..73196bb 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/pulltorefresh/PullToRefresh.kt
@@ -479,7 +479,6 @@
      *   release
      */
     @Suppress("DEPRECATION")
-    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     @Composable
     fun Indicator(
         state: PullToRefreshState,
@@ -672,7 +671,6 @@
 }
 
 /** The default pull indicator for [PullToRefreshBox] */
-@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun CircularArrowProgressIndicator(
     progress: () -> Float,
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.native.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ButtonGroupSmallTokens.kt
similarity index 65%
rename from kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.native.kt
rename to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ButtonGroupSmallTokens.kt
index 0e510cf..ec51dcd 100644
--- a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.native.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ButtonGroupSmallTokens.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 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.
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+// VERSION: v0_11_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
 
-package androidx.kruth
+package androidx.compose.material3.tokens
 
-internal actual interface PlatformStandardSubjectBuilder
+import androidx.compose.ui.unit.dp
 
-internal actual class PlatformStandardSubjectBuilderImpl
-actual constructor(
-    metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder
+internal object ButtonGroupSmallTokens {
+    val BetweenSpace = 12.0.dp
+    val ContainerHeight = 40.0.dp
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LargeIconButtonTokens.kt
similarity index 62%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LargeIconButtonTokens.kt
index aa3dc18..040a520 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/LargeIconButtonTokens.kt
@@ -13,25 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_4_0
+// VERSION: v0_9_0
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
 
 import androidx.compose.ui.unit.dp
 
-internal object IconButtonSmallTokens {
-    val ContainerHeight = 40.0.dp
+internal object LargeIconButtonTokens {
+    val ContainerHeight = 96.0.dp
     val ContainerShapeRound = ShapeKeyTokens.CornerFull
-    val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
-    val IconSize = 24.0.dp
-    val NarrowLeadingSpace = 4.0.dp
-    val NarrowTrailingSpace = 4.0.dp
-    val OutlinedOutlineWidth = 1.0.dp
+    val ContainerShapeSquare = ShapeKeyTokens.CornerExtraLarge
+    val UniformLeadingSpace = 32.0.dp
+    val UniformTrailingSpace = 32.0.dp
+    val IconSize = 32.0.dp
+    val NarrowLeadingSpace = 16.0.dp
+    val NarrowTrailingSpace = 16.0.dp
+    val OutlinedOutlineWidth = 2.0.dp
     val PressedContainerCornerSizeMultiplierPercent = 50.0f
-    val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
-    val UniformLeadingSpace = 8.0.dp
-    val UniformTrailingSpace = 8.0.dp
-    val WideLeadingSpace = 14.0.dp
-    val WideTrailingSpace = 14.0.dp
+    val SelectedPressedContainerShape = ShapeKeyTokens.CornerExtraLarge
+    val WideLeadingSpace = 48.0.dp
+    val WideTrailingSpace = 48.0.dp
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MediumIconButtonTokens.kt
similarity index 67%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MediumIconButtonTokens.kt
index aa3dc18..310d016 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MediumIconButtonTokens.kt
@@ -13,25 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_4_0
+// VERSION: v0_9_0
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
 
 import androidx.compose.ui.unit.dp
 
-internal object IconButtonSmallTokens {
-    val ContainerHeight = 40.0.dp
+internal object MediumIconButtonTokens {
+    val ContainerHeight = 56.0.dp
     val ContainerShapeRound = ShapeKeyTokens.CornerFull
-    val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
+    val ContainerShapeSquare = ShapeKeyTokens.CornerLarge
     val IconSize = 24.0.dp
-    val NarrowLeadingSpace = 4.0.dp
-    val NarrowTrailingSpace = 4.0.dp
+    val NarrowLeadingSpace = 10.0.dp
+    val NarrowTrailingSpace = 10.0.dp
     val OutlinedOutlineWidth = 1.0.dp
     val PressedContainerCornerSizeMultiplierPercent = 50.0f
-    val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
-    val UniformLeadingSpace = 8.0.dp
-    val UniformTrailingSpace = 8.0.dp
-    val WideLeadingSpace = 14.0.dp
-    val WideTrailingSpace = 14.0.dp
+    val SelectedPressedContainerShape = ShapeKeyTokens.CornerLarge
+    val UniformLeadingSpace = 16.0.dp
+    val UniformTrailingSpace = 16.0.dp
+    val WideLeadingSpace = 24.0.dp
+    val WideTrailingSpace = 24.0.dp
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SmallIconButtonTokens.kt
similarity index 95%
rename from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
rename to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SmallIconButtonTokens.kt
index aa3dc18..e2bda5b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SmallIconButtonTokens.kt
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_4_0
+// VERSION: v0_9_0
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
 
 import androidx.compose.ui.unit.dp
 
-internal object IconButtonSmallTokens {
+internal object SmallIconButtonTokens {
     val ContainerHeight = 40.0.dp
     val ContainerShapeRound = ShapeKeyTokens.CornerFull
     val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XLargeIconButtonTokens.kt
similarity index 62%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XLargeIconButtonTokens.kt
index aa3dc18..372522d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XLargeIconButtonTokens.kt
@@ -13,25 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_4_0
+// VERSION: v0_9_0
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
 
 import androidx.compose.ui.unit.dp
 
-internal object IconButtonSmallTokens {
-    val ContainerHeight = 40.0.dp
+internal object XLargeIconButtonTokens {
+    val ContainerHeight = 136.0.dp
     val ContainerShapeRound = ShapeKeyTokens.CornerFull
-    val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
-    val IconSize = 24.0.dp
-    val NarrowLeadingSpace = 4.0.dp
-    val NarrowTrailingSpace = 4.0.dp
-    val OutlinedOutlineWidth = 1.0.dp
+    val ContainerShapeSquare = ShapeKeyTokens.CornerExtraLarge
+    val IconSize = 40.0.dp
+    val NarrowLeadingSpace = 32.0.dp
+    val NarrowTrailingSpace = 32.0.dp
+    val OutlinedOutlineWidth = 3.0.dp
     val PressedContainerCornerSizeMultiplierPercent = 50.0f
-    val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
-    val UniformLeadingSpace = 8.0.dp
-    val UniformTrailingSpace = 8.0.dp
-    val WideLeadingSpace = 14.0.dp
-    val WideTrailingSpace = 14.0.dp
+    val SelectedPressedContainerShape = ShapeKeyTokens.CornerExtraLarge
+    val UniformLeadingSpace = 48.0.dp
+    val UniformTrailingSpace = 48.0.dp
+    val WideLeadingSpace = 72.0.dp
+    val WideTrailingSpace = 72.0.dp
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XSmallIconButtonTokens.kt
similarity index 80%
copy from compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XSmallIconButtonTokens.kt
index aa3dc18..75ab01b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/IconButtonSmallTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/XSmallIconButtonTokens.kt
@@ -13,25 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_4_0
+// VERSION: v0_9_0
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
 
 import androidx.compose.ui.unit.dp
 
-internal object IconButtonSmallTokens {
-    val ContainerHeight = 40.0.dp
+internal object XSmallIconButtonTokens {
+    val ContainerHeight = 32.0.dp
     val ContainerShapeRound = ShapeKeyTokens.CornerFull
     val ContainerShapeSquare = ShapeKeyTokens.CornerMedium
-    val IconSize = 24.0.dp
+    val IconSize = 20.0.dp
     val NarrowLeadingSpace = 4.0.dp
     val NarrowTrailingSpace = 4.0.dp
     val OutlinedOutlineWidth = 1.0.dp
     val PressedContainerCornerSizeMultiplierPercent = 50.0f
     val SelectedPressedContainerShape = ShapeKeyTokens.CornerMedium
-    val UniformLeadingSpace = 8.0.dp
-    val UniformTrailingSpace = 8.0.dp
-    val WideLeadingSpace = 14.0.dp
-    val WideTrailingSpace = 14.0.dp
+    val UniformLeadingSpace = 6.0.dp
+    val UniformTrailingSpace = 6.0.dp
+    val WideLeadingSpace = 10.0.dp
+    val WideTrailingSpace = 10.0.dp
 }
diff --git a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt
index a2c5526..aaf4893 100644
--- a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt
+++ b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/internal/Strings.commonStubs.kt
@@ -32,6 +32,7 @@
     actual companion object {
         actual val NavigationMenu: Strings = implementedInJetBrainsFork()
         actual val CloseDrawer: Strings = implementedInJetBrainsFork()
+        actual val CloseRail: Strings = implementedInJetBrainsFork()
         actual val CloseSheet: Strings = implementedInJetBrainsFork()
         actual val DefaultErrorMessage: Strings = implementedInJetBrainsFork()
         actual val SliderRangeStart: Strings = implementedInJetBrainsFork()
@@ -95,6 +96,7 @@
         actual val TooltipPaneDescription: Strings = implementedInJetBrainsFork()
         actual val ExposedDropdownMenu: Strings = implementedInJetBrainsFork()
         actual val ToggleDropdownMenu: Strings = implementedInJetBrainsFork()
+        actual val WideNavigationRailPaneTitle: Strings = implementedInJetBrainsFork()
     }
 }
 
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index a82f5ac..fc61115 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -43,7 +43,7 @@
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 implementation(libs.kotlinCoroutinesCore)
-                implementation(project(":collection:collection"))
+                implementation("androidx.collection:collection:1.4.2")
             }
         }
 
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
index 52aa010..65f7a91 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
+++ b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
@@ -24,9 +24,6 @@
 android {
     compileSdk 35
     namespace "androidx.compose.runtime.benchmark"
-
-    // Experiment to observe effect of minification on noise, see b/354264743
-    buildTypes.release.androidTest.enableMinification = true
 }
 
 dependencies {
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
index a4936f1..c7df5e5 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
@@ -709,7 +709,7 @@
     internal data class DrawParams(
         var density: Density = DefaultDensity,
         var layoutDirection: LayoutDirection = LayoutDirection.Ltr,
-        var canvas: Canvas = EmptyCanvas(),
+        var canvas: Canvas = EmptyCanvas,
         var size: Size = Size.Zero
     )
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt
index 28dccc7..8f34f12 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawContext.kt
@@ -47,7 +47,7 @@
 
     /** The target canvas to issue drawing commands */
     var canvas: Canvas
-        get() = EmptyCanvas()
+        get() = EmptyCanvas
         set(_) {}
 
     /** The controller for issuing transformations to the drawing environment */
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
index 8f41a31..6ca81a8 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
@@ -36,8 +36,7 @@
  * thrown at runtime. During normal use, the canvas used within [DrawScope] is consuming a valid
  * Canvas that draws content into a valid destination
  */
-internal class EmptyCanvas : Canvas {
-
+internal object EmptyCanvas : Canvas {
     override fun save() {
         throw UnsupportedOperationException()
     }
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt
index e0e78f2..58ded89 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/injectionscope/touch/MoveWithHistoryTest.kt
@@ -26,8 +26,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.testutils.WithTouchSlop
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
@@ -44,6 +46,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class MoveWithHistoryTest {
@@ -114,9 +117,18 @@
             val from = topCenter + Offset(0f, 120f)
             val to = topCenter + Offset(0f, 100f)
 
-            val historicalTimes = listOf(-16L, -12L, -8L)
+            val historicalTimes =
+                if (VelocityTrackerAddPointsFix) {
+                    listOf(-16L, -12L, -8L)
+                } else {
+                    listOf(-16L, -8L)
+                }
             val historicalCoordinates =
-                listOf(to + Offset(0f, 70f), to + Offset(0f, 55f), to + Offset(0f, 35f))
+                if (VelocityTrackerAddPointsFix) {
+                    listOf(to + Offset(0f, 70f), to + Offset(0f, 55f), to + Offset(0f, 35f))
+                } else {
+                    listOf(to + Offset(0f, 70f), to + Offset(0f, 35f))
+                }
             val delayMillis = 100L
 
             down(from)
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
index ea391f4..819b397 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/TextLayoutCacheTest.kt
@@ -32,6 +32,7 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -54,6 +55,39 @@
     }
 
     @Test
+    fun capacity_one_shouldEvictTheCache_forEveryDifferentLayoutInput() {
+        val textLayoutCache = TextLayoutCache(1)
+
+        val input1 =
+            textLayoutInput(text = AnnotatedString("W"), style = TextStyle(color = Color.Red))
+        textLayoutCache.put(input1, layoutText(input1))
+
+        val input2 =
+            textLayoutInput(text = AnnotatedString("Wo"), style = TextStyle(color = Color.Red))
+        textLayoutCache.put(input2, layoutText(input2))
+        assertThat(textLayoutCache.get(input2)).isNotNull()
+        assertThat(textLayoutCache.get(input1)).isNull()
+
+        val input3 =
+            textLayoutInput(text = AnnotatedString("Wor"), style = TextStyle(color = Color.Red))
+        textLayoutCache.put(input3, layoutText(input3))
+        assertThat(textLayoutCache.get(input3)).isNotNull()
+        assertThat(textLayoutCache.get(input2)).isNull()
+
+        val input4 =
+            textLayoutInput(text = AnnotatedString("Worl"), style = TextStyle(color = Color.Red))
+        textLayoutCache.put(input4, layoutText(input4))
+        assertThat(textLayoutCache.get(input4)).isNotNull()
+        assertThat(textLayoutCache.get(input3)).isNull()
+
+        val input5 =
+            textLayoutInput(text = AnnotatedString("World"), style = TextStyle(color = Color.Red))
+        textLayoutCache.put(input5, layoutText(input5))
+        assertThat(textLayoutCache.get(input5)).isNotNull()
+        assertThat(textLayoutCache.get(input4)).isNull()
+    }
+
+    @Test
     fun exactInput_shouldReturnTheSameResult() {
         val textLayoutCache = TextLayoutCache(16)
         val textLayoutInput =
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
index f3fa771..405ca04 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
@@ -359,10 +359,30 @@
  * @throws IllegalArgumentException if capacity is not a positive integer.
  */
 internal class TextLayoutCache(capacity: Int = DefaultCacheSize) {
-    private val cache = SieveCache<CacheTextLayoutInput, TextLayoutResult>(capacity, capacity)
+    // Do not allocate an LRU cache if the size is just 1.
+    private val cache: SieveCache<CacheTextLayoutInput, TextLayoutResult>? =
+        if (capacity != 1) {
+            // 0 or negative cache size is also handled by SieveCache.
+            SieveCache(capacity, capacity)
+        } else {
+            null
+        }
+
+    private var singleSizeCacheInput: CacheTextLayoutInput? = null
+    private var singleSizeCacheResult: TextLayoutResult? = null
 
     fun get(key: TextLayoutInput): TextLayoutResult? {
-        val resultFromCache = cache[CacheTextLayoutInput(key)] ?: return null
+        val cacheKey = CacheTextLayoutInput(key)
+        val resultFromCache =
+            if (cache != null) {
+                cache[cacheKey]
+            } else if (singleSizeCacheInput == cacheKey) {
+                singleSizeCacheResult
+            } else {
+                return null
+            }
+
+        if (resultFromCache == null) return null
 
         if (resultFromCache.multiParagraph.intrinsics.hasStaleResolvedFonts) {
             // one of the resolved fonts has updated, and this MeasuredText is no longer valid for
@@ -373,12 +393,13 @@
         return resultFromCache
     }
 
-    fun put(key: TextLayoutInput, value: TextLayoutResult): TextLayoutResult? {
-        return cache.put(CacheTextLayoutInput(key), value)
-    }
-
-    fun remove(key: TextLayoutInput): TextLayoutResult? {
-        return cache.remove(CacheTextLayoutInput(key))
+    fun put(key: TextLayoutInput, value: TextLayoutResult) {
+        if (cache != null) {
+            cache.put(CacheTextLayoutInput(key), value)
+        } else {
+            singleSizeCacheInput = CacheTextLayoutInput(key)
+            singleSizeCacheResult = value
+        }
     }
 }
 
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 3dfd54f..e88ca9c 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2052,8 +2052,11 @@
 
   public final class VelocityTrackerKt {
     method public static void addPointerInputChange(androidx.compose.ui.input.pointer.util.VelocityTracker, androidx.compose.ui.input.pointer.PointerInputChange event);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getVelocityTrackerAddPointsFix();
     method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static boolean getVelocityTrackerStrategyUseImpulse();
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setVelocityTrackerAddPointsFix(boolean);
     method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static void setVelocityTrackerStrategyUseImpulse(boolean);
+    property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean VelocityTrackerAddPointsFix;
     property @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static final boolean VelocityTrackerStrategyUseImpulse;
   }
 
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 7929b25..3c279a0 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2052,8 +2052,11 @@
 
   public final class VelocityTrackerKt {
     method public static void addPointerInputChange(androidx.compose.ui.input.pointer.util.VelocityTracker, androidx.compose.ui.input.pointer.PointerInputChange event);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getVelocityTrackerAddPointsFix();
     method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static boolean getVelocityTrackerStrategyUseImpulse();
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setVelocityTrackerAddPointsFix(boolean);
     method @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static void setVelocityTrackerStrategyUseImpulse(boolean);
+    property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean VelocityTrackerAddPointsFix;
     property @SuppressCompatibility @androidx.compose.ui.input.pointer.util.ExperimentalVelocityTrackerApi public static final boolean VelocityTrackerStrategyUseImpulse;
   }
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt
index e3dcf8d..8d0b0ee 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingListParityTest.kt
@@ -33,9 +33,11 @@
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.background
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.tests.R
@@ -70,10 +72,12 @@
     private var latestComposeVelocity = 0f
     private var latestRVState = -1
 
+    @OptIn(ExperimentalComposeUiApi::class)
     @Before
     fun setUp() {
         layoutManager = null
         latestComposeVelocity = 0f
+        VelocityTrackerAddPointsFix = true
     }
 
     @Test
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
index 39354a7..4fef305 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
@@ -33,6 +33,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.background
 import androidx.compose.ui.graphics.Color
@@ -43,6 +44,7 @@
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.positionChangedIgnoreConsumed
+import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
 import androidx.compose.ui.input.pointer.util.addPointerInputChange
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalViewConfiguration
@@ -75,6 +77,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class VelocityTrackingParityTest {
@@ -92,6 +95,7 @@
     @Before
     fun setUp() {
         latestComposeVelocity = Velocity.Zero
+        VelocityTrackerAddPointsFix = true
     }
 
     fun tearDown() {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index 9a2afc9..5e4f56a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -40,6 +40,8 @@
 import androidx.compose.ui.graphics.layer.drawLayer
 import androidx.compose.ui.graphics.layer.setOutline
 import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.internal.requirePrecondition
 import androidx.compose.ui.layout.GraphicLayerInfo
 import androidx.compose.ui.node.OwnedLayer
 import androidx.compose.ui.unit.Density
@@ -351,10 +353,12 @@
         invalidateParentLayer: () -> Unit
     ) {
         val context =
-            requireNotNull(context) {
+            checkPreconditionNotNull(context) {
                 "currently reuse is only supported when we manage the layer lifecycle"
             }
-        require(graphicsLayer.isReleased) { "layer should have been released before reuse" }
+        requirePrecondition(graphicsLayer.isReleased) {
+            "layer should have been released before reuse"
+        }
 
         // recreate a layer
         graphicsLayer = context.createGraphicsLayer()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index 2fc6e18..c934641 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.input.pointer.util
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
@@ -357,7 +358,52 @@
  *
  * @param event Pointer change to track.
  */
+@OptIn(ExperimentalComposeUiApi::class)
 fun VelocityTracker.addPointerInputChange(event: PointerInputChange) {
+    if (VelocityTrackerAddPointsFix) {
+        addPointerInputChangeWithFix(event)
+    } else {
+        addPointerInputChangeLegacy(event)
+    }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private fun VelocityTracker.addPointerInputChangeLegacy(event: PointerInputChange) {
+
+    // Register down event as the starting point for the accumulator
+    if (event.changedToDownIgnoreConsumed()) {
+        currentPointerPositionAccumulator = event.position
+        resetTracking()
+    }
+
+    // To calculate delta, for each step we want to  do currentPosition - previousPosition.
+    // Initially the previous position is the previous position of the current event
+    var previousPointerPosition = event.previousPosition
+    @OptIn(ExperimentalComposeUiApi::class)
+    event.historical.fastForEach {
+        // Historical data happens within event.position and event.previousPosition
+        // That means, event.previousPosition < historical data < event.position
+        // Initially, the first delta will happen between the previousPosition and
+        // the first position in historical delta. For subsequent historical data, the
+        // deltas happen between themselves. That's why we need to update previousPointerPosition
+        // everytime.
+        val historicalDelta = it.position - previousPointerPosition
+        previousPointerPosition = it.position
+
+        // Update the current position with the historical delta and add it to the tracker
+        currentPointerPositionAccumulator += historicalDelta
+        addPosition(it.uptimeMillis, currentPointerPositionAccumulator)
+    }
+
+    // For the last position in the event
+    // If there's historical data, the delta is event.position - lastHistoricalPoint
+    // If there's no historical data, the delta is event.position - event.previousPosition
+    val delta = event.position - previousPointerPosition
+    currentPointerPositionAccumulator += delta
+    addPosition(event.uptimeMillis, currentPointerPositionAccumulator)
+}
+
+private fun VelocityTracker.addPointerInputChangeWithFix(event: PointerInputChange) {
     // If this is ACTION_DOWN: Reset the tracking.
     if (event.changedToDownIgnoreConsumed()) {
         resetTracking()
@@ -624,6 +670,21 @@
 }
 
 /**
+ * A flag to indicate that we'll use the fix of how we add points to the velocity tracker.
+ *
+ * This is an experiment flag and will be removed once the experiments with the fix a finished. The
+ * final goal is that we will use the true path once the flag is removed. If you find any issues
+ * with the new fix, flip this flag to false to confirm they are newly introduced then file a bug.
+ * Tracking bug: (b/318621681)
+ */
+@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:Suppress("GetterSetterNames")
+@get:ExperimentalComposeUiApi
+@set:ExperimentalComposeUiApi
+@ExperimentalComposeUiApi
+var VelocityTrackerAddPointsFix: Boolean = true
+
+/**
  * Selecting flag to enable impulse strategy for the velocity trackers. This is an experiment flag
  * and will be removed once the experiments with the fix a finished. The final goal is that we will
  * use the true path once the flag is removed. If you find any issues with the new fix, flip this
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
index 2f07d423..c58bad7 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
@@ -120,37 +120,6 @@
 
     /** IME visibility is only reliable on API 23+, where we have access to the root WindowInsets */
     @SdkSuppress(minSdkVersion = 23)
-    @Test
-    public fun do_not_show_IME_if_TextView_in_dialog_not_focused() {
-        val dialog =
-            scenario.withActivity {
-                object : Dialog(this) {
-                        override fun onAttachedToWindow() {
-                            super.onAttachedToWindow()
-                            WindowCompat.setDecorFitsSystemWindows(window!!, false)
-                        }
-                    }
-                    .apply { setContentView(R.layout.insets_compat_activity) }
-            }
-
-        val editText = dialog.findViewById<TextView>(R.id.edittext)
-
-        // We hide the edit text to ensure it won't be automatically focused
-        scenario.onActivity {
-            dialog.show()
-            editText.visibility = View.GONE
-            assertThat(editText.isFocused, `is`(false))
-        }
-
-        val type = WindowInsetsCompat.Type.ime()
-        scenario.onActivity {
-            WindowCompat.getInsetsController(dialog.window!!, editText).show(type)
-        }
-        container.assertInsetsVisibility(type, false)
-    }
-
-    /** IME visibility is only reliable on API 23+, where we have access to the root WindowInsets */
-    @SdkSuppress(minSdkVersion = 23)
     @Ignore("b/294556594")
     @Test
     fun show_IME_fromEditText_in_dialog() {
diff --git a/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java b/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
index b165f5c..fcf65ad 100644
--- a/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
+++ b/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
@@ -78,7 +78,8 @@
     }
 
     /**
-     * Cache for Typeface objects dynamically loaded from assets.
+     * Cache for Typeface objects dynamically loaded from assets,
+     * keyed by {@link #createResourceUid(Resources, int, String, int, int)}
      */
     private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
 
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
index 761c25b..509de8d 100644
--- a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
+++ b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
@@ -58,6 +58,9 @@
 
     private FontRequestWorker() {}
 
+    /**
+     * Keyed by {@link #createCacheId(List, int)}
+     */
     static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
 
     private static final ExecutorService DEFAULT_EXECUTOR_SERVICE = RequestExecutor
diff --git a/core/core/src/main/java/androidx/core/view/WindowCompat.java b/core/core/src/main/java/androidx/core/view/WindowCompat.java
index 679f4cf..3c16649 100644
--- a/core/core/src/main/java/androidx/core/view/WindowCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowCompat.java
@@ -164,9 +164,13 @@
 
         static void setDecorFitsSystemWindows(@NonNull Window window,
                 final boolean decorFitsSystemWindows) {
+            final int stableFlag = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+
             final View decorView = window.getDecorView();
             final int sysUiVis = decorView.getSystemUiVisibility();
-            decorView.setSystemUiVisibility(sysUiVis | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+            decorView.setSystemUiVisibility(decorFitsSystemWindows
+                    ? sysUiVis & ~stableFlag
+                    : sysUiVis | stableFlag);
             window.setDecorFitsSystemWindows(decorFitsSystemWindows);
         }
     }
diff --git a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
index f7400fa..e74ec94 100644
--- a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
@@ -393,6 +393,8 @@
     }
 
     private static class Impl {
+        static final int KEY_BEHAVIOR = 356039078;
+
         Impl() {
             //private
         }
@@ -412,7 +414,7 @@
         }
 
         int getSystemBarsBehavior() {
-            return 0;
+            return BEHAVIOR_DEFAULT;
         }
 
         public boolean isAppearanceLightStatusBars() {
@@ -533,6 +535,7 @@
 
         @Override
         void setSystemBarsBehavior(int behavior) {
+            mWindow.getDecorView().setTag(KEY_BEHAVIOR, behavior);
             switch (behavior) {
                 case BEHAVIOR_DEFAULT:
                     unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
@@ -551,7 +554,8 @@
 
         @Override
         int getSystemBarsBehavior() {
-            return 0;
+            final Object behaviorTag = mWindow.getDecorView().getTag(KEY_BEHAVIOR);
+            return behaviorTag != null ? (int) behaviorTag : BEHAVIOR_DEFAULT;
         }
 
         @Override
@@ -777,7 +781,28 @@
          */
         @Override
         void setSystemBarsBehavior(@Behavior int behavior) {
-            mInsetsController.setSystemBarsBehavior(behavior);
+            if (mWindow != null) {
+                // Use the legacy way to control the behavior as a workaround because API 30 has a
+                // bug that the behavior might be cleared unexpectedly after setting a LayoutParam
+                // to a window.
+                mWindow.getDecorView().setTag(KEY_BEHAVIOR, behavior);
+                switch (behavior) {
+                    case BEHAVIOR_DEFAULT:
+                        unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                        setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE);
+                        break;
+                    case BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE:
+                        unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE);
+                        setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                        break;
+                    case BEHAVIOR_SHOW_BARS_BY_TOUCH:
+                        unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE
+                                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                        break;
+                }
+            } else {
+                mInsetsController.setSystemBarsBehavior(behavior);
+            }
         }
 
         /**
@@ -790,7 +815,12 @@
         @Override
         @Behavior
         int getSystemBarsBehavior() {
-            return mInsetsController.getSystemBarsBehavior();
+            if (mWindow != null) {
+                final Object behaviorTag = mWindow.getDecorView().getTag(KEY_BEHAVIOR);
+                return behaviorTag != null ? (int) behaviorTag : BEHAVIOR_DEFAULT;
+            } else {
+                return mInsetsController.getSystemBarsBehavior();
+            }
         }
 
         @Override
@@ -839,8 +869,48 @@
         }
     }
 
+    @RequiresApi(31)
+    private static class Impl31 extends Impl30 {
+
+        Impl31(@NonNull Window window,
+                @NonNull WindowInsetsControllerCompat compatController,
+                @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) {
+            super(window, compatController, softwareKeyboardControllerCompat);
+        }
+
+        Impl31(@NonNull WindowInsetsController insetsController,
+                @NonNull WindowInsetsControllerCompat compatController,
+                @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) {
+            super(insetsController, compatController, softwareKeyboardControllerCompat);
+        }
+
+        /**
+         * Controls the behavior of system bars.
+         *
+         * @param behavior Determines how the bars behave when being hidden by the application.
+         * @see #getSystemBarsBehavior
+         */
+        @Override
+        void setSystemBarsBehavior(@Behavior int behavior) {
+            mInsetsController.setSystemBarsBehavior(behavior);
+        }
+
+        /**
+         * Retrieves the requested behavior of system bars.
+         *
+         * @return the system bar behavior controlled by this window.
+         * @see #setSystemBarsBehavior(int)
+         */
+        @SuppressLint("WrongConstant")
+        @Override
+        @Behavior
+        int getSystemBarsBehavior() {
+            return mInsetsController.getSystemBarsBehavior();
+        }
+    }
+
     @RequiresApi(35)
-    private static class Impl35 extends Impl30 {
+    private static class Impl35 extends Impl31 {
 
         Impl35(@NonNull Window window,
                 @NonNull WindowInsetsControllerCompat compatController,
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
index 2d72cf9..3f650e5 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerJavaTest.java
@@ -21,7 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 
@@ -52,7 +52,7 @@
                 ActivityScenario.launch(TestCredentialsActivity.class);
         activityScenario.onActivity(
                 activity -> {
-                    assumeFalse(deviceHasGMS(getApplicationContext()));
+                    assumeTrue(deviceHasGMS(getApplicationContext()));
                     com.google.android.gms.auth.api.identity.BeginSignInRequest actualResponse =
                             androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity)
                                     .convertRequestToPlayServices(
@@ -70,7 +70,7 @@
                 ActivityScenario.launch(TestCredentialsActivity.class);
         activityScenario.onActivity(
                 activity -> {
-                    assumeFalse(deviceHasGMS(getApplicationContext()));
+                    assumeTrue(deviceHasGMS(getApplicationContext()));
                     com.google.android.gms.auth.api.identity.BeginSignInRequest actualResponse =
                             androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity)
                                     .convertRequestToPlayServices(
@@ -90,7 +90,7 @@
                 ActivityScenario.launch(TestCredentialsActivity.class);
         activityScenario.onActivity(
                 activity -> {
-                    assumeFalse(deviceHasGMS(getApplicationContext()));
+                    assumeTrue(deviceHasGMS(getApplicationContext()));
                     assertThrows(
                             "null get credential request must throw exception",
                             NullPointerException.class,
@@ -106,7 +106,7 @@
                 ActivityScenario.launch(TestCredentialsActivity.class);
         activityScenario.onActivity(
                 activity -> {
-                    assumeFalse(deviceHasGMS(getApplicationContext()));
+                    assumeTrue(deviceHasGMS(getApplicationContext()));
                     assertThrows(
                             "null sign in credential response must throw exception",
                             NullPointerException.class,
@@ -133,7 +133,7 @@
 
         activityScenario.onActivity(
                 activity -> {
-                    assumeFalse(deviceHasGMS(getApplicationContext()));
+                    assumeTrue(deviceHasGMS(getApplicationContext()));
                     com.google.android.gms.auth.api.identity.BeginSignInRequest actualRequest =
                             androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity)
                                     .convertRequestToPlayServices(
@@ -165,7 +165,7 @@
                 ActivityScenario.launch(TestCredentialsActivity.class);
         activityScenario.onActivity(
                 activity -> {
-                assumeFalse(deviceHasGMS(getApplicationContext()));
+                assumeTrue(deviceHasGMS(getApplicationContext()));
                     androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController firstInstance =
                             androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController.getInstance(activity);
                     androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController secondInstance =
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
index 4bc0d41..53c2296 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
@@ -31,7 +31,7 @@
 import com.google.android.gms.common.GoogleApiAvailability
 import com.google.android.libraries.identity.googleid.GetGoogleIdOption
 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
 
@@ -44,7 +44,7 @@
     fun convertRequestToPlayServices_setPasswordOptionRequestAndFalseAutoSelect_success() {
         val activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
         activityScenario.onActivity { activity: TestCredentialsActivity? ->
-            assumeFalse(deviceHasGMS(getApplicationContext()))
+            assumeTrue(deviceHasGMS(getApplicationContext()))
             val actualResponse =
                 getInstance(activity!!)
                     .convertRequestToPlayServices(GetCredentialRequest(listOf(GetPasswordOption())))
@@ -57,7 +57,7 @@
     fun convertRequestToPlayServices_setPasswordOptionRequestAndTrueAutoSelect_success() {
         val activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
         activityScenario.onActivity { activity: TestCredentialsActivity? ->
-            assumeFalse(deviceHasGMS(getApplicationContext()))
+            assumeTrue(deviceHasGMS(getApplicationContext()))
             val actualResponse =
                 getInstance(activity!!)
                     .convertRequestToPlayServices(
@@ -83,7 +83,7 @@
                 .build()
 
         activityScenario.onActivity { activity: TestCredentialsActivity? ->
-            assumeFalse(deviceHasGMS(getApplicationContext()))
+            assumeTrue(deviceHasGMS(getApplicationContext()))
             val actualRequest =
                 getInstance(activity!!)
                     .convertRequestToPlayServices(GetCredentialRequest(listOf(option)))
@@ -106,7 +106,7 @@
     fun duplicateGetInstance_shouldBeUnequal() {
         val activityScenario = ActivityScenario.launch(TestCredentialsActivity::class.java)
         activityScenario.onActivity { activity: TestCredentialsActivity? ->
-            assumeFalse(deviceHasGMS(getApplicationContext()))
+            assumeTrue(deviceHasGMS(getApplicationContext()))
             val firstInstance = getInstance(activity!!)
             val secondInstance = getInstance(activity)
             assertThat(firstInstance).isNotEqualTo(secondInstance)
diff --git a/datastore/datastore-core-okio/bcv/native/current.txt b/datastore/datastore-core-okio/bcv/native/current.txt
index a4e9063..283d853 100644
--- a/datastore/datastore-core-okio/bcv/native/current.txt
+++ b/datastore/datastore-core-okio/bcv/native/current.txt
@@ -1,5 +1,5 @@
 // Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
 // Rendering settings:
 // - Signature version: 2
 // - Show manifest properties: true
diff --git a/datastore/datastore-core-okio/build.gradle b/datastore/datastore-core-okio/build.gradle
index dc55a3d..76f36b5 100644
--- a/datastore/datastore-core-okio/build.gradle
+++ b/datastore/datastore-core-okio/build.gradle
@@ -35,6 +35,8 @@
     mac()
     linux()
     ios()
+    watchos()
+    tvos()
 
     defaultPlatform(PlatformIdentifier.JVM)
 
diff --git a/datastore/datastore-core/bcv/native/current.txt b/datastore/datastore-core/bcv/native/current.txt
index 76b70d8..cac599e 100644
--- a/datastore/datastore-core/bcv/native/current.txt
+++ b/datastore/datastore-core/bcv/native/current.txt
@@ -1,5 +1,5 @@
 // Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
 // Rendering settings:
 // - Signature version: 2
 // - Show manifest properties: true
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index 0a0a304..f184764 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -67,6 +67,8 @@
     mac()
     linux()
     ios()
+    watchos()
+    tvos()
     android()
 
     defaultPlatform(PlatformIdentifier.ANDROID)
@@ -80,7 +82,7 @@
             dependencies {
                 api(libs.kotlinStdlib)
                 api(libs.kotlinCoroutinesCore)
-                api(project(":annotation:annotation"))
+                api("androidx.annotation:annotation:1.8.1")
             }
         }
 
diff --git a/datastore/datastore-preferences-core/bcv/native/current.txt b/datastore/datastore-preferences-core/bcv/native/current.txt
index 3953afe..e0837fe 100644
--- a/datastore/datastore-preferences-core/bcv/native/current.txt
+++ b/datastore/datastore-preferences-core/bcv/native/current.txt
@@ -1,5 +1,5 @@
 // Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
 // Rendering settings:
 // - Signature version: 2
 // - Show manifest properties: true
diff --git a/datastore/datastore-preferences-core/build.gradle b/datastore/datastore-preferences-core/build.gradle
index 94c61c8..efa4b1f 100644
--- a/datastore/datastore-preferences-core/build.gradle
+++ b/datastore/datastore-preferences-core/build.gradle
@@ -37,6 +37,8 @@
     mac()
     linux()
     ios()
+    watchos()
+    tvos()
     // NOTE, if you add android target here, make sure to add the proguard file as well.
 
     defaultPlatform(PlatformIdentifier.JVM)
diff --git a/datastore/datastore-preferences/bcv/native/current.txt b/datastore/datastore-preferences/bcv/native/current.txt
index 68d64eb..6d72e0f 100644
--- a/datastore/datastore-preferences/bcv/native/current.txt
+++ b/datastore/datastore-preferences/bcv/native/current.txt
@@ -1,5 +1,5 @@
 // Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
 // Rendering settings:
 // - Signature version: 2
 // - Show manifest properties: true
diff --git a/datastore/datastore-preferences/build.gradle b/datastore/datastore-preferences/build.gradle
index 6fcf34e..1bf3287 100644
--- a/datastore/datastore-preferences/build.gradle
+++ b/datastore/datastore-preferences/build.gradle
@@ -37,6 +37,8 @@
     mac()
     linux()
     ios()
+    watchos()
+    tvos()
     android()
 
     defaultPlatform(PlatformIdentifier.ANDROID)
diff --git a/datastore/datastore/bcv/native/current.txt b/datastore/datastore/bcv/native/current.txt
index 3c9cae7..3ad1416 100644
--- a/datastore/datastore/bcv/native/current.txt
+++ b/datastore/datastore/bcv/native/current.txt
@@ -1,5 +1,5 @@
 // Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
 // Rendering settings:
 // - Signature version: 2
 // - Show manifest properties: true
diff --git a/datastore/datastore/build.gradle b/datastore/datastore/build.gradle
index c59b19f..6bdb7be 100644
--- a/datastore/datastore/build.gradle
+++ b/datastore/datastore/build.gradle
@@ -37,6 +37,8 @@
     jvm()
     mac()
     ios()
+    watchos()
+    tvos()
     linux()
     android()
 
@@ -47,7 +49,7 @@
             dependencies {
                 api(libs.kotlinStdlib)
                 api(libs.kotlinCoroutinesCore)
-                api(project(":annotation:annotation"))
+                api("androidx.annotation:annotation:1.8.1")
                 api(project(":datastore:datastore-core"))
                 api(project(":datastore:datastore-core-okio"))
             }
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index cc94802..ef118d2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -225,6 +225,8 @@
 kotlinXHtml = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version = "0.7.3" }
 ksp = { module = "com.google.devtools.ksp:symbol-processing", version.ref = "ksp" }
 kspApi = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
+kspCommon = { module = "com.google.devtools.ksp:symbol-processing-common-deps", version.ref = "ksp" }
+kspEmbeddable = { module = "com.google.devtools.ksp:symbol-processing-aa-embeddable", version.ref = "ksp" }
 kspGradlePluginz = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
 ktfmt = { module = "com.facebook:ktfmt", version.ref = "ktfmt" }
 kxml2 = { module = "net.sf.kxml:kxml2", version = "2.3.0" }
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index 7980e5f..3213852 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -39,7 +39,7 @@
     implementation(libs.kspApi)
 
     testImplementation(project(":hilt:hilt-common"))
-    testImplementation(project(":annotation:annotation"))
+    testImplementation("androidx.annotation:annotation:1.8.1")
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     testImplementation(project(":room:room-compiler-processing-testing"))
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
index 3d39c0e..ee50a22 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/work/WorkerStep.kt
@@ -42,6 +42,8 @@
         return emptySet()
     }
 
+    // usage of findTypeElement and requireType with -Pandroidx.maxDepVersions=true
+    @Suppress("DEPRECATION")
     private fun parse(env: XProcessingEnv, workerTypeElement: XTypeElement): WorkerElement? {
         var valid = true
 
diff --git a/kruth/kruth/api/api_lint.ignore b/kruth/kruth/api/api_lint.ignore
index 68e708e..7a03d99 100644
--- a/kruth/kruth/api/api_lint.ignore
+++ b/kruth/kruth/api/api_lint.ignore
@@ -85,12 +85,6 @@
     Callback method names must follow the on<Something> style: invokeAssertion
 
 
-DocumentExceptions: androidx.kruth.ExpectFailure#getFailure():
-    Method ExpectFailure.getFailure appears to be throwing java.lang.AssertionError; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.kruth.ExpectFailure#whenTesting():
-    Method ExpectFailure.whenTesting appears to be throwing androidx.kruth.AssertionErrorWithFacts; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-
-
 ExecutorRegistration: androidx.kruth.ExpectFailure#expectFailure(androidx.kruth.ExpectFailure.StandardSubjectBuilderCallback):
     Registration methods should have overload that accepts delivery Executor: `expectFailure`
 ExecutorRegistration: androidx.kruth.ExpectFailure.Companion#expectFailure(androidx.kruth.ExpectFailure.StandardSubjectBuilderCallback):
@@ -98,13 +92,13 @@
 
 
 InvalidNullabilityOverride: androidx.kruth.Expect#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #0:
-    Invalid nullability on parameter `base` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+    Invalid nullability on type org.junit.runners.model.Statement in parameter `base` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
 InvalidNullabilityOverride: androidx.kruth.Expect#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #1:
-    Invalid nullability on parameter `description` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+    Invalid nullability on type org.junit.runner.Description in parameter `description` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
 InvalidNullabilityOverride: androidx.kruth.ExpectFailure#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #0:
-    Invalid nullability on parameter `base` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+    Invalid nullability on type org.junit.runners.model.Statement in parameter `base` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
 InvalidNullabilityOverride: androidx.kruth.ExpectFailure#apply(org.junit.runners.model.Statement, org.junit.runner.Description) parameter #1:
-    Invalid nullability on parameter `description` in method `apply`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+    Invalid nullability on type org.junit.runner.Description in parameter `description` in method `apply`. Parameter in method override cannot use a non-null type when the corresponding type from the super method is platform-nullness.
 
 
 MissingBuildMethod: androidx.kruth.SimpleSubjectBuilder:
diff --git a/kruth/kruth/api/current.ignore b/kruth/kruth/api/current.ignore
index c12fb51..b21650b 100644
--- a/kruth/kruth/api/current.ignore
+++ b/kruth/kruth/api/current.ignore
@@ -25,6 +25,14 @@
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<T>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ComparableSubject<ComparableT> to androidx.kruth.ComparableSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<T>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.GuavaOptionalSubject to androidx.kruth.GuavaOptionalSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<K,V>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultimapSubject to androidx.kruth.MultimapSubject<K,V>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<T>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultisetSubject to androidx.kruth.MultisetSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<R,C,V>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.TableSubject to androidx.kruth.TableSubject<R,C,V>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(java.util.Map<K,? extends V>):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MapSubject to androidx.kruth.MapSubject<K,V>
 ChangedType: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT):
@@ -39,24 +47,36 @@
 
 InvalidNullConversion: androidx.kruth.IterableSubject#isNotIn(Iterable<?>) parameter #0:
     Attempted to change nullability of java.lang.Object (from NONNULL to NULLABLE) in parameter iterable in androidx.kruth.IterableSubject.isNotIn(Iterable<?> iterable)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(Class<?>) parameter #0:
+    Attempted to change nullability of java.lang.Class<?> (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(Class<?> actual)
 InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(T[]) parameter #0:
     Attempted to change nullability of T (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(T[] actual)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal) parameter #0:
+    Attempted to change nullability of java.math.BigDecimal (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal actual)
 InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#withMessage(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter messageToPrepend in androidx.kruth.StandardSubjectBuilder.withMessage(String messageToPrepend)
 InvalidNullConversion: androidx.kruth.StringSubject#contains(CharSequence) parameter #0:
     Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.contains(CharSequence charSequence)
 InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContain(CharSequence) parameter #0:
     Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.doesNotContain(CharSequence charSequence)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#endsWith(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.endsWith(String string)
 InvalidNullConversion: androidx.kruth.StringSubject#matches(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#matches(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#startsWith(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.startsWith(String string)
 InvalidNullConversion: androidx.kruth.StringSubject.CaseInsensitiveStringComparison#doesNotContain(CharSequence) parameter #0:
@@ -93,10 +113,6 @@
     Class androidx.kruth.Fact no longer implements java.io.Serializable
 
 
-RemovedMethod: androidx.kruth.ComparableSubject#isIn(com.google.common.collect.Range<T>):
-    Removed method androidx.kruth.ComparableSubject.isIn(com.google.common.collect.Range<T>)
-RemovedMethod: androidx.kruth.ComparableSubject#isNotIn(com.google.common.collect.Range<T>):
-    Removed method androidx.kruth.ComparableSubject.isNotIn(com.google.common.collect.Range<T>)
 RemovedMethod: androidx.kruth.IterableSubject#IterableSubject(androidx.kruth.FailureMetadata, Iterable<?>):
     Removed constructor androidx.kruth.IterableSubject(androidx.kruth.FailureMetadata,Iterable<?>)
 RemovedMethod: androidx.kruth.IterableSubject#comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -143,28 +159,8 @@
     Removed method androidx.kruth.PrimitiveFloatArraySubject.usingTolerance(double)
 RemovedMethod: androidx.kruth.StandardSubjectBuilder#about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>):
     Removed method androidx.kruth.StandardSubjectBuilder.about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Class<?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(Class<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.base.Optional<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<?,?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multimap<?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multiset<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<?,?,?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Table<?,?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal)
 RemovedMethod: androidx.kruth.StandardSubjectBuilder#withMessage(String, java.lang.Object...):
     Removed method androidx.kruth.StandardSubjectBuilder.withMessage(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#matches(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.matches(java.util.regex.Pattern)
 RemovedMethod: androidx.kruth.Subject#Subject(androidx.kruth.FailureMetadata, Object):
     Removed constructor androidx.kruth.Subject(androidx.kruth.FailureMetadata,Object)
 RemovedMethod: androidx.kruth.Subject#isInstanceOf(Class<?>):
diff --git a/kruth/kruth/api/current.txt b/kruth/kruth/api/current.txt
index 717aa87..889c6aa 100644
--- a/kruth/kruth/api/current.txt
+++ b/kruth/kruth/api/current.txt
@@ -23,7 +23,9 @@
     method public final void isAtMost(T? other);
     method public void isEquivalentAccordingToCompareTo(T? other);
     method public final void isGreaterThan(T? other);
+    method public final void isIn(com.google.common.collect.Range<T> range);
     method public final void isLessThan(T? other);
+    method public final void isNotIn(com.google.common.collect.Range<T> range);
   }
 
   public final class DoubleSubject extends androidx.kruth.ComparableSubject<java.lang.Double> {
@@ -334,15 +336,21 @@
     method public final androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
     method public final androidx.kruth.Subject<java.lang.Character> that(char actual);
     method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
+    method public final <T> androidx.kruth.GuavaOptionalSubject<T> that(com.google.common.base.Optional<T> actual);
+    method public final <K, V> androidx.kruth.MultimapSubject<K,V> that(com.google.common.collect.Multimap<K,V> actual);
+    method public final <T> androidx.kruth.MultisetSubject<T> that(com.google.common.collect.Multiset<T> actual);
+    method public final <R, C, V> androidx.kruth.TableSubject<R,C,V> that(com.google.common.collect.Table<R,C,V> actual);
     method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
     method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
     method public final androidx.kruth.IntegerSubject that(int actual);
     method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
     method public final androidx.kruth.BooleanSubject that(Boolean? actual);
+    method public final androidx.kruth.ClassSubject that(Class<? extends java.lang.Object?> actual);
     method public final androidx.kruth.DoubleSubject that(Double? actual);
     method public final androidx.kruth.FloatSubject that(Float? actual);
     method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
     method public final androidx.kruth.StringSubject that(String? actual);
+    method public final androidx.kruth.BigDecimalSubject that(java.math.BigDecimal actual);
     method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
     method public final androidx.kruth.LongSubject that(long actual);
     method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
@@ -365,11 +373,14 @@
     ctor protected StringSubject(androidx.kruth.FailureMetadata metadata, String? actual);
     method public void contains(CharSequence charSequence);
     method public void containsMatch(String regex);
+    method public void containsMatch(java.util.regex.Pattern regex);
     method public final void containsMatch(kotlin.text.Regex regex);
     method public void doesNotContain(CharSequence charSequence);
     method public void doesNotContainMatch(String regex);
+    method public void doesNotContainMatch(java.util.regex.Pattern regex);
     method public final void doesNotContainMatch(kotlin.text.Regex regex);
     method public void doesNotMatch(String regex);
+    method public void doesNotMatch(java.util.regex.Pattern regex);
     method public final void doesNotMatch(kotlin.text.Regex regex);
     method public void endsWith(String string);
     method public void hasLength(int expectedLength);
@@ -377,6 +388,7 @@
     method public void isEmpty();
     method public void isNotEmpty();
     method public void matches(String regex);
+    method public void matches(java.util.regex.Pattern regex);
     method public final void matches(kotlin.text.Regex regex);
     method public void startsWith(String string);
   }
diff --git a/kruth/kruth/api/restricted_current.ignore b/kruth/kruth/api/restricted_current.ignore
index c12fb51..b21650b 100644
--- a/kruth/kruth/api/restricted_current.ignore
+++ b/kruth/kruth/api/restricted_current.ignore
@@ -25,6 +25,14 @@
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.IterableSubject to androidx.kruth.IterableSubject<T>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(T):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.ComparableSubject<ComparableT> to androidx.kruth.ComparableSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<T>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.GuavaOptionalSubject to androidx.kruth.GuavaOptionalSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<K,V>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultimapSubject to androidx.kruth.MultimapSubject<K,V>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<T>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MultisetSubject to androidx.kruth.MultisetSubject<T>
+ChangedType: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<R,C,V>):
+    Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.TableSubject to androidx.kruth.TableSubject<R,C,V>
 ChangedType: androidx.kruth.StandardSubjectBuilder#that(java.util.Map<K,? extends V>):
     Method androidx.kruth.StandardSubjectBuilder.that has changed return type from androidx.kruth.MapSubject to androidx.kruth.MapSubject<K,V>
 ChangedType: androidx.kruth.Subject.Factory#createSubject(androidx.kruth.FailureMetadata, ActualT):
@@ -39,24 +47,36 @@
 
 InvalidNullConversion: androidx.kruth.IterableSubject#isNotIn(Iterable<?>) parameter #0:
     Attempted to change nullability of java.lang.Object (from NONNULL to NULLABLE) in parameter iterable in androidx.kruth.IterableSubject.isNotIn(Iterable<?> iterable)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(Class<?>) parameter #0:
+    Attempted to change nullability of java.lang.Class<?> (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(Class<?> actual)
 InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(T[]) parameter #0:
     Attempted to change nullability of T (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(T[] actual)
+InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal) parameter #0:
+    Attempted to change nullability of java.math.BigDecimal (from NULLABLE to NONNULL) in parameter actual in androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal actual)
 InvalidNullConversion: androidx.kruth.StandardSubjectBuilder#withMessage(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter messageToPrepend in androidx.kruth.StandardSubjectBuilder.withMessage(String messageToPrepend)
 InvalidNullConversion: androidx.kruth.StringSubject#contains(CharSequence) parameter #0:
     Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.contains(CharSequence charSequence)
 InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContain(CharSequence) parameter #0:
     Attempted to change nullability of java.lang.CharSequence (from NULLABLE to NONNULL) in parameter charSequence in androidx.kruth.StringSubject.doesNotContain(CharSequence charSequence)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#endsWith(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.endsWith(String string)
 InvalidNullConversion: androidx.kruth.StringSubject#matches(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(String regex)
+InvalidNullConversion: androidx.kruth.StringSubject#matches(java.util.regex.Pattern) parameter #0:
+    Attempted to change nullability of java.util.regex.Pattern (from NULLABLE to NONNULL) in parameter regex in androidx.kruth.StringSubject.matches(java.util.regex.Pattern regex)
 InvalidNullConversion: androidx.kruth.StringSubject#startsWith(String) parameter #0:
     Attempted to change nullability of java.lang.String (from NULLABLE to NONNULL) in parameter string in androidx.kruth.StringSubject.startsWith(String string)
 InvalidNullConversion: androidx.kruth.StringSubject.CaseInsensitiveStringComparison#doesNotContain(CharSequence) parameter #0:
@@ -93,10 +113,6 @@
     Class androidx.kruth.Fact no longer implements java.io.Serializable
 
 
-RemovedMethod: androidx.kruth.ComparableSubject#isIn(com.google.common.collect.Range<T>):
-    Removed method androidx.kruth.ComparableSubject.isIn(com.google.common.collect.Range<T>)
-RemovedMethod: androidx.kruth.ComparableSubject#isNotIn(com.google.common.collect.Range<T>):
-    Removed method androidx.kruth.ComparableSubject.isNotIn(com.google.common.collect.Range<T>)
 RemovedMethod: androidx.kruth.IterableSubject#IterableSubject(androidx.kruth.FailureMetadata, Iterable<?>):
     Removed constructor androidx.kruth.IterableSubject(androidx.kruth.FailureMetadata,Iterable<?>)
 RemovedMethod: androidx.kruth.IterableSubject#comparingElementsUsing(androidx.kruth.Correspondence<? super A,? super E>):
@@ -143,28 +159,8 @@
     Removed method androidx.kruth.PrimitiveFloatArraySubject.usingTolerance(double)
 RemovedMethod: androidx.kruth.StandardSubjectBuilder#about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>):
     Removed method androidx.kruth.StandardSubjectBuilder.about(androidx.kruth.CustomSubjectBuilder.Factory<CustomSubjectBuilderT>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(Class<?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(Class<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.base.Optional<?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.base.Optional<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multimap<?,?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multimap<?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Multiset<?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Multiset<?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(com.google.common.collect.Table<?,?,?>):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(com.google.common.collect.Table<?,?,?>)
-RemovedMethod: androidx.kruth.StandardSubjectBuilder#that(java.math.BigDecimal):
-    Removed method androidx.kruth.StandardSubjectBuilder.that(java.math.BigDecimal)
 RemovedMethod: androidx.kruth.StandardSubjectBuilder#withMessage(String, java.lang.Object...):
     Removed method androidx.kruth.StandardSubjectBuilder.withMessage(String,java.lang.Object...)
-RemovedMethod: androidx.kruth.StringSubject#containsMatch(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.containsMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotContainMatch(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.doesNotContainMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#doesNotMatch(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.doesNotMatch(java.util.regex.Pattern)
-RemovedMethod: androidx.kruth.StringSubject#matches(java.util.regex.Pattern):
-    Removed method androidx.kruth.StringSubject.matches(java.util.regex.Pattern)
 RemovedMethod: androidx.kruth.Subject#Subject(androidx.kruth.FailureMetadata, Object):
     Removed constructor androidx.kruth.Subject(androidx.kruth.FailureMetadata,Object)
 RemovedMethod: androidx.kruth.Subject#isInstanceOf(Class<?>):
diff --git a/kruth/kruth/api/restricted_current.txt b/kruth/kruth/api/restricted_current.txt
index 204fee0..78e2d5a 100644
--- a/kruth/kruth/api/restricted_current.txt
+++ b/kruth/kruth/api/restricted_current.txt
@@ -23,7 +23,9 @@
     method public final void isAtMost(T? other);
     method public void isEquivalentAccordingToCompareTo(T? other);
     method public final void isGreaterThan(T? other);
+    method public final void isIn(com.google.common.collect.Range<T> range);
     method public final void isLessThan(T? other);
+    method public final void isNotIn(com.google.common.collect.Range<T> range);
   }
 
   public final class DoubleSubject extends androidx.kruth.ComparableSubject<java.lang.Double> {
@@ -334,15 +336,21 @@
     method public final androidx.kruth.PrimitiveByteArraySubject that(byte[]? actual);
     method public final androidx.kruth.Subject<java.lang.Character> that(char actual);
     method public final androidx.kruth.PrimitiveCharArraySubject that(char[]? actual);
+    method public final <T> androidx.kruth.GuavaOptionalSubject<T> that(com.google.common.base.Optional<T> actual);
+    method public final <K, V> androidx.kruth.MultimapSubject<K,V> that(com.google.common.collect.Multimap<K,V> actual);
+    method public final <T> androidx.kruth.MultisetSubject<T> that(com.google.common.collect.Multiset<T> actual);
+    method public final <R, C, V> androidx.kruth.TableSubject<R,C,V> that(com.google.common.collect.Table<R,C,V> actual);
     method public final androidx.kruth.PrimitiveDoubleArraySubject that(double[]? actual);
     method public final androidx.kruth.PrimitiveFloatArraySubject that(float[]? actual);
     method public final androidx.kruth.IntegerSubject that(int actual);
     method public final androidx.kruth.PrimitiveIntArraySubject that(int[]? actual);
     method public final androidx.kruth.BooleanSubject that(Boolean? actual);
+    method public final androidx.kruth.ClassSubject that(Class<? extends java.lang.Object?> actual);
     method public final androidx.kruth.DoubleSubject that(Double? actual);
     method public final androidx.kruth.FloatSubject that(Float? actual);
     method public final <T> androidx.kruth.IterableSubject<T> that(Iterable<? extends T>? actual);
     method public final androidx.kruth.StringSubject that(String? actual);
+    method public final androidx.kruth.BigDecimalSubject that(java.math.BigDecimal actual);
     method public final <K, V> androidx.kruth.MapSubject<K,V> that(java.util.Map<K,? extends V>? actual);
     method public final androidx.kruth.LongSubject that(long actual);
     method public final androidx.kruth.PrimitiveLongArraySubject that(long[]? actual);
@@ -365,11 +373,14 @@
     ctor protected StringSubject(androidx.kruth.FailureMetadata metadata, String? actual);
     method public void contains(CharSequence charSequence);
     method public void containsMatch(String regex);
+    method public void containsMatch(java.util.regex.Pattern regex);
     method public final void containsMatch(kotlin.text.Regex regex);
     method public void doesNotContain(CharSequence charSequence);
     method public void doesNotContainMatch(String regex);
+    method public void doesNotContainMatch(java.util.regex.Pattern regex);
     method public final void doesNotContainMatch(kotlin.text.Regex regex);
     method public void doesNotMatch(String regex);
+    method public void doesNotMatch(java.util.regex.Pattern regex);
     method public final void doesNotMatch(kotlin.text.Regex regex);
     method public void endsWith(String string);
     method public void hasLength(int expectedLength);
@@ -377,6 +388,7 @@
     method public void isEmpty();
     method public void isNotEmpty();
     method public void matches(String regex);
+    method public void matches(java.util.regex.Pattern regex);
     method public final void matches(kotlin.text.Regex regex);
     method public void startsWith(String string);
   }
diff --git a/kruth/kruth/bcv/native/current.txt b/kruth/kruth/bcv/native/current.txt
index d555412..fd89b84 100644
--- a/kruth/kruth/bcv/native/current.txt
+++ b/kruth/kruth/bcv/native/current.txt
@@ -207,7 +207,7 @@
     open fun isNoneOf(kotlin/Any?, kotlin/Any?, kotlin/Array<out kotlin/Any?>...) // androidx.kruth/IterableSubject.isNoneOf|isNoneOf(kotlin.Any?;kotlin.Any?;kotlin.Array<out|kotlin.Any?>...){}[0]
     open fun isNotIn(kotlin.collections/Iterable<*>?) // androidx.kruth/IterableSubject.isNotIn|isNotIn(kotlin.collections.Iterable<*>?){}[0]
 }
-open class <#A: kotlin/Comparable<#A>> androidx.kruth/ComparableSubject : androidx.kruth/PlatformComparableSubject<#A>, androidx.kruth/Subject<#A> { // androidx.kruth/ComparableSubject|null[0]
+open class <#A: kotlin/Comparable<#A>> androidx.kruth/ComparableSubject : androidx.kruth/Subject<#A> { // androidx.kruth/ComparableSubject|null[0]
     constructor <init>(androidx.kruth/FailureMetadata, #A?) // androidx.kruth/ComparableSubject.<init>|<init>(androidx.kruth.FailureMetadata;1:0?){}[0]
     final fun isAtLeast(#A?) // androidx.kruth/ComparableSubject.isAtLeast|isAtLeast(1:0?){}[0]
     final fun isAtMost(#A?) // androidx.kruth/ComparableSubject.isAtMost|isAtMost(1:0?){}[0]
@@ -264,7 +264,7 @@
     final fun isWithin(kotlin/Long): androidx.kruth/LongSubject.TolerantLongComparison // androidx.kruth/LongSubject.isWithin|isWithin(kotlin.Long){}[0]
     open fun isEquivalentAccordingToCompareTo(kotlin/Long?) // androidx.kruth/LongSubject.isEquivalentAccordingToCompareTo|isEquivalentAccordingToCompareTo(kotlin.Long?){}[0]
 }
-open class androidx.kruth/StandardSubjectBuilder : androidx.kruth/PlatformStandardSubjectBuilder { // androidx.kruth/StandardSubjectBuilder|null[0]
+open class androidx.kruth/StandardSubjectBuilder { // androidx.kruth/StandardSubjectBuilder|null[0]
     final fun <#A1: kotlin/Any?, #B1: androidx.kruth/Subject<#A1>> about(androidx.kruth/Subject.Factory<#B1, #A1>): androidx.kruth/SimpleSubjectBuilder<#B1, #A1> // androidx.kruth/StandardSubjectBuilder.about|about(androidx.kruth.Subject.Factory<0:1,0:0>){0§<kotlin.Any?>;1§<androidx.kruth.Subject<0:0>>}[0]
     final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> that(kotlin.collections/Map<#A1, #B1>?): androidx.kruth/MapSubject<#A1, #B1> // androidx.kruth/StandardSubjectBuilder.that|that(kotlin.collections.Map<0:0,0:1>?){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
     final fun <#A1: kotlin/Any?> that(#A1?): androidx.kruth/Subject<#A1> // androidx.kruth/StandardSubjectBuilder.that|that(0:0?){0§<kotlin.Any?>}[0]
@@ -295,7 +295,7 @@
         final fun forCustomFailureStrategy(androidx.kruth/FailureStrategy): androidx.kruth/StandardSubjectBuilder // androidx.kruth/StandardSubjectBuilder.Companion.forCustomFailureStrategy|forCustomFailureStrategy(androidx.kruth.FailureStrategy){}[0]
     }
 }
-open class androidx.kruth/StringSubject : androidx.kruth/ComparableSubject<kotlin/String>, androidx.kruth/PlatformStringSubject { // androidx.kruth/StringSubject|null[0]
+open class androidx.kruth/StringSubject : androidx.kruth/ComparableSubject<kotlin/String> { // androidx.kruth/StringSubject|null[0]
     constructor <init>(androidx.kruth/FailureMetadata, kotlin/String?) // androidx.kruth/StringSubject.<init>|<init>(androidx.kruth.FailureMetadata;kotlin.String?){}[0]
     final fun containsMatch(kotlin.text/Regex) // androidx.kruth/StringSubject.containsMatch|containsMatch(kotlin.text.Regex){}[0]
     final fun doesNotContainMatch(kotlin.text/Regex) // androidx.kruth/StringSubject.doesNotContainMatch|doesNotContainMatch(kotlin.text.Regex){}[0]
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
index 6c4407b..c13fdd6 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/ComparableSubject.kt
@@ -25,15 +25,13 @@
  * @constructor Constructor for use by subclasses. If you want to create an instance of this class
  *   itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-open class ComparableSubject<T : Comparable<T>>
+expect open class ComparableSubject<T : Comparable<T>>
 protected constructor(
     metadata: FailureMetadata,
     actual: T?,
-) :
-    Subject<T>(actual, metadata, typeDescriptionOverride = null),
-    PlatformComparableSubject<T> by PlatformComparableSubjectImpl(actual, metadata) {
+) : Subject<T> {
 
-    internal constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+    internal constructor(actual: T?, metadata: FailureMetadata)
 
     /**
      * Checks that the subject is equivalent to [other] according to [Comparable.compareTo], (i.e.,
@@ -41,78 +39,78 @@
      *
      * **Note:** Do not use this method for checking object equality. Instead, use [isEqualTo].
      */
-    open fun isEquivalentAccordingToCompareTo(other: T?) {
-        requireNonNull(actual)
-        requireNonNull(other)
-
-        if (actual.compareTo(other) != 0) {
-            failWithActualInternal(fact("Expected value that sorts equal to", other))
-        }
-    }
+    open fun isEquivalentAccordingToCompareTo(other: T?)
 
     /**
      * Checks that the subject is greater than [other].
      *
      * To check that the subject is greater than *or equal to* [other], use [isAtLeast].
      */
-    fun isGreaterThan(other: T?) {
-        requireNonNull(actual)
-        requireNonNull(other)
-
-        if (actual <= other) {
-            failWithActualInternal(fact("Expected to be greater than", other))
-        }
-    }
+    fun isGreaterThan(other: T?)
 
     /**
      * Checks that the subject is less than [other].
      *
      * @throws NullPointerException if [actual] or [other] is `null`.
      */
-    fun isLessThan(other: T?) {
-        requireNonNull(actual) { "Expected to be less than $other, but was $actual" }
-        requireNonNull(other) { "Expected to be less than $other, but was $actual" }
-
-        if (actual >= other) {
-            failWithActualInternal(fact("Expected to be less than", other))
-        }
-    }
+    fun isLessThan(other: T?)
 
     /**
      * Checks that the subject is less than or equal to [other].
      *
      * @throws NullPointerException if [actual] or [other] is `null`.
      */
-    fun isAtMost(other: T?) {
-        requireNonNull(actual) { "Expected to be at most $other, but was $actual" }
-        requireNonNull(other) { "Expected to be at most $other, but was $actual" }
-        if (actual > other) {
-            failWithActualInternal(fact("Expected to be at most", other))
-        }
-    }
+    fun isAtMost(other: T?)
 
     /**
      * Checks that the subject is greater than or equal to [other].
      *
      * @throws NullPointerException if [actual] or [other] is `null`.
      */
-    fun isAtLeast(other: T?) {
-        requireNonNull(actual) { "Expected to be at least $other, but was $actual" }
-        requireNonNull(other) { "Expected to be at least $other, but was $actual" }
-        if (actual < other) {
-            failWithActualInternal(fact("Expected to be at least", other))
-        }
+    fun isAtLeast(other: T?)
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsEquivalentAccordingToCompareTo(
+    other: T?
+) {
+    requireNonNull(actual)
+    requireNonNull(other)
+
+    if (actual.compareTo(other) != 0) {
+        failWithActualInternal(fact("Expected value that sorts equal to", other))
     }
 }
 
-/**
- * Platform-specific propositions for [Comparable] typed subjects.
- *
- * @param T the type of the object being tested by this [ComparableSubject]
- */
-internal expect interface PlatformComparableSubject<T : Comparable<T>>
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsGreaterThan(other: T?) {
+    requireNonNull(actual)
+    requireNonNull(other)
 
-internal expect class PlatformComparableSubjectImpl<T : Comparable<T>>(
-    actual: T?,
-    metadata: FailureMetadata,
-) : Subject<T>, PlatformComparableSubject<T>
+    if (actual <= other) {
+        failWithActualInternal(fact("Expected to be greater than", other))
+    }
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsLessThan(other: T?) {
+    requireNonNull(actual) { "Expected to be less than $other, but was $actual" }
+    requireNonNull(other) { "Expected to be less than $other, but was $actual" }
+
+    if (actual >= other) {
+        failWithActualInternal(fact("Expected to be less than", other))
+    }
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsAtMost(other: T?) {
+    requireNonNull(actual) { "Expected to be at most $other, but was $actual" }
+    requireNonNull(other) { "Expected to be at most $other, but was $actual" }
+    if (actual > other) {
+        failWithActualInternal(fact("Expected to be at most", other))
+    }
+}
+
+internal fun <T : Comparable<T>> ComparableSubject<T>.commonIsAtLeast(other: T?) {
+    requireNonNull(actual) { "Expected to be at least $other, but was $actual" }
+    requireNonNull(other) { "Expected to be at least $other, but was $actual" }
+    if (actual < other) {
+        failWithActualInternal(fact("Expected to be at least", other))
+    }
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
index 1b23c84..8348fb9 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StandardSubjectBuilder.kt
@@ -18,8 +18,6 @@
 
 import kotlin.jvm.JvmStatic
 
-typealias CharSubject = Subject<Char>
-
 /**
  * In a fluent assertion chain, an object with which you can do any of the following:
  * - Set an optional message with [withMessage].
@@ -27,22 +25,13 @@
  *   [withMessage].
  */
 @Suppress("StaticFinalBuilder") // Cannot be final for binary compatibility.
-open class StandardSubjectBuilder
-internal constructor(
-    metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder by PlatformStandardSubjectBuilderImpl(metadata) {
-    internal val metadata = metadata
-        get() {
-            checkStatePreconditions()
-            return field
-        }
+expect open class StandardSubjectBuilder internal constructor(metadata: FailureMetadata) {
+    internal val metadata: FailureMetadata
 
     companion object {
         /** Returns a new instance that invokes the given [FailureStrategy] when a check fails. */
         @JvmStatic
-        fun forCustomFailureStrategy(failureStrategy: FailureStrategy): StandardSubjectBuilder {
-            return StandardSubjectBuilder(FailureMetadata(failureStrategy = failureStrategy))
-        }
+        fun forCustomFailureStrategy(failureStrategy: FailureStrategy): StandardSubjectBuilder
     }
 
     /**
@@ -50,75 +39,59 @@
      * this method is called multiple times, the messages will appear in the order that they were
      * specified.
      */
-    fun withMessage(messageToPrepend: String): StandardSubjectBuilder =
-        StandardSubjectBuilder(metadata = metadata.withMessage(message = messageToPrepend))
+    fun withMessage(messageToPrepend: String): StandardSubjectBuilder
 
-    fun <T> that(actual: T?): Subject<T> = Subject(actual = actual, metadata = metadata, null)
+    fun <T> that(actual: T?): Subject<T>
 
     // actual cannot be made nullable due to autoboxing and this overload is necessary to allow
     // StandardSubjectBuilder.that(char) from Java to resolve properly as an Object
     // (otherwise it is source-incompatibly interpreted as Int).
     // See: NumericComparisonTest#testNumericPrimitiveTypes_isNotEqual_shouldFail_charToInt
-    fun that(actual: Char): Subject<Char> = Subject(actual = actual, metadata = metadata, null)
+    fun that(actual: Char): Subject<Char>
 
-    fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T> =
-        ComparableSubject(actual = actual, metadata = metadata)
+    fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T>
 
-    fun <T : Throwable> that(actual: T?): ThrowableSubject<T> =
-        ThrowableSubject(actual = actual, metadata = metadata, "throwable")
+    fun <T : Throwable> that(actual: T?): ThrowableSubject<T>
 
-    fun that(actual: Boolean?): BooleanSubject =
-        BooleanSubject(actual = actual, metadata = metadata)
+    fun that(actual: Boolean?): BooleanSubject
 
-    fun that(actual: Long): LongSubject = LongSubject(actual = actual, metadata = metadata)
+    fun that(actual: Long): LongSubject
 
     // Workaround for https://youtrack.jetbrains.com/issue/KT-645
-    fun <T : Long?> that(actual: T): LongSubject = LongSubject(actual = actual, metadata = metadata)
+    fun <T : Long?> that(actual: T): LongSubject
 
-    fun that(actual: Double?): DoubleSubject = DoubleSubject(actual = actual, metadata = metadata)
+    fun that(actual: Double?): DoubleSubject
 
-    fun that(actual: Float?): FloatSubject = FloatSubject(actual = actual, metadata = metadata)
+    fun that(actual: Float?): FloatSubject
 
-    fun that(actual: Int): IntegerSubject = IntegerSubject(actual = actual, metadata = metadata)
+    fun that(actual: Int): IntegerSubject
 
     // Workaround for https://youtrack.jetbrains.com/issue/KT-645
-    fun <T : Int?> that(actual: T): IntegerSubject =
-        IntegerSubject(actual = actual, metadata = metadata)
+    fun <T : Int?> that(actual: T): IntegerSubject
 
-    fun that(actual: String?): StringSubject = StringSubject(actual = actual, metadata = metadata)
+    fun that(actual: String?): StringSubject
 
-    fun <T> that(actual: Iterable<T>?): IterableSubject<T> =
-        IterableSubject(actual = actual, metadata = metadata)
+    fun <T> that(actual: Iterable<T>?): IterableSubject<T>
 
-    fun <T> that(actual: Array<out T>?): ObjectArraySubject<T> =
-        ObjectArraySubject(actual = actual, metadata = metadata)
+    fun <T> that(actual: Array<out T>?): ObjectArraySubject<T>
 
-    fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject =
-        PrimitiveBooleanArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject
 
-    fun that(actual: ShortArray?): PrimitiveShortArraySubject =
-        PrimitiveShortArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: ShortArray?): PrimitiveShortArraySubject
 
-    fun that(actual: IntArray?): PrimitiveIntArraySubject =
-        PrimitiveIntArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: IntArray?): PrimitiveIntArraySubject
 
-    fun that(actual: LongArray?): PrimitiveLongArraySubject =
-        PrimitiveLongArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: LongArray?): PrimitiveLongArraySubject
 
-    fun that(actual: ByteArray?): PrimitiveByteArraySubject =
-        PrimitiveByteArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: ByteArray?): PrimitiveByteArraySubject
 
-    fun that(actual: CharArray?): PrimitiveCharArraySubject =
-        PrimitiveCharArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: CharArray?): PrimitiveCharArraySubject
 
-    fun that(actual: FloatArray?): PrimitiveFloatArraySubject =
-        PrimitiveFloatArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: FloatArray?): PrimitiveFloatArraySubject
 
-    fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject =
-        PrimitiveDoubleArraySubject(actual = actual, metadata = metadata)
+    fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject
 
-    fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V> =
-        MapSubject(actual = actual, metadata = metadata)
+    fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V>
 
     /**
      * Given a factory for some [Subject] class, returns [SimpleSubjectBuilder] whose
@@ -127,8 +100,7 @@
      */
     fun <T, S : Subject<T>> about(
         subjectFactory: Subject.Factory<S, T>,
-    ): SimpleSubjectBuilder<S, T> =
-        SimpleSubjectBuilder(metadata = metadata, subjectFactory = subjectFactory)
+    ): SimpleSubjectBuilder<S, T>
 
     /**
      * Reports a failure.
@@ -136,16 +108,104 @@
      * To set a message, first call [withMessage] (or, more commonly, use the shortcut
      * [assertWithMessage].
      */
-    fun fail() {
-        metadata.fail()
-    }
+    fun fail()
 
-    internal open fun checkStatePreconditions() {}
+    internal open fun checkStatePreconditions()
 }
 
-/** Platform-specific additions for [StandardSubjectBuilder]. */
-internal expect interface PlatformStandardSubjectBuilder
+internal fun commonForCustomFailureStrategy(
+    failureStrategy: FailureStrategy
+): StandardSubjectBuilder {
+    return StandardSubjectBuilder(FailureMetadata(failureStrategy = failureStrategy))
+}
 
-internal expect class PlatformStandardSubjectBuilderImpl(
-    metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder
+internal fun StandardSubjectBuilder.commonWithMessage(
+    messageToPrepend: String
+): StandardSubjectBuilder =
+    StandardSubjectBuilder(metadata = metadata.withMessage(message = messageToPrepend))
+
+internal fun <T> StandardSubjectBuilder.commonThat(actual: T?): Subject<T> =
+    Subject(actual = actual, metadata = metadata, null)
+
+// actual cannot be made nullable due to autoboxing and this overload is necessary to allow
+// StandardSubjectBuilder.that(char) from Java to resolve properly as an Object
+// (otherwise it is source-incompatibly interpreted as Int).
+// See: NumericComparisonTest#testNumericPrimitiveTypes_isNotEqual_shouldFail_charToInt
+internal fun StandardSubjectBuilder.commonThat(actual: Char): Subject<Char> =
+    Subject(actual = actual, metadata = metadata, null)
+
+internal fun <T : Comparable<T>> StandardSubjectBuilder.commonThat(
+    actual: T?
+): ComparableSubject<T> = ComparableSubject(actual = actual, metadata = metadata)
+
+internal fun <T : Throwable> StandardSubjectBuilder.commonThat(actual: T?): ThrowableSubject<T> =
+    ThrowableSubject(actual = actual, metadata = metadata, "throwable")
+
+internal fun StandardSubjectBuilder.commonThat(actual: Boolean?): BooleanSubject =
+    BooleanSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Long): LongSubject =
+    LongSubject(actual = actual, metadata = metadata)
+
+// Workaround for https://youtrack.jetbrains.com/issue/KT-645
+internal fun <T : Long?> StandardSubjectBuilder.commonThat(actual: T): LongSubject =
+    LongSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Double?): DoubleSubject =
+    DoubleSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Float?): FloatSubject =
+    FloatSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: Int): IntegerSubject =
+    IntegerSubject(actual = actual, metadata = metadata)
+
+// Workaround for https://youtrack.jetbrains.com/issue/KT-645
+internal fun <T : Int?> StandardSubjectBuilder.commonThat(actual: T): IntegerSubject =
+    IntegerSubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: String?): StringSubject =
+    StringSubject(actual = actual, metadata = metadata)
+
+internal fun <T> StandardSubjectBuilder.commonThat(actual: Iterable<T>?): IterableSubject<T> =
+    IterableSubject(actual = actual, metadata = metadata)
+
+internal fun <T> StandardSubjectBuilder.commonThat(actual: Array<out T>?): ObjectArraySubject<T> =
+    ObjectArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(
+    actual: BooleanArray?
+): PrimitiveBooleanArraySubject = PrimitiveBooleanArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: ShortArray?): PrimitiveShortArraySubject =
+    PrimitiveShortArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: IntArray?): PrimitiveIntArraySubject =
+    PrimitiveIntArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: LongArray?): PrimitiveLongArraySubject =
+    PrimitiveLongArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: ByteArray?): PrimitiveByteArraySubject =
+    PrimitiveByteArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: CharArray?): PrimitiveCharArraySubject =
+    PrimitiveCharArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: FloatArray?): PrimitiveFloatArraySubject =
+    PrimitiveFloatArraySubject(actual = actual, metadata = metadata)
+
+internal fun StandardSubjectBuilder.commonThat(actual: DoubleArray?): PrimitiveDoubleArraySubject =
+    PrimitiveDoubleArraySubject(actual = actual, metadata = metadata)
+
+internal fun <K, V> StandardSubjectBuilder.commonThat(actual: Map<K, V>?): MapSubject<K, V> =
+    MapSubject(actual = actual, metadata = metadata)
+
+internal fun <T, S : Subject<T>> StandardSubjectBuilder.commonAbout(
+    subjectFactory: Subject.Factory<S, T>,
+): SimpleSubjectBuilder<S, T> =
+    SimpleSubjectBuilder(metadata = metadata, subjectFactory = subjectFactory)
+
+internal fun StandardSubjectBuilder.commonFail() {
+    metadata.fail()
+}
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
index ee0a93c..a07fea8 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
@@ -25,123 +25,55 @@
  * @constructor Constructor for use by subclasses. If you want to create an instance of this class
  *   itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-open class StringSubject
-protected constructor(
-    metadata: FailureMetadata,
-    actual: String?,
-) :
-    ComparableSubject<String>(actual, metadata),
-    PlatformStringSubject by PlatformStringSubjectImpl(actual, metadata) {
+expect open class StringSubject protected constructor(metadata: FailureMetadata, actual: String?) :
+    ComparableSubject<String> {
 
-    internal constructor(actual: String?, metadata: FailureMetadata) : this(metadata, actual)
+    internal constructor(actual: String?, metadata: FailureMetadata)
 
     /** Fails if the string does not contain the given sequence. */
-    open fun contains(charSequence: CharSequence) {
-        if (actual == null) {
-            failWithActual("expected a string that contains", charSequence)
-        } else if (!actual.contains(charSequence)) {
-            failWithActual("expected to contain", charSequence)
-        }
-    }
+    open fun contains(charSequence: CharSequence)
 
     /** Fails if the string does not have the given length. */
-    open fun hasLength(expectedLength: Int) {
-        require(expectedLength >= 0) { "expectedLength($expectedLength) must be >= 0" }
-        check("length").that(requireNonNull(actual).length).isEqualTo(expectedLength)
-    }
+    open fun hasLength(expectedLength: Int)
 
     /** Fails if the string is not equal to the zero-length "empty string." */
-    open fun isEmpty() {
-        if (actual == null) {
-            failWithActual(simpleFact("expected an empty string"))
-        } else if (actual.isNotEmpty()) {
-            failWithActual(simpleFact("expected to be string"))
-        }
-    }
+    open fun isEmpty()
 
     /** Fails if the string is equal to the zero-length "empty string." */
-    open fun isNotEmpty() {
-        if (actual == null) {
-            failWithActual(simpleFact("expected a non-empty string"))
-        } else if (actual.isEmpty()) {
-            failWithoutActual(simpleFact("expected not to be empty"))
-        }
-    }
+    open fun isNotEmpty()
 
     /** Fails if the string contains the given sequence. */
-    open fun doesNotContain(charSequence: CharSequence) {
-        if (actual == null) {
-            failWithActual("expected a string that does not contain", charSequence)
-        } else if (actual.contains(charSequence)) {
-            failWithActual("expected not to contain", charSequence)
-        }
-    }
+    open fun doesNotContain(charSequence: CharSequence)
 
     /** Fails if the string does not start with the given string. */
-    open fun startsWith(string: String) {
-        if (actual == null) {
-            failWithActual("expected a string that starts with", string)
-        } else if (!actual.startsWith(string)) {
-            failWithActual("expected to start with", string)
-        }
-    }
+    open fun startsWith(string: String)
 
     /** Fails if the string does not end with the given string. */
-    open fun endsWith(string: String) {
-        if (actual == null) {
-            failWithActual("expected a string that ends with", string)
-        } else if (!actual.endsWith(string)) {
-            failWithActual("expected to end with", string)
-        }
-    }
+    open fun endsWith(string: String)
 
     /** Fails if the string does not match the given [regex]. */
-    open fun matches(regex: String) {
-        matchesImpl(regex.toRegex()) {
-            "Looks like you want to use .isEqualTo() for an exact equality assertion."
-        }
-    }
+    open fun matches(regex: String)
 
     /** Fails if the string does not match the given [regex]. */
-    fun matches(regex: Regex) {
-        matchesImpl(regex) {
-            "If you want an exact equality assertion you can escape your regex with Regex.escape()."
-        }
-    }
+    fun matches(regex: Regex)
 
     /** Fails if the string matches the given regex. */
-    open fun doesNotMatch(regex: String) {
-        doesNotMatchImpl(regex.toRegex())
-    }
+    open fun doesNotMatch(regex: String)
 
     /** Fails if the string matches the given regex. */
-    fun doesNotMatch(regex: Regex) {
-        doesNotMatchImpl(regex)
-    }
+    fun doesNotMatch(regex: Regex)
 
     /** Fails if the string does not contain a match on the given regex. */
-    open fun containsMatch(regex: String) {
-        containsMatchImpl(regex.toRegex())
-    }
+    open fun containsMatch(regex: String)
 
     /** Fails if the string does not contain a match on the given regex. */
-    fun containsMatch(regex: Regex) {
-        containsMatchImpl(regex)
-    }
+    fun containsMatch(regex: Regex)
 
     /** Fails if the string contains a match on the given regex. */
-    open fun doesNotContainMatch(regex: String) {
-        if (actual == null) {
-            failWithActual("expected a string that does not contain a match for", regex)
-        } else if (regex.toRegex().containsMatchIn(actual)) {
-            failWithActual("expected not to contain a match for", regex)
-        }
-    }
+    open fun doesNotContainMatch(regex: String)
 
     /** Fails if the string contains a match on the given regex. */
-    fun doesNotContainMatch(regex: Regex) {
-        doesNotContainMatchImpl(regex)
-    }
+    fun doesNotContainMatch(regex: Regex)
 
     /**
      * Returns a [StringSubject]-like instance that will ignore the case of the characters.
@@ -150,7 +82,7 @@
      * calling [Char.lowercaseChar] or after calling [Char.uppercaseChar]. Note that this is
      * independent of any locale.
      */
-    open fun ignoringCase(): CaseInsensitiveStringComparison = CaseInsensitiveStringComparison()
+    open fun ignoringCase(): CaseInsensitiveStringComparison
 
     inner class CaseInsensitiveStringComparison internal constructor() {
         /**
@@ -161,82 +93,205 @@
          *
          * Example: "abc" is equal to "ABC", but not to "abcd".
          */
-        fun isEqualTo(expected: String?) {
-            if ((actual == null) && (expected != null)) {
-                failWithoutActual(
-                    fact("expected a string that is equal to", expected),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            } else if ((expected == null) && (actual != null)) {
-                failWithoutActual(
-                    fact("expected", "null (null reference)"),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            } else if (!actual.equals(expected, ignoreCase = true)) {
-                failWithoutActual(
-                    fact("expected", expected),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            }
-        }
+        fun isEqualTo(expected: String?)
 
         /**
          * Fails if the subject is equal to the given string (while ignoring case). The meaning of
          * equality is the same as for the [isEqualTo] method.
          */
-        fun isNotEqualTo(unexpected: String?) {
-            if ((actual == null) && (unexpected == null)) {
-                failWithoutActual(
-                    fact("expected a string that is not equal to", "null (null reference)"),
-                    simpleFact("(case is ignored)")
-                )
-            } else if (actual.equals(unexpected, ignoreCase = true)) {
-                failWithoutActual(
-                    fact("expected not to be", unexpected),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            }
-        }
+        fun isNotEqualTo(unexpected: String?)
 
         /** Fails if the string does not contain the given sequence (while ignoring case). */
-        fun contains(expected: CharSequence?) {
-            requireNonNull(expected)
-
-            if (actual == null) {
-                failWithoutActual(
-                    fact("expected a string that contains", expected),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            } else if (!actual.contains(expected, ignoreCase = true)) {
-                failWithoutActual(
-                    fact("expected to contain", expected),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            }
-        }
+        fun contains(expected: CharSequence?)
 
         /** Fails if the string contains the given sequence (while ignoring case). */
-        fun doesNotContain(expected: CharSequence) {
-            if (actual == null) {
-                failWithoutActual(
-                    fact("expected a string that does not contain", expected),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            } else if (actual.contains(expected, ignoreCase = true)) {
-                failWithoutActual(
-                    fact("expected not to contain", expected),
-                    fact("but was", actual),
-                    simpleFact("(case is ignored)")
-                )
-            }
-        }
+        fun doesNotContain(expected: CharSequence)
+    }
+}
+
+/** Fails if the string does not contain the given sequence. */
+internal fun StringSubject.commonContains(charSequence: CharSequence) {
+    if (actual == null) {
+        failWithActualInternal("expected a string that contains", charSequence)
+    } else if (!actual.contains(charSequence)) {
+        failWithActualInternal("expected to contain", charSequence)
+    }
+}
+
+/** Fails if the string does not have the given length. */
+internal fun StringSubject.commonHasLength(expectedLength: Int) {
+    require(expectedLength >= 0) { "expectedLength($expectedLength) must be >= 0" }
+    checkInternal("length").that(requireNonNull(actual).length).isEqualTo(expectedLength)
+}
+
+/** Fails if the string is not equal to the zero-length "empty string." */
+internal fun StringSubject.commonIsEmpty() {
+    if (actual == null) {
+        failWithActualInternal(simpleFact("expected an empty string"))
+    } else if (actual.isNotEmpty()) {
+        failWithActualInternal(simpleFact("expected to be string"))
+    }
+}
+
+/** Fails if the string is equal to the zero-length "empty string." */
+internal fun StringSubject.commonIsNotEmpty() {
+    if (actual == null) {
+        failWithActualInternal(simpleFact("expected a non-empty string"))
+    } else if (actual.isEmpty()) {
+        failWithoutActualInternal(simpleFact("expected not to be empty"))
+    }
+}
+
+/** Fails if the string contains the given sequence. */
+internal fun StringSubject.commonDoesNotContain(charSequence: CharSequence) {
+    if (actual == null) {
+        failWithActualInternal("expected a string that does not contain", charSequence)
+    } else if (actual.contains(charSequence)) {
+        failWithActualInternal("expected not to contain", charSequence)
+    }
+}
+
+/** Fails if the string does not start with the given string. */
+internal fun StringSubject.commonStartsWith(string: String) {
+    if (actual == null) {
+        failWithActualInternal("expected a string that starts with", string)
+    } else if (!actual.startsWith(string)) {
+        failWithActualInternal("expected to start with", string)
+    }
+}
+
+/** Fails if the string does not end with the given string. */
+internal fun StringSubject.commonEndsWith(string: String) {
+    if (actual == null) {
+        failWithActualInternal("expected a string that ends with", string)
+    } else if (!actual.endsWith(string)) {
+        failWithActualInternal("expected to end with", string)
+    }
+}
+
+/** Fails if the string does not match the given [regex]. */
+internal fun StringSubject.commonMatches(regex: String) {
+    matchesImpl(regex.toRegex()) {
+        "Looks like you want to use .isEqualTo() for an exact equality assertion."
+    }
+}
+
+/** Fails if the string does not match the given [regex]. */
+internal fun StringSubject.commonMatches(regex: Regex) {
+    matchesImpl(regex) {
+        "If you want an exact equality assertion you can escape your regex with Regex.escape()."
+    }
+}
+
+/** Fails if the string matches the given regex. */
+internal fun StringSubject.commonDoesNotMatch(regex: String) {
+    doesNotMatchImpl(regex.toRegex())
+}
+
+/** Fails if the string matches the given regex. */
+internal fun StringSubject.commonDoesNotMatch(regex: Regex) {
+    doesNotMatchImpl(regex)
+}
+
+/** Fails if the string does not contain a match on the given regex. */
+internal fun StringSubject.commonContainsMatch(regex: String) {
+    containsMatchImpl(regex.toRegex())
+}
+
+/** Fails if the string does not contain a match on the given regex. */
+internal fun StringSubject.commonContainsMatch(regex: Regex) {
+    containsMatchImpl(regex)
+}
+
+/** Fails if the string contains a match on the given regex. */
+internal fun StringSubject.commonDoesNotContainMatch(regex: String) {
+    if (actual == null) {
+        failWithActualInternal("expected a string that does not contain a match for", regex)
+    } else if (regex.toRegex().containsMatchIn(actual)) {
+        failWithActualInternal("expected not to contain a match for", regex)
+    }
+}
+
+/** Fails if the string contains a match on the given regex. */
+internal fun StringSubject.commonDoesNotContainMatch(regex: Regex) {
+    doesNotContainMatchImpl(regex)
+}
+
+internal fun StringSubject.commonIgnoringCase(): StringSubject.CaseInsensitiveStringComparison =
+    CaseInsensitiveStringComparison()
+
+internal fun StringSubject.commonCaseInsensitiveStringComparisonIsEqualTo(expected: String?) {
+    if ((actual == null) && (expected != null)) {
+        failWithoutActualInternal(
+            fact("expected a string that is equal to", expected),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
+    } else if ((expected == null) && (actual != null)) {
+        failWithoutActualInternal(
+            fact("expected", "null (null reference)"),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
+    } else if (!actual.equals(expected, ignoreCase = true)) {
+        failWithoutActualInternal(
+            fact("expected", expected),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
+    }
+}
+
+internal fun StringSubject.commonCaseInsensitiveStringComparisonIsNotEqualTo(unexpected: String?) {
+    if ((actual == null) && (unexpected == null)) {
+        failWithoutActualInternal(
+            fact("expected a string that is not equal to", "null (null reference)"),
+            simpleFact("(case is ignored)")
+        )
+    } else if (actual.equals(unexpected, ignoreCase = true)) {
+        failWithoutActualInternal(
+            fact("expected not to be", unexpected),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
+    }
+}
+
+/** Fails if the string does not contain the given sequence (while ignoring case). */
+internal fun StringSubject.commonCaseInsensitiveStringComparisonContains(expected: CharSequence?) {
+    requireNonNull(expected)
+
+    if (actual == null) {
+        failWithoutActualInternal(
+            fact("expected a string that contains", expected),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
+    } else if (!actual.contains(expected, ignoreCase = true)) {
+        failWithoutActualInternal(
+            fact("expected to contain", expected),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
+    }
+}
+
+/** Fails if the string contains the given sequence (while ignoring case). */
+internal fun StringSubject.commonCaseInsensitiveStringComparisonDoesNotContain(
+    expected: CharSequence
+) {
+    if (actual == null) {
+        failWithoutActualInternal(
+            fact("expected a string that does not contain", expected),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
+    } else if (actual.contains(expected, ignoreCase = true)) {
+        failWithoutActualInternal(
+            fact("expected not to contain", expected),
+            fact("but was", actual),
+            simpleFact("(case is ignored)")
+        )
     }
 }
 
@@ -289,10 +344,3 @@
         )
     }
 }
-
-internal expect interface PlatformStringSubject
-
-internal expect class PlatformStringSubjectImpl(
-    actual: String?,
-    metadata: FailureMetadata,
-) : Subject<String>, PlatformStringSubject
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
index 5901926..d58fdbc 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
@@ -357,6 +357,10 @@
             else -> toString()
         }
 
+    @Suppress("NOTHING_TO_INLINE")
+    internal inline fun checkInternal(format: String, vararg args: Any): StandardSubjectBuilder =
+        check(format, *args)
+
     /**
      * Returns a builder for creating a derived subject.
      *
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt
index c507ac6..1be5df0 100644
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/ComparableSubject.jvm.kt
@@ -20,32 +20,63 @@
 import com.google.common.collect.Range
 
 /**
- * Platform-specific propositions for [Comparable] typed subjects.
+ * Propositions for [Comparable] typed subjects.
  *
  * @param T the type of the object being tested by this [ComparableSubject]
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ *   itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-internal actual interface PlatformComparableSubject<T : Comparable<T>> {
+actual open class ComparableSubject<T : Comparable<T>>
+protected actual constructor(metadata: FailureMetadata, actual: T?) :
+    Subject<T>(actual, metadata, typeDescriptionOverride = null) {
+    internal actual constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+
+    /**
+     * Checks that the subject is equivalent to [other] according to [Comparable.compareTo], (i.e.,
+     * checks that `a.comparesTo(b) == 0`).
+     *
+     * **Note:** Do not use this method for checking object equality. Instead, use [isEqualTo].
+     */
+    actual open fun isEquivalentAccordingToCompareTo(other: T?) =
+        commonIsEquivalentAccordingToCompareTo(other)
+
+    /**
+     * Checks that the subject is greater than [other].
+     *
+     * To check that the subject is greater than *or equal to* [other], use [isAtLeast].
+     */
+    actual fun isGreaterThan(other: T?) = commonIsGreaterThan(other)
+
+    /**
+     * Checks that the subject is less than [other].
+     *
+     * @throws NullPointerException if [actual] or [other] is `null`.
+     */
+    actual fun isLessThan(other: T?) = commonIsLessThan(other)
+
+    /**
+     * Checks that the subject is less than or equal to [other].
+     *
+     * @throws NullPointerException if [actual] or [other] is `null`.
+     */
+    actual fun isAtMost(other: T?) = commonIsAtMost(other)
+
+    /**
+     * Checks that the subject is greater than or equal to [other].
+     *
+     * @throws NullPointerException if [actual] or [other] is `null`.
+     */
+    actual fun isAtLeast(other: T?) = commonIsAtLeast(other)
 
     /** Checks that the subject is in [range]. */
-    fun isIn(range: Range<T>)
-
-    /** Checks that the subject is *not* in [range]. */
-    fun isNotIn(range: Range<T>)
-}
-
-internal actual class PlatformComparableSubjectImpl<T : Comparable<T>>
-actual constructor(
-    actual: T?,
-    metadata: FailureMetadata,
-) : Subject<T>(actual, metadata, typeDescriptionOverride = null), PlatformComparableSubject<T> {
-
-    override fun isIn(range: Range<T>) {
+    fun isIn(range: Range<T>) {
         if (requireNonNull(actual) !in range) {
             failWithoutActualInternal(fact("Expected to be in range", range))
         }
     }
 
-    override fun isNotIn(range: Range<T>) {
+    /** Checks that the subject is *not* in [range]. */
+    fun isNotIn(range: Range<T>) {
         if (requireNonNull(actual) in range) {
             failWithoutActualInternal(fact("Expected not to be in range", range))
         }
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
deleted file mode 100644
index 98742fa..0000000
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStandardSubjectBuilder.jvm.kt
+++ /dev/null
@@ -1,62 +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.kruth
-
-import com.google.common.base.Optional
-import com.google.common.collect.Multimap
-import com.google.common.collect.Multiset
-import com.google.common.collect.Table
-import java.math.BigDecimal
-
-internal actual interface PlatformStandardSubjectBuilder {
-
-    fun that(actual: Class<*>): ClassSubject
-
-    fun <T : Any> that(actual: Optional<T>): GuavaOptionalSubject<T>
-
-    fun that(actual: BigDecimal): BigDecimalSubject
-
-    fun <T> that(actual: Multiset<T>): MultisetSubject<T>
-
-    fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V>
-
-    fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V>
-}
-
-internal actual class PlatformStandardSubjectBuilderImpl
-actual constructor(
-    private val metadata: FailureMetadata,
-) : PlatformStandardSubjectBuilder {
-
-    override fun that(actual: Class<*>): ClassSubject =
-        ClassSubject(actual = actual, metadata = metadata)
-
-    override fun <T : Any> that(actual: Optional<T>): GuavaOptionalSubject<T> =
-        GuavaOptionalSubject(actual = actual, metadata = metadata)
-
-    override fun that(actual: BigDecimal): BigDecimalSubject =
-        BigDecimalSubject(actual = actual, metadata = metadata)
-
-    override fun <T> that(actual: Multiset<T>): MultisetSubject<T> =
-        MultisetSubject(actual = actual, metadata = metadata)
-
-    override fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V> =
-        MultimapSubject(actual = actual, metadata = metadata)
-
-    override fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V> =
-        TableSubject(actual = actual, metadata = metadata)
-}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.jvm.kt
deleted file mode 100644
index 8f7f6ed..0000000
--- a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.jvm.kt
+++ /dev/null
@@ -1,60 +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.kruth
-
-import java.util.regex.Pattern
-
-internal actual interface PlatformStringSubject {
-
-    /** Fails if the string does not match the given regex. */
-    fun matches(regex: Pattern)
-
-    /** Fails if the string matches the given regex. */
-    fun doesNotMatch(regex: Pattern)
-
-    /** Fails if the string does not contain a match on the given regex. */
-    fun containsMatch(regex: Pattern)
-
-    /** Fails if the string contains a match on the given regex. */
-    fun doesNotContainMatch(regex: Pattern)
-}
-
-internal actual class PlatformStringSubjectImpl
-actual constructor(
-    actual: String?,
-    metadata: FailureMetadata,
-) : Subject<String>(actual, metadata, typeDescriptionOverride = null), PlatformStringSubject {
-
-    override fun matches(regex: Pattern) {
-        matchesImpl(regex.toRegex()) {
-            "If you want an exact equality assertion you can escape your regex with " +
-                "Pattern.quote()."
-        }
-    }
-
-    override fun doesNotMatch(regex: Pattern) {
-        doesNotMatchImpl(regex.toRegex())
-    }
-
-    override fun containsMatch(regex: Pattern) {
-        containsMatchImpl(regex.toRegex())
-    }
-
-    override fun doesNotContainMatch(regex: Pattern) {
-        doesNotContainMatchImpl(regex.toRegex())
-    }
-}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StandardSubjectBuilder.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StandardSubjectBuilder.jvm.kt
new file mode 100644
index 0000000..9f688ff
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StandardSubjectBuilder.jvm.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2024 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.kruth
+
+import com.google.common.base.Optional
+import com.google.common.collect.Multimap
+import com.google.common.collect.Multiset
+import com.google.common.collect.Table
+import java.math.BigDecimal
+
+/**
+ * In a fluent assertion chain, an object with which you can do any of the following:
+ * - Set an optional message with [withMessage].
+ * - For the types of [Subject] built into Kruth, directly specify the value under test with
+ *   [withMessage].
+ */
+@Suppress("StaticFinalBuilder") // Cannot be final for binary compatibility.
+actual open class StandardSubjectBuilder internal actual constructor(metadata: FailureMetadata) {
+    internal actual val metadata: FailureMetadata = metadata
+        get() {
+            checkStatePreconditions()
+            return field
+        }
+
+    actual companion object {
+        /** Returns a new instance that invokes the given [FailureStrategy] when a check fails. */
+        @JvmStatic
+        actual fun forCustomFailureStrategy(
+            failureStrategy: FailureStrategy
+        ): StandardSubjectBuilder = commonForCustomFailureStrategy(failureStrategy)
+    }
+
+    /**
+     * Returns a new instance that will output the given message before the main failure message. If
+     * this method is called multiple times, the messages will appear in the order that they were
+     * specified.
+     */
+    actual fun withMessage(messageToPrepend: String): StandardSubjectBuilder =
+        commonWithMessage(messageToPrepend)
+
+    actual fun <T> that(actual: T?): Subject<T> = commonThat(actual)
+
+    actual fun that(actual: Char): Subject<Char> = commonThat(actual)
+
+    actual fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T> = commonThat(actual)
+
+    actual fun <T : Throwable> that(actual: T?): ThrowableSubject<T> = commonThat(actual)
+
+    actual fun that(actual: Boolean?): BooleanSubject = commonThat(actual)
+
+    actual fun that(actual: Long): LongSubject = commonThat(actual)
+
+    actual fun <T : Long?> that(actual: T): LongSubject = commonThat(actual)
+
+    actual fun that(actual: Double?): DoubleSubject = commonThat(actual)
+
+    actual fun that(actual: Float?): FloatSubject = commonThat(actual)
+
+    actual fun that(actual: Int): IntegerSubject = commonThat(actual)
+
+    actual fun <T : Int?> that(actual: T): IntegerSubject = commonThat(actual)
+
+    actual fun that(actual: String?): StringSubject = commonThat(actual)
+
+    actual fun <T> that(actual: Iterable<T>?): IterableSubject<T> = commonThat(actual)
+
+    actual fun <T> that(actual: Array<out T>?): ObjectArraySubject<T> = commonThat(actual)
+
+    actual fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject = commonThat(actual)
+
+    actual fun that(actual: ShortArray?): PrimitiveShortArraySubject = commonThat(actual)
+
+    actual fun that(actual: IntArray?): PrimitiveIntArraySubject = commonThat(actual)
+
+    actual fun that(actual: LongArray?): PrimitiveLongArraySubject = commonThat(actual)
+
+    actual fun that(actual: ByteArray?): PrimitiveByteArraySubject = commonThat(actual)
+
+    actual fun that(actual: CharArray?): PrimitiveCharArraySubject = commonThat(actual)
+
+    actual fun that(actual: FloatArray?): PrimitiveFloatArraySubject = commonThat(actual)
+
+    actual fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject = commonThat(actual)
+
+    actual fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V> = commonThat(actual)
+
+    /**
+     * Given a factory for some [Subject] class, returns [SimpleSubjectBuilder] whose
+     * [that][SimpleSubjectBuilder.that] method creates instances of that class. Created subjects
+     * use the previously set failure strategy and any previously set failure message.
+     */
+    actual fun <T, S : Subject<T>> about(
+        subjectFactory: Subject.Factory<S, T>
+    ): SimpleSubjectBuilder<S, T> = commonAbout(subjectFactory)
+
+    /**
+     * Reports a failure.
+     *
+     * To set a message, first call [withMessage] (or, more commonly, use the shortcut
+     * [assertWithMessage].
+     */
+    actual fun fail() = commonFail()
+
+    internal actual open fun checkStatePreconditions() {}
+
+    @Suppress("BuilderSetStyle") // Necessary for compatibility
+    fun that(actual: Class<*>): ClassSubject = ClassSubject(actual = actual, metadata = metadata)
+
+    @Suppress("BuilderSetStyle") // Necessary for compatibility
+    fun <T : Any> that(actual: Optional<T>): GuavaOptionalSubject<T> =
+        GuavaOptionalSubject(actual = actual, metadata = metadata)
+
+    @Suppress("BuilderSetStyle") // Necessary for compatibility
+    fun that(actual: BigDecimal): BigDecimalSubject =
+        BigDecimalSubject(actual = actual, metadata = metadata)
+
+    @Suppress("BuilderSetStyle") // Necessary for compatibility
+    fun <T> that(actual: Multiset<T>): MultisetSubject<T> =
+        MultisetSubject(actual = actual, metadata = metadata)
+
+    @Suppress("BuilderSetStyle") // Necessary for compatibility
+    fun <K, V> that(actual: Multimap<K, V>): MultimapSubject<K, V> =
+        MultimapSubject(actual = actual, metadata = metadata)
+
+    @Suppress("BuilderSetStyle") // Necessary for compatibility
+    fun <R, C, V> that(actual: Table<R, C, V>): TableSubject<R, C, V> =
+        TableSubject(actual = actual, metadata = metadata)
+}
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StringSubject.jvm.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StringSubject.jvm.kt
new file mode 100644
index 0000000..4345487
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/StringSubject.jvm.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2024 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.kruth
+
+import java.util.regex.Pattern
+
+/**
+ * Propositions for [String] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ *   itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
+ */
+actual open class StringSubject
+protected actual constructor(metadata: FailureMetadata, actual: String?) :
+    ComparableSubject<String>(actual, metadata) {
+
+    internal actual constructor(actual: String?, metadata: FailureMetadata) : this(metadata, actual)
+
+    /** Fails if the string does not contain the given sequence. */
+    actual open fun contains(charSequence: CharSequence) = commonContains(charSequence)
+
+    /** Fails if the string does not have the given length. */
+    actual open fun hasLength(expectedLength: Int) = commonHasLength(expectedLength)
+
+    /** Fails if the string is not equal to the zero-length "empty string." */
+    actual open fun isEmpty() = commonIsEmpty()
+
+    /** Fails if the string is equal to the zero-length "empty string." */
+    actual open fun isNotEmpty() = commonIsNotEmpty()
+
+    /** Fails if the string contains the given sequence. */
+    actual open fun doesNotContain(charSequence: CharSequence) = commonDoesNotContain(charSequence)
+
+    /** Fails if the string does not start with the given string. */
+    actual open fun startsWith(string: String) = commonStartsWith(string)
+
+    /** Fails if the string does not end with the given string. */
+    actual open fun endsWith(string: String) = commonEndsWith(string)
+
+    /** Fails if the string does not match the given [regex]. */
+    actual open fun matches(regex: String) = commonMatches(regex)
+
+    /** Fails if the string does not match the given [regex]. */
+    actual fun matches(regex: Regex) = commonMatches(regex)
+
+    /** Fails if the string matches the given regex. */
+    actual open fun doesNotMatch(regex: String) = commonDoesNotMatch(regex)
+
+    /** Fails if the string matches the given regex. */
+    actual fun doesNotMatch(regex: Regex) = commonDoesNotMatch(regex)
+
+    /** Fails if the string does not contain a match on the given regex. */
+    actual open fun containsMatch(regex: String) = commonContainsMatch(regex)
+
+    /** Fails if the string does not contain a match on the given regex. */
+    actual fun containsMatch(regex: Regex) = commonContainsMatch(regex)
+
+    /** Fails if the string contains a match on the given regex. */
+    actual open fun doesNotContainMatch(regex: String) = commonDoesNotContainMatch(regex)
+
+    /** Fails if the string contains a match on the given regex. */
+    actual fun doesNotContainMatch(regex: Regex) = commonDoesNotContainMatch(regex)
+
+    /**
+     * Returns a [StringSubject]-like instance that will ignore the case of the characters.
+     *
+     * Character equality ignoring case is defined as follows: Characters must be equal either after
+     * calling [Char.lowercaseChar] or after calling [Char.uppercaseChar]. Note that this is
+     * independent of any locale.
+     */
+    actual open fun ignoringCase(): CaseInsensitiveStringComparison = commonIgnoringCase()
+
+    /** Fails if the string does not match the given regex. */
+    open fun matches(regex: Pattern) {
+        matchesImpl(regex.toRegex()) {
+            "If you want an exact equality assertion you can escape your regex with " +
+                "Pattern.quote()."
+        }
+    }
+
+    /** Fails if the string matches the given regex. */
+    open fun doesNotMatch(regex: Pattern) {
+        doesNotMatchImpl(regex.toRegex())
+    }
+
+    /** Fails if the string does not contain a match on the given regex. */
+    open fun containsMatch(regex: Pattern) {
+        containsMatchImpl(regex.toRegex())
+    }
+
+    /** Fails if the string contains a match on the given regex. */
+    open fun doesNotContainMatch(regex: Pattern) {
+        doesNotContainMatchImpl(regex.toRegex())
+    }
+
+    actual inner class CaseInsensitiveStringComparison internal actual constructor() {
+        /**
+         * Fails if the subject is not equal to the given sequence (while ignoring case). For the
+         * purposes of this comparison, two strings are equal if any of the following is true:
+         * * they are equal according to [String.equals] with `ignoreCase = true`
+         * * they are both null
+         *
+         * Example: "abc" is equal to "ABC", but not to "abcd".
+         */
+        actual fun isEqualTo(expected: String?) =
+            commonCaseInsensitiveStringComparisonIsEqualTo(expected)
+
+        /**
+         * Fails if the subject is equal to the given string (while ignoring case). The meaning of
+         * equality is the same as for the [isEqualTo] method.
+         */
+        actual fun isNotEqualTo(unexpected: String?) =
+            commonCaseInsensitiveStringComparisonIsNotEqualTo(unexpected)
+
+        /** Fails if the string does not contain the given sequence (while ignoring case). */
+        actual fun contains(expected: CharSequence?) =
+            commonCaseInsensitiveStringComparisonContains(expected)
+
+        /** Fails if the string contains the given sequence (while ignoring case). */
+        actual fun doesNotContain(expected: CharSequence) =
+            commonCaseInsensitiveStringComparisonDoesNotContain(expected)
+    }
+}
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt
index 9093e27..bdea756 100644
--- a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/ComparableSubject.native.kt
@@ -17,14 +17,52 @@
 package androidx.kruth
 
 /**
- * Platform-specific propositions for [Comparable] typed subjects.
+ * Propositions for [Comparable] typed subjects.
  *
  * @param T the type of the object being tested by this [ComparableSubject]
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ *   itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
  */
-internal actual interface PlatformComparableSubject<T : Comparable<T>>
+actual open class ComparableSubject<T : Comparable<T>>
+protected actual constructor(metadata: FailureMetadata, actual: T?) :
+    Subject<T>(actual, metadata, typeDescriptionOverride = null) {
 
-internal actual class PlatformComparableSubjectImpl<T : Comparable<T>>
-actual constructor(
-    actual: T?,
-    metadata: FailureMetadata,
-) : Subject<T>(actual, metadata, typeDescriptionOverride = null), PlatformComparableSubject<T>
+    internal actual constructor(actual: T?, metadata: FailureMetadata) : this(metadata, actual)
+
+    /**
+     * Checks that the subject is equivalent to [other] according to [Comparable.compareTo], (i.e.,
+     * checks that `a.comparesTo(b) == 0`).
+     *
+     * **Note:** Do not use this method for checking object equality. Instead, use [isEqualTo].
+     */
+    actual open fun isEquivalentAccordingToCompareTo(other: T?) =
+        commonIsEquivalentAccordingToCompareTo(other)
+
+    /**
+     * Checks that the subject is greater than [other].
+     *
+     * To check that the subject is greater than *or equal to* [other], use [isAtLeast].
+     */
+    actual fun isGreaterThan(other: T?) = commonIsGreaterThan(other)
+
+    /**
+     * Checks that the subject is less than [other].
+     *
+     * @throws NullPointerException if [actual] or [other] is `null`.
+     */
+    actual fun isLessThan(other: T?) = commonIsLessThan(other)
+
+    /**
+     * Checks that the subject is less than or equal to [other].
+     *
+     * @throws NullPointerException if [actual] or [other] is `null`.
+     */
+    actual fun isAtMost(other: T?) = commonIsAtMost(other)
+
+    /**
+     * Checks that the subject is greater than or equal to [other].
+     *
+     * @throws NullPointerException if [actual] or [other] is `null`.
+     */
+    actual fun isAtLeast(other: T?) = commonIsAtLeast(other)
+}
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt
deleted file mode 100644
index afcd294..0000000
--- a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt
+++ /dev/null
@@ -1,25 +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.kruth
-
-internal actual interface PlatformStringSubject
-
-internal actual class PlatformStringSubjectImpl
-actual constructor(
-    actual: String?,
-    metadata: FailureMetadata,
-) : Subject<String>(actual, metadata, typeDescriptionOverride = null), PlatformStringSubject
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StandardSubjectBuilder.native.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StandardSubjectBuilder.native.kt
new file mode 100644
index 0000000..ed09dad
--- /dev/null
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StandardSubjectBuilder.native.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 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.kruth
+
+/**
+ * In a fluent assertion chain, an object with which you can do any of the following:
+ * - Set an optional message with [withMessage].
+ * - For the types of [Subject] built into Kruth, directly specify the value under test with
+ *   [withMessage].
+ */
+@Suppress("StaticFinalBuilder") // Cannot be final for binary compatibility.
+actual open class StandardSubjectBuilder internal actual constructor(metadata: FailureMetadata) {
+    internal actual val metadata: FailureMetadata = metadata
+        get() {
+            checkStatePreconditions()
+            return field
+        }
+
+    actual companion object {
+        /** Returns a new instance that invokes the given [FailureStrategy] when a check fails. */
+        actual fun forCustomFailureStrategy(
+            failureStrategy: FailureStrategy
+        ): StandardSubjectBuilder = commonForCustomFailureStrategy(failureStrategy)
+    }
+
+    /**
+     * Returns a new instance that will output the given message before the main failure message. If
+     * this method is called multiple times, the messages will appear in the order that they were
+     * specified.
+     */
+    actual fun withMessage(messageToPrepend: String): StandardSubjectBuilder =
+        commonWithMessage(messageToPrepend)
+
+    actual fun <T> that(actual: T?): Subject<T> = commonThat(actual)
+
+    actual fun that(actual: Char): Subject<Char> = commonThat(actual)
+
+    actual fun <T : Comparable<T>> that(actual: T?): ComparableSubject<T> = commonThat(actual)
+
+    actual fun <T : Throwable> that(actual: T?): ThrowableSubject<T> = commonThat(actual)
+
+    actual fun that(actual: Boolean?): BooleanSubject = commonThat(actual)
+
+    actual fun that(actual: Long): LongSubject = commonThat(actual)
+
+    actual fun <T : Long?> that(actual: T): LongSubject = commonThat(actual)
+
+    actual fun that(actual: Double?): DoubleSubject = commonThat(actual)
+
+    actual fun that(actual: Float?): FloatSubject = commonThat(actual)
+
+    actual fun that(actual: Int): IntegerSubject = commonThat(actual)
+
+    actual fun <T : Int?> that(actual: T): IntegerSubject = commonThat(actual)
+
+    actual fun that(actual: String?): StringSubject = commonThat(actual)
+
+    actual fun <T> that(actual: Iterable<T>?): IterableSubject<T> = commonThat(actual)
+
+    actual fun <T> that(actual: Array<out T>?): ObjectArraySubject<T> = commonThat(actual)
+
+    actual fun that(actual: BooleanArray?): PrimitiveBooleanArraySubject = commonThat(actual)
+
+    actual fun that(actual: ShortArray?): PrimitiveShortArraySubject = commonThat(actual)
+
+    actual fun that(actual: IntArray?): PrimitiveIntArraySubject = commonThat(actual)
+
+    actual fun that(actual: LongArray?): PrimitiveLongArraySubject = commonThat(actual)
+
+    actual fun that(actual: ByteArray?): PrimitiveByteArraySubject = commonThat(actual)
+
+    actual fun that(actual: CharArray?): PrimitiveCharArraySubject = commonThat(actual)
+
+    actual fun that(actual: FloatArray?): PrimitiveFloatArraySubject = commonThat(actual)
+
+    actual fun that(actual: DoubleArray?): PrimitiveDoubleArraySubject = commonThat(actual)
+
+    actual fun <K, V> that(actual: Map<K, V>?): MapSubject<K, V> = commonThat(actual)
+
+    /**
+     * Given a factory for some [Subject] class, returns [SimpleSubjectBuilder] whose
+     * [that][SimpleSubjectBuilder.that] method creates instances of that class. Created subjects
+     * use the previously set failure strategy and any previously set failure message.
+     */
+    actual fun <T, S : Subject<T>> about(
+        subjectFactory: Subject.Factory<S, T>
+    ): SimpleSubjectBuilder<S, T> = commonAbout(subjectFactory)
+
+    /**
+     * Reports a failure.
+     *
+     * To set a message, first call [withMessage] (or, more commonly, use the shortcut
+     * [assertWithMessage].
+     */
+    actual fun fail() = commonFail()
+
+    internal actual open fun checkStatePreconditions() {}
+}
diff --git a/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StringSubject.native.kt b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StringSubject.native.kt
new file mode 100644
index 0000000..4759796
--- /dev/null
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/StringSubject.native.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 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.kruth
+
+/**
+ * Propositions for [String] subjects.
+ *
+ * @constructor Constructor for use by subclasses. If you want to create an instance of this class
+ *   itself, call [check(...)][Subject.check].[that(actual)][StandardSubjectBuilder.that].
+ */
+actual open class StringSubject
+protected actual constructor(metadata: FailureMetadata, actual: String?) :
+    ComparableSubject<String>(actual, metadata) {
+
+    internal actual constructor(actual: String?, metadata: FailureMetadata) : this(metadata, actual)
+
+    /** Fails if the string does not contain the given sequence. */
+    actual open fun contains(charSequence: CharSequence) = commonContains(charSequence)
+
+    /** Fails if the string does not have the given length. */
+    actual open fun hasLength(expectedLength: Int) = commonHasLength(expectedLength)
+
+    /** Fails if the string is not equal to the zero-length "empty string." */
+    actual open fun isEmpty() = commonIsEmpty()
+
+    /** Fails if the string is equal to the zero-length "empty string." */
+    actual open fun isNotEmpty() = commonIsNotEmpty()
+
+    /** Fails if the string contains the given sequence. */
+    actual open fun doesNotContain(charSequence: CharSequence) = commonDoesNotContain(charSequence)
+
+    /** Fails if the string does not start with the given string. */
+    actual open fun startsWith(string: String) = commonStartsWith(string)
+
+    /** Fails if the string does not end with the given string. */
+    actual open fun endsWith(string: String) = commonEndsWith(string)
+
+    /** Fails if the string does not match the given [regex]. */
+    actual open fun matches(regex: String) = commonMatches(regex)
+
+    /** Fails if the string does not match the given [regex]. */
+    actual fun matches(regex: Regex) = commonMatches(regex)
+
+    /** Fails if the string matches the given regex. */
+    actual open fun doesNotMatch(regex: String) = commonDoesNotMatch(regex)
+
+    /** Fails if the string matches the given regex. */
+    actual fun doesNotMatch(regex: Regex) = commonDoesNotMatch(regex)
+
+    /** Fails if the string does not contain a match on the given regex. */
+    actual open fun containsMatch(regex: String) = commonContainsMatch(regex)
+
+    /** Fails if the string does not contain a match on the given regex. */
+    actual fun containsMatch(regex: Regex) = commonContainsMatch(regex)
+
+    /** Fails if the string contains a match on the given regex. */
+    actual open fun doesNotContainMatch(regex: String) = commonDoesNotContainMatch(regex)
+
+    /** Fails if the string contains a match on the given regex. */
+    actual fun doesNotContainMatch(regex: Regex) = commonDoesNotContainMatch(regex)
+
+    /**
+     * Returns a [StringSubject]-like instance that will ignore the case of the characters.
+     *
+     * Character equality ignoring case is defined as follows: Characters must be equal either after
+     * calling [Char.lowercaseChar] or after calling [Char.uppercaseChar]. Note that this is
+     * independent of any locale.
+     */
+    actual open fun ignoringCase(): CaseInsensitiveStringComparison = commonIgnoringCase()
+
+    actual inner class CaseInsensitiveStringComparison internal actual constructor() {
+        /**
+         * Fails if the subject is not equal to the given sequence (while ignoring case). For the
+         * purposes of this comparison, two strings are equal if any of the following is true:
+         * * they are equal according to [String.equals] with `ignoreCase = true`
+         * * they are both null
+         *
+         * Example: "abc" is equal to "ABC", but not to "abcd".
+         */
+        actual fun isEqualTo(expected: String?) =
+            commonCaseInsensitiveStringComparisonIsEqualTo(expected)
+
+        /**
+         * Fails if the subject is equal to the given string (while ignoring case). The meaning of
+         * equality is the same as for the [isEqualTo] method.
+         */
+        actual fun isNotEqualTo(unexpected: String?) =
+            commonCaseInsensitiveStringComparisonIsNotEqualTo(unexpected)
+
+        /** Fails if the string does not contain the given sequence (while ignoring case). */
+        actual fun contains(expected: CharSequence?) =
+            commonCaseInsensitiveStringComparisonContains(expected)
+
+        /** Fails if the string contains the given sequence (while ignoring case). */
+        actual fun doesNotContain(expected: CharSequence) =
+            commonCaseInsensitiveStringComparisonDoesNotContain(expected)
+    }
+}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index 793f063..de01d95 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -356,7 +356,12 @@
         val request = NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build()
         val matchingDeepLink =
             if (this is NavGraph) {
-                matchDeepLinkExcludingChildren(request)
+                matchDeepLinkComprehensive(
+                    request,
+                    searchChildren = false,
+                    searchParent = false,
+                    lastVisited = this
+                )
             } else {
                 matchDeepLink(request)
             }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
index 4e19649..3a34696 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -64,24 +64,49 @@
         }
     }
 
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public override fun matchDeepLink(navDeepLinkRequest: NavDeepLinkRequest): DeepLinkMatch? {
-        // First search through any deep links directly added to this NavGraph
-        val bestMatch = super.matchDeepLink(navDeepLinkRequest)
-        // Then search through all child destinations for a matching deep link
-        val bestChildMatch =
-            mapNotNull { child -> child.matchDeepLink(navDeepLinkRequest) }.maxOrNull()
-
-        return listOfNotNull(bestMatch, bestChildMatch).maxOrNull()
-    }
-
     /**
-     * Only searches through deep links added directly to this graph. Does not recursively search
-     * through its children as [matchDeepLink] does.
+     * Matches deeplink with all children and parents recursively.
+     *
+     * Does not revisit graphs (whether it's a child or parent) if it has already been visited.
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public fun matchDeepLinkExcludingChildren(request: NavDeepLinkRequest): DeepLinkMatch? =
-        super.matchDeepLink(request)
+    public fun matchDeepLinkComprehensive(
+        navDeepLinkRequest: NavDeepLinkRequest,
+        searchChildren: Boolean,
+        searchParent: Boolean,
+        lastVisited: NavDestination
+    ): DeepLinkMatch? {
+        // First search through any deep links directly added to this NavGraph
+        val bestMatch = super.matchDeepLink(navDeepLinkRequest)
+
+        // If searchChildren is true, search through all child destinations for a matching deeplink
+        val bestChildMatch =
+            if (searchChildren) {
+                mapNotNull { child ->
+                        if (child != lastVisited) child.matchDeepLink(navDeepLinkRequest) else null
+                    }
+                    .maxOrNull()
+            } else null
+
+        // If searchParent is true, search through all parents (and their children) destinations
+        // for a matching deeplink
+        val bestParentMatch =
+            parent?.let {
+                if (searchParent && it != lastVisited)
+                    it.matchDeepLinkComprehensive(navDeepLinkRequest, searchChildren, true, this)
+                else null
+            }
+        return listOfNotNull(bestMatch, bestChildMatch, bestParentMatch).maxOrNull()
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public override fun matchDeepLink(navDeepLinkRequest: NavDeepLinkRequest): DeepLinkMatch? =
+        matchDeepLinkComprehensive(
+            navDeepLinkRequest,
+            searchChildren = true,
+            searchParent = false,
+            lastVisited = this
+        )
 
     /**
      * Adds a destination to this NavGraph. The destination must have an [NavDestination.id] id}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index b4ae718..325c413 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -787,6 +787,98 @@
 
     @UiThreadTest
     @Test
+    fun testNavigateSharedDestination() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "root", startDestination = "home_nav") {
+                navigation(route = "home_nav", startDestination = "home") {
+                    test("home")
+                    test("shared")
+                }
+                navigation(route = "list_nav", startDestination = "list") {
+                    test("list")
+                    test("shared")
+                }
+            }
+
+        assertThat(navController.currentDestination?.route).isEqualTo("home")
+
+        navController.navigate("shared")
+        val currentDest = navController.currentDestination!!
+        assertThat(currentDest.route).isEqualTo("shared")
+        assertThat(currentDest.parent!!.route).isEqualTo("home_nav")
+        assertThat(navController.currentBackStack.value.map { it.destination.route })
+            .containsExactly("root", "home_nav", "home", "shared")
+            .inOrder()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateNestedSharedDestination() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "root", startDestination = "home_nav") {
+                navigation(route = "home_nav", startDestination = "home") {
+                    test("home")
+                    navigation(route = "home_nested_nav", startDestination = "home_nested") {
+                        test("home_nested")
+                        test("shared")
+                    }
+                }
+                navigation(route = "list_nav", startDestination = "list") {
+                    test("list")
+                    test("shared")
+                }
+            }
+
+        assertThat(navController.currentDestination?.route).isEqualTo("home")
+
+        navController.navigate("shared")
+        val currentDest = navController.currentDestination!!
+        assertThat(currentDest.route).isEqualTo("shared")
+        assertThat(currentDest.parent!!.route).isEqualTo("home_nested_nav")
+        assertThat(navController.currentBackStack.value.map { it.destination.route })
+            .containsExactly("root", "home_nav", "home", "home_nested_nav", "shared")
+            .inOrder()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateParentSharedDestination() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "root", startDestination = "home_nav") {
+                navigation(route = "home_nav", startDestination = "home") {
+                    test("home")
+                    test("shared")
+                    navigation(route = "home_nested_nav", startDestination = "home_nested") {
+                        test("home_nested")
+                    }
+                }
+                navigation(route = "list_nav", startDestination = "list") {
+                    test("list")
+                    test("shared")
+                }
+            }
+
+        assertThat(navController.currentDestination?.route).isEqualTo("home")
+
+        // first navigate into nested graph
+        navController.navigate("home_nested")
+        assertThat(navController.currentDestination!!.route).isEqualTo("home_nested")
+
+        // then navigate to the shared destination that is sibling of parent
+        navController.navigate("shared")
+        val currentDest = navController.currentDestination!!
+        assertThat(currentDest.route).isEqualTo("shared")
+        assertThat(currentDest.parent!!.route).isEqualTo("home_nav")
+        assertThat(navController.currentBackStack.value.map { it.destination.route })
+            .containsExactly("root", "home_nav", "home", "home_nested_nav", "home_nested", "shared")
+            .inOrder()
+    }
+
+    @UiThreadTest
+    @Test
     fun testNavigateSingleTopSharedStartDestination() {
         val navController = createNavController()
         navController.graph =
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index 1c2703d..9e36bf8 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -1014,8 +1014,14 @@
                         // Include the original deep link Intent so the Destinations can
                         // synthetically generate additional arguments as necessary.
                         args.putParcelable(KEY_DEEP_LINK_INTENT, activity!!.intent)
+                        val currGraph = backQueue.getTopGraph()
                         val matchingDeepLink =
-                            _graph!!.matchDeepLink(NavDeepLinkRequest(activity!!.intent))
+                            currGraph.matchDeepLinkComprehensive(
+                                navDeepLinkRequest = NavDeepLinkRequest(activity!!.intent),
+                                searchChildren = true,
+                                searchParent = true,
+                                lastVisited = currGraph
+                            )
                         if (matchingDeepLink?.matchingArgs != null) {
                             val destinationArgs =
                                 matchingDeepLink.destination.addInDefaultArgs(
@@ -1403,7 +1409,14 @@
             globalArgs.putAll(deepLinkExtras)
         }
         if (deepLink == null || deepLink.isEmpty()) {
-            val matchingDeepLink = _graph!!.matchDeepLink(NavDeepLinkRequest(intent))
+            val currGraph = backQueue.getTopGraph()
+            val matchingDeepLink =
+                currGraph.matchDeepLinkComprehensive(
+                    navDeepLinkRequest = NavDeepLinkRequest(intent),
+                    searchChildren = true,
+                    searchParent = true,
+                    lastVisited = currGraph
+                )
             if (matchingDeepLink != null) {
                 val destination = matchingDeepLink.destination
                 deepLink = destination.buildDeepLinkIds()
@@ -1621,9 +1634,17 @@
         if (_graph!!.route == route || _graph!!.matchDeepLink(route) != null) {
             return _graph
         }
-        val currentNode = backQueue.lastOrNull()?.destination ?: _graph!!
-        val currentGraph = if (currentNode is NavGraph) currentNode else currentNode.parent!!
-        return currentGraph.findNode(route)
+        return backQueue.getTopGraph().findNode(route)
+    }
+
+    /**
+     * Returns the last NavGraph on the backstack.
+     *
+     * If there are no NavGraphs on the stack, returns [_graph]
+     */
+    private fun ArrayDeque<NavBackStackEntry>.getTopGraph(): NavGraph {
+        val currentNode = lastOrNull()?.destination ?: _graph!!
+        return if (currentNode is NavGraph) currentNode else currentNode.parent!!
     }
 
     // Finds destination within _graph including its children and
@@ -1886,7 +1907,14 @@
             "Cannot navigate to $request. Navigation graph has not been set for " +
                 "NavController $this."
         }
-        val deepLinkMatch = _graph!!.matchDeepLink(request)
+        val currGraph = backQueue.getTopGraph()
+        val deepLinkMatch =
+            currGraph.matchDeepLinkComprehensive(
+                navDeepLinkRequest = request,
+                searchChildren = true,
+                searchParent = true,
+                lastVisited = currGraph
+            )
         if (deepLinkMatch != null) {
             val destination = deepLinkMatch.destination
             val args = destination.addInDefaultArgs(deepLinkMatch.matchingArgs) ?: Bundle()
diff --git a/paging/integration-tests/testapp/build.gradle b/paging/integration-tests/testapp/build.gradle
index cbd8d34..a1918e9 100644
--- a/paging/integration-tests/testapp/build.gradle
+++ b/paging/integration-tests/testapp/build.gradle
@@ -41,12 +41,10 @@
 
     // Only needed to ensure version of annotation:annotation matches in impl
     // and androidTestImpl, for both AOSP and playground builds.
-    implementation(project(":annotation:annotation"))
-    implementation(project(":annotation:annotation-experimental"))
+    implementation("androidx.annotation:annotation:1.8.1")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation(project(":annotation:annotation"))
-    androidTestImplementation(project(":annotation:annotation-experimental"))
     androidTestImplementation(libs.kotlinTest)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testExtJunit)
diff --git a/pdf/integration-tests/testapp/build.gradle b/pdf/integration-tests/testapp/build.gradle
index d8990dc..24a850b 100644
--- a/pdf/integration-tests/testapp/build.gradle
+++ b/pdf/integration-tests/testapp/build.gradle
@@ -6,7 +6,6 @@
 
 android {
     namespace 'androidx.pdf.testapp'
-    buildToolsVersion "35.0.0-rc1"
 
     defaultConfig {
         applicationId "androidx.pdf.testapp"
@@ -27,7 +26,6 @@
     implementation(libs.testCore)
 
     androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.testRunner)
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/testapp/MainActivityTest.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/testapp/MainActivityTest.kt
new file mode 100644
index 0000000..f59bcb5
--- /dev/null
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/testapp/MainActivityTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 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.pdf.testapp
+
+import androidx.activity.result.ActivityResultLauncher
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario.launch
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.android.material.button.MaterialButton
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.*
+
+@Suppress("UNCHECKED_CAST")
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MainActivityTest {
+    @get:Rule var activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
+
+    @Test
+    fun testActivityInitialization() {
+        launch(MainActivity::class.java).use { scenario ->
+            scenario.moveToState(Lifecycle.State.CREATED)
+
+            Assert.assertEquals(Lifecycle.State.CREATED, scenario.state)
+        }
+    }
+
+    @Test
+    fun testGetContentButtonClickLaunchesFilePicker() {
+        val mockFilePicker: ActivityResultLauncher<String> =
+            mock(ActivityResultLauncher::class.java) as ActivityResultLauncher<String>
+
+        launch(MainActivity::class.java).use { scenario ->
+            scenario.moveToState(Lifecycle.State.CREATED)
+            scenario.onActivity { activity ->
+                activity.filePicker = mockFilePicker
+                val getContentButton: MaterialButton = activity.findViewById(R.id.launch_button)
+
+                Assert.assertNotNull(getContentButton)
+                getContentButton.performClick()
+                verify(mockFilePicker).launch("application/pdf")
+            }
+        }
+    }
+}
diff --git a/pdf/integration-tests/testapp/src/main/AndroidManifest.xml b/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
index 2c55785..68c94d8 100644
--- a/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/pdf/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -24,7 +24,7 @@
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
-        android:theme="@style/Theme.Androidx"
+        android:theme="@style/AppTheme"
         tools:replace="android:label">
         <activity
             android:name=".MainActivity"
diff --git a/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt b/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt
index ef1d51b..d4d0f6c 100644
--- a/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt
+++ b/pdf/integration-tests/testapp/src/main/kotlin/androidx/pdf/testapp/MainActivity.kt
@@ -19,8 +19,10 @@
 import android.annotation.SuppressLint
 import android.net.Uri
 import android.os.Bundle
+import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.GetContent
 import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
 import androidx.appcompat.app.AppCompatActivity
 import androidx.fragment.app.FragmentManager
 import androidx.fragment.app.FragmentTransaction
@@ -32,9 +34,15 @@
 class MainActivity : AppCompatActivity() {
 
     private var pdfViewerFragment: PdfViewerFragment? = null
+    private var isPdfViewInitialized = false
 
-    private val filePicker =
+    @VisibleForTesting
+    var filePicker: ActivityResultLauncher<String> =
         registerForActivityResult(GetContent()) { uri: Uri? ->
+            if (!isPdfViewInitialized) {
+                setPdfView()
+                isPdfViewInitialized = true
+            }
             uri?.let { pdfViewerFragment?.documentUri = uri }
         }
 
@@ -53,9 +61,6 @@
 
         getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
         searchButton.setOnClickListener { setFindInFileViewVisible() }
-        if (savedInstanceState == null) {
-            setPdfView()
-        }
     }
 
     private fun setPdfView() {
diff --git a/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml b/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 76f5b6cf..016b59a 100644
--- a/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/pdf/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -15,8 +15,10 @@
   limitations under the License.
   -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/pdf_container_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_gravity="center"
@@ -25,30 +27,42 @@
 
     <FrameLayout
         android:id="@+id/fragment_container_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_weight="1" />
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginBottom="8dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/launch_button"/>
 
-    <LinearLayout
-        android:layout_width="match_parent"
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/launch_button"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:orientation="horizontal">
+        android:text="@string/launch_string"
+        android:textColor="@color/google_white"
+        app:backgroundTint="@color/google_grey"
+        app:strokeColor="@color/google_white"
+        app:strokeWidth="1dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginBottom="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/search_button"/>
 
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/launch_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:text="@string/launch_string"
-            android:textSize="16sp" />
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/search_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/search_string"
+        android:textColor="@color/google_white"
+        app:backgroundTint="@color/google_grey"
+        app:strokeColor="@color/google_white"
+        app:strokeWidth="1dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginBottom="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@id/launch_button"
+        app:layout_constraintEnd_toEndOf="parent" />
 
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/search_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:text="@string/search_string"
-            android:textSize="16sp" />
-    </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml b/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml
index 5b89e97..f1e321a 100644
--- a/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml
+++ b/pdf/integration-tests/testapp/src/main/res/values-night/strings.xml
@@ -15,5 +15,5 @@
   -->
 
 <resources>
-    <string name="launch_string">Launch AOSP PDF Viewer</string>
+    <string name="launch_string">Open Pdf</string>
 </resources>
\ No newline at end of file
diff --git a/pdf/integration-tests/testapp/src/main/res/values/strings.xml b/pdf/integration-tests/testapp/src/main/res/values/strings.xml
index ee8e986..60a4880 100644
--- a/pdf/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/pdf/integration-tests/testapp/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
 <resources>
-    <string name="app_name">PDF Viewer Integration Tests</string>
-    <string name="launch_string">Launch AOSP PDF Viewer</string>
+    <string name="app_name">PDF Viewer Sample App</string>
+    <string name="launch_string">Open Pdf</string>
     <string name="search_string">Search</string>
 </resources>
\ No newline at end of file
diff --git a/pdf/integration-tests/testapp/src/main/res/values/themes.xml b/pdf/integration-tests/testapp/src/main/res/values/themes.xml
index ffe1464..b12dd9b 100644
--- a/pdf/integration-tests/testapp/src/main/res/values/themes.xml
+++ b/pdf/integration-tests/testapp/src/main/res/values/themes.xml
@@ -1,7 +1,10 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Base application theme. -->
-    <style name="Base.Theme.Androidx" parent="Theme.Material3.DayNight.NoActionBar">
+    <style name="BaseAppTheme" parent="Theme.Material3.Dark.NoActionBar">
+        <item name="colorPrimary">@color/google_grey</item>
+        <item name="colorPrimaryDark">@color/black</item>
+        <item name="colorAccent">@color/google_white</item>
     </style>
 
-    <style name="Theme.Androidx" parent="Base.Theme.Androidx" />
+    <style name="AppTheme" parent="BaseAppTheme" />
 </resources>
\ No newline at end of file
diff --git a/pdf/pdf-viewer-fragment/build.gradle b/pdf/pdf-viewer-fragment/build.gradle
index 62e1c47..adebc12 100644
--- a/pdf/pdf-viewer-fragment/build.gradle
+++ b/pdf/pdf-viewer-fragment/build.gradle
@@ -40,7 +40,6 @@
 
     defaultConfig {
         minSdk 31
-        buildToolsVersion "35.0.0-rc1"
         compileSdk 35
     }
 }
diff --git a/pdf/pdf-viewer/build.gradle b/pdf/pdf-viewer/build.gradle
index 45aca39..faa169f 100644
--- a/pdf/pdf-viewer/build.gradle
+++ b/pdf/pdf-viewer/build.gradle
@@ -24,7 +24,6 @@
 }
 
 dependencies {
-    api(libs.rxjava2)
     api(libs.guavaAndroid)
     api(libs.kotlinCoroutinesCore)
     api("androidx.fragment:fragment-ktx:1.8.1")
@@ -64,7 +63,6 @@
 
     defaultConfig {
         minSdk 31
-        buildToolsVersion "35.0.0-rc1"
         compileSdk 35
     }
 
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
index 77ca69e..405014c 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PdfSelectionHandles.java
@@ -84,6 +84,7 @@
 
     @Override
     protected void onDragHandleMove(int deltaX, int deltaY) {
+        mSelectionActionMode.stopActionMode();
         SelectionBoundary updated = SelectionBoundary.atPoint(mDragging.getX() + deltaX,
                 mDragging.getY() + deltaY);
         mSelectionModel.updateSelectionAsync(mFixed, updated);
@@ -92,6 +93,5 @@
     @Override
     protected void onDragHandleUp() {
         mSelectionActionMode.resume();
-        // Nothing required.
     }
 }
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
index 977f26e..101939d 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
@@ -20,6 +20,7 @@
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.testing.CompilationResultSubject
 import androidx.privacysandbox.tools.testing.CompilationTestHelper.assertThat
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
 import androidx.room.compiler.processing.util.compiler.compile
@@ -40,6 +41,7 @@
                 TestCompilationArguments(
                     sources = sources.toList(),
                     symbolProcessorProviders = listOf(provider),
+                    kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
                 )
             )
         )
@@ -55,8 +57,9 @@
             Files.createTempDirectory("test").toFile(),
             TestCompilationArguments(
                 sources = sources.asList(),
-                symbolProcessorProviders = listOf(provider)
-            )
+                symbolProcessorProviders = listOf(provider),
+                kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+            ),
         )
     return assertThat(result).also { it.fails() }
 }
diff --git a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
index 51bcaed..7c68d5b 100644
--- a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
+++ b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/CompilationTestHelper.kt
@@ -18,6 +18,7 @@
 
 import androidx.room.compiler.processing.util.DiagnosticLocation
 import androidx.room.compiler.processing.util.DiagnosticMessage
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
 import androidx.room.compiler.processing.util.compiler.TestCompilationResult
@@ -50,6 +51,7 @@
                 classpath = extraClasspath,
                 symbolProcessorProviders = symbolProcessorProviders,
                 processorOptions = processorOptions,
+                kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
             )
         )
     }
diff --git a/remotecallback/remotecallback/build.gradle b/remotecallback/remotecallback/build.gradle
index e5ff2a7..c1da69a 100644
--- a/remotecallback/remotecallback/build.gradle
+++ b/remotecallback/remotecallback/build.gradle
@@ -30,14 +30,14 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.8.1")
-    implementation(project(":collection:collection"))
+    implementation("androidx.collection:collection:1.4.2")
 
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.mockitoCore, excludes.bytebuddy)
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
-    androidTestAnnotationProcessor (project(":remotecallback:remotecallback-processor"))
+    androidTestAnnotationProcessor(project(":remotecallback:remotecallback-processor"))
 }
 
 android {
diff --git a/resourceinspection/resourceinspection-processor/build.gradle b/resourceinspection/resourceinspection-processor/build.gradle
index 720cf10e..5251358 100644
--- a/resourceinspection/resourceinspection-processor/build.gradle
+++ b/resourceinspection/resourceinspection-processor/build.gradle
@@ -46,7 +46,7 @@
     testImplementation(libs.truth)
 
     testRuntimeOnly(project(":resourceinspection:resourceinspection-annotation"))
-    testRuntimeOnly(project(":annotation:annotation"))
+    testRuntimeOnly("androidx.annotation:annotation:1.8.1")
     testRuntimeOnly(SdkHelperKt.getSdkDependency(project))
     testRuntimeOnly(libs.intellijAnnotations)
     testRuntimeOnly(libs.jsr250)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
index b576bad..cf236fe 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
@@ -35,7 +35,6 @@
 import kotlinx.coroutines.test.TestScope
 import org.junit.After
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -242,8 +241,9 @@
 
     private fun assertTransactionQueries() {
         testCoroutineScope.testScheduler.advanceUntilIdle()
-        assertNotNull(queryAndArgs.any { it.equals("BEGIN TRANSACTION") })
-        assertNotNull(queryAndArgs.any { it.equals("TRANSACTION SUCCESSFUL") })
-        assertNotNull(queryAndArgs.any { it.equals("END TRANSACTION") })
+        val queries = queryAndArgs.map { it.first }
+        assertThat(queries).contains("BEGIN IMMEDIATE TRANSACTION")
+        assertThat(queries).contains("TRANSACTION SUCCESSFUL")
+        assertThat(queries).contains("END TRANSACTION")
     }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/JvmOnlyDatabaseDeclarationTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/JvmOnlyDatabaseDeclarationTest.kt
new file mode 100644
index 0000000..d716001
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/JvmOnlyDatabaseDeclarationTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 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.multiplatformtestapp.test
+
+import androidx.kruth.assertThat
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.useReaderConnection
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+
+/**
+ * This test validates that a [Database] declared in the Jvm source set does not required the usage
+ * of [androidx.room.ConstructedBy] nor [androidx.room.RoomDatabaseConstructor].
+ */
+class JvmOnlyDatabaseDeclarationTest {
+
+    @Test
+    fun buildJvmOnlyRoomDatabase() = runTest {
+        val database =
+            Room.inMemoryDatabaseBuilder<TestDatabase>().setDriver(BundledSQLiteDriver()).build()
+        val dbVersion =
+            database.useReaderConnection { connection ->
+                connection.usePrepared("PRAGMA user_version") {
+                    it.step()
+                    it.getLong(0)
+                }
+            }
+        assertThat(dbVersion).isEqualTo(1)
+    }
+
+    @Database(entities = [TestEntity::class], version = 1, exportSchema = false)
+    abstract class TestDatabase : RoomDatabase()
+
+    @Entity data class TestEntity(@PrimaryKey val id: Long)
+}
diff --git a/room/room-compiler-processing-testing/build.gradle b/room/room-compiler-processing-testing/build.gradle
index f3b5539..77d66f7 100644
--- a/room/room-compiler-processing-testing/build.gradle
+++ b/room/room-compiler-processing-testing/build.gradle
@@ -30,18 +30,26 @@
     id("kotlin")
 }
 
+ext {
+    kotlin = "2.0.20-Beta2"
+    ksp = "${kotlin}-1.0.23"
+}
+
 dependencies {
     api(project(":room:room-compiler-processing"))
-    implementation(libs.kotlinStdlib)
-    implementation(libs.kspApi)
-    implementation(libs.kotlinStdlibJdk8) // KSP defines older version as dependency, force update.
-    implementation(libs.ksp)
+    implementation(libs.kotlinStdlibJdk8)
+    // For Java source compilation
     implementation(libs.googleCompileTesting)
-    // specify these to match the kotlin compiler version in AndroidX rather than what KSP or KCT
-    // uses
-    implementation(libs.kotlinCompilerEmbeddable)
-    implementation(libs.kotlinDaemonEmbeddable)
-    implementation(libs.kotlinAnnotationProcessingEmbeddable)
+    // TODO(b/314151707): Use the versions in Androidx once Androidx moves to 2.x.
+    // For KSP processing
+    implementation("com.google.devtools.ksp:symbol-processing:${ksp}")
+    implementation("com.google.devtools.ksp:symbol-processing-api:${ksp}")
+    implementation("com.google.devtools.ksp:symbol-processing-common-deps:${ksp}")
+    implementation("com.google.devtools.ksp:symbol-processing-aa-embeddable:${ksp}")
+    // For Kotlin source compilation and KAPT
+    implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlin}")
+    implementation("org.jetbrains.kotlin:kotlin-daemon-embeddable:${kotlin}")
+    implementation("org.jetbrains.kotlin:kotlin-annotation-processing-embeddable:${kotlin}")
 }
 
 /**
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
index 8a862b0..afbc127 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -68,7 +68,7 @@
     override fun toString(): String {
         return buildString {
             appendLine("CompilationResult (with $testRunnerName)")
-            Diagnostic.Kind.entries.forEach { kind ->
+            Diagnostic.Kind.values().forEach { kind ->
                 val messages = diagnosticsOfKind(kind)
                 appendLine("${kind.name}: ${messages.size}")
                 messages.forEach { appendLine(it) }
@@ -98,7 +98,10 @@
                 "Scripting plugin will not be loaded: not",
                 "Using JVM IR backend",
                 "Configuring the compilation environment",
-                "Loading modules:"
+                "Loading modules:",
+                // TODO: Remove once we use a Kotlin 2.x version that has
+                // https://github.com/JetBrains/kotlin/commit/7e9d6e601d007bc1250e1b47c6b2e55e3399145b
+                "Kapt currently doesn't support language version"
             )
     }
 }
@@ -474,7 +477,7 @@
 @ExperimentalProcessingApi
 internal class JavaCompileTestingCompilationResult(
     testRunner: CompilationTestRunner,
-    private val delegate: Compilation,
+    @Suppress("unused") private val delegate: Compilation,
     processor: SyntheticJavacProcessor,
     diagnostics: Map<Diagnostic.Kind, List<DiagnosticMessage>>,
     override val generatedSources: List<Source>,
@@ -494,7 +497,8 @@
 }
 
 @ExperimentalProcessingApi
-internal class KotlinCompilationResult(
+internal class KotlinCompilationResult
+constructor(
     testRunner: CompilationTestRunner,
     processor: SyntheticProcessor,
     private val delegate: TestCompilationResult
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
index d0065be1..b932e31 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
@@ -549,3 +549,6 @@
         tmpDir.deleteRecursively()
     }
 }
+
+/** Kotlin compiler arguments for K1 */
+val KOTLINC_LANGUAGE_1_9_ARGS = listOf("-language-version=1.9", "-api-version=1.9")
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
index bea0abc..e62003f 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
@@ -16,98 +16,151 @@
 
 package androidx.room.compiler.processing.util.compiler
 
-import androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar.Companion.runCompilation
+import androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar.runCompilation
 import java.net.URI
-import java.nio.file.Paths
+import kotlin.io.path.absolute
+import kotlin.io.path.toPath
 import org.jetbrains.kotlin.cli.common.ExitCode
 import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
 import org.jetbrains.kotlin.com.intellij.mock.MockProject
+import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
 import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.jetbrains.kotlin.config.Services
 import org.jetbrains.kotlin.util.ServiceLoaderLite
 
 /**
- * A component registrar for Kotlin Compiler that delegates to a list of thread local delegates.
+ * A utility object for setting up Kotlin Compiler plugins that delegate to a list of thread local
+ * plugins.
  *
  * see [runCompilation] for usages.
  */
-@Suppress("DEPRECATION") // TODO: Migrate ComponentRegistrar to CompilerPluginRegistrar
 @OptIn(ExperimentalCompilerApi::class)
-internal class DelegatingTestRegistrar :
-    @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
-    override fun registerProjectComponents(
-        project: MockProject,
-        configuration: CompilerConfiguration
-    ) {
-        delegates.get()?.let { it.forEach { it.registerProjectComponents(project, configuration) } }
+object DelegatingTestRegistrar {
+
+    @Suppress("DEPRECATION")
+    private val k1Delegates =
+        ThreadLocal<List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>>()
+
+    private val k2Delegates = ThreadLocal<List<CompilerPluginRegistrar>>()
+
+    class K1Registrar :
+        @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
+        override fun registerProjectComponents(
+            project: MockProject,
+            configuration: CompilerConfiguration
+        ) {
+            k1Delegates.get()?.forEach { it.registerProjectComponents(project, configuration) }
+        }
+
+        // FirKotlinToJvmBytecodeCompiler throws an error when it sees an incompatible plugin.
+        override val supportsK2: Boolean
+            get() = true
     }
 
-    companion object {
-        private const val REGISTRAR_CLASSPATH =
-            "META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar"
+    class K2Registrar : CompilerPluginRegistrar() {
+        override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
+            k2Delegates.get()?.forEach { with(it) { registerExtensions(configuration) } }
+        }
 
-        private val resourcePathForSelfClassLoader by lazy {
+        override val supportsK2: Boolean
+            get() = true
+    }
+
+    private const val K1_SERVICES_REGISTRAR_PATH =
+        "META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar"
+
+    private const val K2_SERVICES_REGISTRAR_PATH =
+        "META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar"
+
+    private val k1ResourcePathForSelfClassLoader by lazy {
+        getResourcePathForClassLoader(K1_SERVICES_REGISTRAR_PATH)
+    }
+
+    private val k2ResourcePathForSelfClassLoader by lazy {
+        getResourcePathForClassLoader(K2_SERVICES_REGISTRAR_PATH)
+    }
+
+    private fun getResourcePathForClassLoader(servicesRegistrarPath: String): String {
+        val registrarClassToLoad =
+            when (servicesRegistrarPath) {
+                K1_SERVICES_REGISTRAR_PATH ->
+                    @Suppress("DEPRECATION")
+                    org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar::class
+                K2_SERVICES_REGISTRAR_PATH -> CompilerPluginRegistrar::class
+                else -> error("Unknown services registrar path: $servicesRegistrarPath")
+            }
+        val expectedRegistrarClass =
+            when (servicesRegistrarPath) {
+                K1_SERVICES_REGISTRAR_PATH -> K1Registrar::class
+                K2_SERVICES_REGISTRAR_PATH -> K2Registrar::class
+                else -> error("Unknown services registrar path: $servicesRegistrarPath")
+            }
+        val classpath =
             this::class
                 .java
                 .classLoader
-                .getResources(REGISTRAR_CLASSPATH)
+                .getResources(servicesRegistrarPath)
                 .asSequence()
                 .mapNotNull { url ->
-                    val uri = URI.create(url.toString().removeSuffix("/$REGISTRAR_CLASSPATH"))
+                    val uri = URI.create(url.toString().removeSuffix("/$servicesRegistrarPath"))
                     when (uri.scheme) {
-                        "jar" -> Paths.get(URI.create(uri.schemeSpecificPart.removeSuffix("!")))
-                        "file" -> Paths.get(uri)
+                        "jar" -> URI.create(uri.schemeSpecificPart.removeSuffix("!")).toPath()
+                        "file" -> uri.toPath()
                         else -> return@mapNotNull null
-                    }.toAbsolutePath()
+                    }.absolute()
                 }
                 .find { resourcesPath ->
                     ServiceLoaderLite.findImplementations(
-                            @Suppress("DEPRECATION")
-                            org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar::class.java,
+                            registrarClassToLoad.java,
                             listOf(resourcesPath.toFile())
                         )
                         .any { implementation ->
-                            implementation == DelegatingTestRegistrar::class.java.name
+                            implementation == expectedRegistrarClass.java.name
                         }
                 }
-                ?.toString()
-                ?: throw AssertionError(
-                    """
-                    Could not find the ComponentRegistrar class loader that should load
-                    ${DelegatingTestRegistrar::class.qualifiedName}
-                    """
-                        .trimIndent()
-                )
+        if (classpath == null) {
+            throw AssertionError(
+                """
+                Could not find the $registrarClassToLoad class loader that should load
+                $expectedRegistrarClass
+                """
+                    .trimIndent()
+            )
         }
-        @Suppress("DEPRECATION")
-        private val delegates =
-            ThreadLocal<List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>>()
+        return classpath.toString()
+    }
 
-        fun runCompilation(
-            compiler: K2JVMCompiler,
-            messageCollector: MessageCollector,
-            arguments: K2JVMCompilerArguments,
-            @Suppress("DEPRECATION")
-            pluginRegistrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>
-        ): ExitCode {
-            try {
-                arguments.addDelegatingTestRegistrar()
-                delegates.set(pluginRegistrars)
-                return compiler.exec(
-                    messageCollector = messageCollector,
-                    services = Services.EMPTY,
-                    arguments = arguments
-                )
-            } finally {
-                delegates.remove()
-            }
+    internal fun runCompilation(
+        compiler: K2JVMCompiler,
+        messageCollector: MessageCollector,
+        arguments: K2JVMCompilerArguments,
+        registrars: PluginRegistrarArguments
+    ): ExitCode {
+        try {
+            k1Delegates.set(registrars.k1Registrars)
+            k2Delegates.set(registrars.k2Registrars)
+            arguments.addDelegatingTestRegistrars()
+            return compiler.exec(
+                messageCollector = messageCollector,
+                services = Services.EMPTY,
+                arguments = arguments
+            )
+        } finally {
+            k1Delegates.remove()
+            k2Delegates.remove()
         }
+    }
 
-        private fun K2JVMCompilerArguments.addDelegatingTestRegistrar() {
-            pluginClasspaths = (pluginClasspaths ?: arrayOf()) + resourcePathForSelfClassLoader
-        }
+    private fun K2JVMCompilerArguments.addDelegatingTestRegistrars() {
+        pluginClasspaths =
+            buildList {
+                    pluginClasspaths?.let { addAll(it) }
+                    add(k1ResourcePathForSelfClassLoader)
+                    add(k2ResourcePathForSelfClassLoader)
+                }
+                .toTypedArray()
     }
 }
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
index 7515bac..5335ea3 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
@@ -18,66 +18,35 @@
 
 import androidx.room.compiler.processing.util.compiler.steps.CompilationStepArguments
 import androidx.room.compiler.processing.util.compiler.steps.RawDiagnosticMessage
-import androidx.room.compiler.processing.util.getSystemClasspaths
 import java.io.File
-import java.net.URLClassLoader
 import org.jetbrains.kotlin.cli.common.ExitCode
 import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
 import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
-import org.jetbrains.kotlin.config.JvmDefaultMode
-import org.jetbrains.kotlin.config.JvmTarget
+import org.jetbrains.kotlin.compiler.plugin.parseLegacyPluginOption
+import org.jetbrains.kotlin.config.LanguageVersion
 
 /** Utility object to run kotlin compiler via its CLI API. */
 internal object KotlinCliRunner {
     private val compiler = K2JVMCompiler()
 
-    private fun List<SourceSet>.existingRootPaths() =
-        this.asSequence().map { it.root }.filter { it.exists() }.map { it.canonicalPath }.distinct()
-
-    private fun CompilationStepArguments.copyToCliArguments(cliArguments: K2JVMCompilerArguments) {
-        // stdlib is in the classpath so no need to specify it here.
-        cliArguments.noStdlib = true
-        cliArguments.noReflect = true
-        cliArguments.jvmTarget = JvmTarget.JVM_1_8.description
-        cliArguments.noOptimize = true
-        // useJavac & compileJava are experimental so lets not use it for now.
-        cliArguments.useJavac = false
-        cliArguments.compileJava = false
-        cliArguments.jvmDefault = JvmDefaultMode.ALL_COMPATIBILITY.description
-        cliArguments.allowNoSourceFiles = true
-        cliArguments.javacArguments = javacArguments.toTypedArray()
-        val inherited =
-            if (inheritClasspaths) {
-                inheritedClasspath
-            } else {
-                emptyList()
-            }
-        cliArguments.classpath =
-            (additionalClasspaths + inherited)
-                .filter { it.exists() }
-                .distinct()
-                .joinToString(separator = File.pathSeparator) { it.canonicalPath }
-        cliArguments.javaSourceRoots =
-            this.sourceSets.filter { it.hasJavaSource }.existingRootPaths().toList().toTypedArray()
-        cliArguments.freeArgs += this.sourceSets.filter { it.hasKotlinSource }.existingRootPaths()
-    }
-
-    /** Runs the kotlin cli API with the given arguments. */
-    @OptIn(ExperimentalCompilerApi::class)
+    /** Runs the Kotlin CLI API with the given arguments. */
     fun runKotlinCli(
         /** Compilation arguments (sources, classpaths etc) */
         arguments: CompilationStepArguments,
         /** Destination directory where generated class files will be written to */
         destinationDir: File,
-        /** List of component registrars for the compilation. */
-        @Suppress("DEPRECATION")
-        pluginRegistrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>
+        /** List of plugin registrars for the compilation. */
+        @OptIn(ExperimentalCompilerApi::class)
+        pluginRegistrars: PluginRegistrarArguments =
+            PluginRegistrarArguments(emptyList(), emptyList())
     ): KotlinCliResult {
-        val cliArguments = compiler.createArguments()
         destinationDir.mkdirs()
-        cliArguments.destination = destinationDir.absolutePath
-        arguments.copyToCliArguments(cliArguments)
+        val cliArguments =
+            compiler.createArguments().apply {
+                destination = destinationDir.absolutePath
+                arguments.copyToCliArguments(this)
+            }
         compiler.parseArguments(arguments.kotlincArguments.toTypedArray(), cliArguments)
 
         val diagnosticsMessageCollector = DiagnosticsMessageCollector("kotlinc")
@@ -86,7 +55,7 @@
                 compiler = compiler,
                 messageCollector = diagnosticsMessageCollector,
                 arguments = cliArguments,
-                pluginRegistrars = pluginRegistrars
+                registrars = pluginRegistrars
             )
 
         return KotlinCliResult(
@@ -97,6 +66,53 @@
         )
     }
 
+    /** Get the language version specified with `-language-version=xxx`. */
+    fun getLanguageVersion(kotlincArguments: List<String>): LanguageVersion {
+        val cliArguments = compiler.createArguments()
+        compiler.parseArguments(kotlincArguments.toTypedArray(), cliArguments)
+        return cliArguments.languageVersion?.let { LanguageVersion.fromVersionString(it) }
+            ?: TestDefaultOptions.kotlinLanguageVersion
+    }
+
+    private fun CompilationStepArguments.copyToCliArguments(cliArguments: K2JVMCompilerArguments) {
+        // stdlib is in the classpath so no need to specify it here.
+        cliArguments.noStdlib = true
+        cliArguments.noReflect = true
+        cliArguments.noOptimize = true
+
+        // We want allow no sources to run test handlers
+        cliArguments.allowNoSourceFiles = true
+
+        cliArguments.languageVersion = TestDefaultOptions.kotlinLanguageVersion.versionString
+        cliArguments.apiVersion = TestDefaultOptions.kotlinApiVersion.versionString
+        cliArguments.jvmTarget = TestDefaultOptions.jvmTarget.description
+        cliArguments.jvmDefault = TestDefaultOptions.jvmDefaultMode.description
+
+        // useJavac & compileJava are experimental so lets not use it for now.
+        cliArguments.useJavac = false
+        cliArguments.compileJava = false
+
+        cliArguments.javacArguments = javacArguments.toTypedArray()
+
+        val inherited =
+            if (inheritClasspaths) {
+                TestClasspath.inheritedClasspath
+            } else {
+                emptyList()
+            }
+        cliArguments.classpath =
+            (additionalClasspaths + inherited)
+                .filter { it.exists() }
+                .distinct()
+                .joinToString(separator = File.pathSeparator) { it.canonicalPath }
+
+        cliArguments.javaSourceRoots =
+            this.sourceSets.filter { it.hasJavaSource }.existingRootPaths().toList().toTypedArray()
+
+        // Sources to compile are passed as args
+        cliArguments.freeArgs += this.sourceSets.filter { it.hasKotlinSource }.existingRootPaths()
+    }
+
     /** Result of a kotlin compilation request */
     internal class KotlinCliResult(
         /** The exit code reported by the compiler */
@@ -109,47 +125,18 @@
         val kotlinCliArguments: K2JVMCompilerArguments
     )
 
-    private val inheritedClasspath by
-        lazy(LazyThreadSafetyMode.NONE) {
-            getClasspathFromClassloader(KotlinCliRunner::class.java.classLoader)
-        }
-
-    // ported from https://github.com/google/compile-testing/blob/master/src/main/java/com
-    // /google/testing/compile/Compiler.java#L231
-    private fun getClasspathFromClassloader(referenceClassLoader: ClassLoader): List<File> {
-        val platformClassLoader: ClassLoader = ClassLoader.getPlatformClassLoader()
-        var currentClassloader = referenceClassLoader
-        val systemClassLoader = ClassLoader.getSystemClassLoader()
-
-        // Concatenate search paths from all classloaders in the hierarchy
-        // 'till the system classloader.
-        val classpaths: MutableSet<String> = LinkedHashSet()
-        while (true) {
-            if (currentClassloader === systemClassLoader) {
-                classpaths.addAll(getSystemClasspaths())
-                break
-            }
-            if (currentClassloader === platformClassLoader) {
-                break
-            }
-            check(currentClassloader is URLClassLoader) {
-                """Classpath for compilation could not be extracted
-                since $currentClassloader is not an instance of URLClassloader
-                """
-                    .trimIndent()
-            }
-            // We only know how to extract classpaths from URLClassloaders.
-            currentClassloader.urLs.forEach { url ->
-                check(url.protocol == "file") {
-                    """Given classloader consists of classpaths which are unsupported for
-                    compilation.
-                    """
-                        .trimIndent()
-                }
-                classpaths.add(url.path)
-            }
-            currentClassloader = currentClassloader.parent
-        }
-        return classpaths.map { File(it) }.filter { it.exists() }
+    internal fun getPluginOptions(
+        pluginId: String,
+        kotlincArguments: List<String>
+    ): Map<String, String> {
+        val options =
+            kotlincArguments
+                .dropLast(1)
+                .zip(kotlincArguments.drop(1))
+                .filter { it.first == "-P" }
+                .mapNotNull { parseLegacyPluginOption(it.second) }
+        return options
+            .filter { it.pluginId == pluginId }
+            .associateBy({ it.optionName }, { it.value })
     }
 }
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp1Compilation.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp1Compilation.kt
new file mode 100644
index 0000000..92cdd9d
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp1Compilation.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import androidx.room.compiler.processing.util.FileResource
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepArguments
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepResult
+import androidx.room.compiler.processing.util.compiler.steps.resolveDiagnostics
+import com.google.devtools.ksp.KspOptions
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import java.io.File
+import org.jetbrains.kotlin.cli.common.ExitCode
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
+
+internal class Ksp1Compilation(
+    private val name: String,
+    private val symbolProcessorProviders: List<SymbolProcessorProvider>,
+    private val processorOptions: Map<String, String>
+) {
+    private fun createKspOptions(workingDir: File): KspOptions.Builder {
+        return KspOptions.Builder().apply {
+            this.javaOutputDir = workingDir.resolve(JAVA_OUT_DIR)
+            this.kotlinOutputDir = workingDir.resolve(KOTLIN_OUT_DIR)
+            this.processingOptions.putAll(processorOptions)
+        }
+    }
+
+    @OptIn(ExperimentalCompilerApi::class)
+    fun execute(workingDir: File, arguments: CompilationStepArguments): CompilationStepResult {
+        if (symbolProcessorProviders.isEmpty()) {
+            return CompilationStepResult.skip(arguments)
+        }
+        val kspMessages = DiagnosticsMessageCollector(name)
+        val result =
+            KotlinCliRunner.runKotlinCli(
+                arguments = arguments,
+                destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
+                pluginRegistrars =
+                    PluginRegistrarArguments(
+                        listOf(
+                            TestKspRegistrar(
+                                kspWorkingDir = workingDir.resolve("ksp-compiler"),
+                                baseOptions = createKspOptions(workingDir),
+                                processorProviders = symbolProcessorProviders,
+                                messageCollector = kspMessages
+                            )
+                        ),
+                        emptyList()
+                    ),
+            )
+        // workaround for https://github.com/google/ksp/issues/623
+        val failureDueToWarnings =
+            result.kotlinCliArguments.allWarningsAsErrors && kspMessages.hasWarnings()
+
+        val generatedSources =
+            listOfNotNull(
+                workingDir.resolve(KOTLIN_OUT_DIR).toSourceSet(),
+                workingDir.resolve(JAVA_OUT_DIR).toSourceSet(),
+            )
+        val diagnostics =
+            resolveDiagnostics(
+                diagnostics = result.diagnostics + kspMessages.getDiagnostics(),
+                sourceSets = arguments.sourceSets + generatedSources
+            )
+        val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
+        val outputClasspath = listOf(result.compiledClasspath) + outputResources
+        val generatedResources =
+            outputResources
+                .walkTopDown()
+                .filter { it.isFile }
+                .map { FileResource(it.relativeTo(outputResources).path, it) }
+                .toList()
+        return CompilationStepResult(
+            success = result.exitCode == ExitCode.OK && !failureDueToWarnings,
+            generatedSourceRoots = generatedSources,
+            diagnostics = diagnostics,
+            nextCompilerArguments =
+                arguments.copy(sourceSets = arguments.sourceSets + generatedSources),
+            outputClasspath = outputClasspath,
+            generatedResources = generatedResources
+        )
+    }
+
+    companion object {
+        private const val JAVA_OUT_DIR = "generatedJava"
+        private const val KOTLIN_OUT_DIR = "generatedKotlin"
+        private const val CLASS_OUT_FOLDER_NAME = "class-out"
+        private const val RESOURCES_OUT_FOLDER_NAME = "ksp-compiler/resourceOutputDir"
+    }
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp2Compilation.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp2Compilation.kt
new file mode 100644
index 0000000..6f1b94b
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/Ksp2Compilation.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import androidx.room.compiler.processing.util.FileResource
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepArguments
+import androidx.room.compiler.processing.util.compiler.steps.CompilationStepResult
+import androidx.room.compiler.processing.util.compiler.steps.RawDiagnosticMessage
+import androidx.room.compiler.processing.util.compiler.steps.resolveDiagnostics
+import com.google.devtools.ksp.impl.KotlinSymbolProcessing
+import com.google.devtools.ksp.processing.KSPJvmConfig
+import com.google.devtools.ksp.processing.KSPLogger
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import com.google.devtools.ksp.symbol.FileLocation
+import com.google.devtools.ksp.symbol.KSNode
+import com.google.devtools.ksp.symbol.NonExistLocation
+import java.io.File
+import java.io.PrintWriter
+import java.io.StringWriter
+import javax.tools.Diagnostic
+
+internal class Ksp2Compilation(
+    private val name: String,
+    private val symbolProcessorProviders: List<SymbolProcessorProvider>,
+    private val processorOptions: Map<String, String>
+) {
+    fun execute(workingDir: File, arguments: CompilationStepArguments): CompilationStepResult {
+        if (symbolProcessorProviders.isEmpty()) {
+            return CompilationStepResult.skip(arguments)
+        }
+
+        val kspConfig = createKspConfig(workingDir, arguments)
+        val kspDiagnostics = DiagnosticsCollectorKspLogger()
+        val exitCode =
+            KotlinSymbolProcessing(
+                    kspConfig = kspConfig,
+                    symbolProcessorProviders = symbolProcessorProviders,
+                    logger = kspDiagnostics
+                )
+                .execute()
+        val generatedSources =
+            listOfNotNull(
+                workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME).toSourceSet(),
+                workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).toSourceSet(),
+            )
+        val diagnostics =
+            resolveDiagnostics(
+                diagnostics = kspDiagnostics.messages,
+                sourceSets = arguments.sourceSets + generatedSources
+            )
+        val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
+        val outputClasspath = listOf(workingDir.resolve(CLASS_OUT_FOLDER_NAME))
+        val generatedResources =
+            outputResources
+                .walkTopDown()
+                .filter { it.isFile }
+                .map { FileResource(it.relativeTo(outputResources).path, it) }
+                .toList()
+        return CompilationStepResult(
+            success = exitCode == KotlinSymbolProcessing.ExitCode.OK,
+            generatedSourceRoots = generatedSources,
+            diagnostics = diagnostics,
+            nextCompilerArguments =
+                arguments.copy(sourceSets = arguments.sourceSets + generatedSources),
+            outputClasspath = outputClasspath,
+            generatedResources = generatedResources
+        )
+    }
+
+    private fun createKspConfig(workingDir: File, arguments: CompilationStepArguments) =
+        KSPJvmConfig.Builder()
+            .apply {
+                projectBaseDir = workingDir
+
+                sourceRoots =
+                    arguments.sourceSets.filter { it.hasKotlinSource }.existingRoots().toList()
+                javaSourceRoots =
+                    arguments.sourceSets.filter { it.hasJavaSource }.existingRoots().toList()
+
+                libraries = buildList {
+                    if (arguments.inheritClasspaths) {
+                        addAll(TestClasspath.inheritedClasspath)
+                    }
+                    addAll(arguments.additionalClasspaths)
+                }
+                jdkHome = File(System.getProperty("java.home"))
+
+                outputBaseDir = workingDir
+                javaOutputDir = workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME)
+                kotlinOutputDir = workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME)
+                resourceOutputDir = workingDir.resolve(RESOURCE_OUT_FOLDER_NAME)
+                classOutputDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME)
+
+                cachesDir = workingDir.resolve(CACHE_FOLDER_NAME)
+
+                moduleName = ""
+
+                languageVersion = TestDefaultOptions.kotlinLanguageVersion.versionString
+                apiVersion = TestDefaultOptions.kotlinApiVersion.versionString
+                jvmTarget = TestDefaultOptions.jvmTarget.description
+                jvmDefaultMode = TestDefaultOptions.jvmDefaultMode.description
+
+                processorOptions = this@Ksp2Compilation.processorOptions
+            }
+            .build()
+
+    // We purposely avoid using MessageCollectorBasedKSPLogger to reduce our dependency on impls.
+    private class DiagnosticsCollectorKspLogger : KSPLogger {
+
+        val messages = mutableListOf<RawDiagnosticMessage>()
+
+        override fun error(message: String, symbol: KSNode?) {
+            messages.add(RawDiagnosticMessage(Diagnostic.Kind.ERROR, message, symbol.toLocation()))
+        }
+
+        override fun exception(e: Throwable) {
+            val writer = StringWriter()
+            e.printStackTrace(PrintWriter(writer))
+            messages.add(RawDiagnosticMessage(Diagnostic.Kind.ERROR, writer.toString(), null))
+        }
+
+        override fun info(message: String, symbol: KSNode?) {
+            messages.add(RawDiagnosticMessage(Diagnostic.Kind.NOTE, message, symbol.toLocation()))
+        }
+
+        override fun logging(message: String, symbol: KSNode?) {
+            messages.add(RawDiagnosticMessage(Diagnostic.Kind.NOTE, message, symbol.toLocation()))
+        }
+
+        override fun warn(message: String, symbol: KSNode?) {
+            messages.add(
+                RawDiagnosticMessage(Diagnostic.Kind.WARNING, message, symbol.toLocation())
+            )
+        }
+
+        private fun KSNode?.toLocation(): RawDiagnosticMessage.Location? {
+            val location = this?.location ?: return null
+            return when (location) {
+                is FileLocation ->
+                    RawDiagnosticMessage.Location(
+                        path = location.filePath,
+                        line = location.lineNumber
+                    )
+                NonExistLocation -> null
+            }
+        }
+    }
+
+    companion object {
+        private const val JAVA_SRC_OUT_FOLDER_NAME = "ksp-java-src-out"
+        private const val KOTLIN_SRC_OUT_FOLDER_NAME = "ksp-kotlin-src-out"
+        private const val RESOURCE_OUT_FOLDER_NAME = "ksp-resource-out"
+        private const val CACHE_FOLDER_NAME = "ksp-cache"
+        private const val CLASS_OUT_FOLDER_NAME = "class-out"
+        private const val RESOURCES_OUT_FOLDER_NAME = "ksp-compiler/resourceOutputDir"
+    }
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
index e1bf9a1..cc02a4f 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
@@ -105,3 +105,9 @@
     } else {
         null
     }
+
+internal fun List<SourceSet>.existingRoots() =
+    this.asSequence().map { it.root }.filter { it.exists() }.distinct()
+
+internal fun List<SourceSet>.existingRootPaths() =
+    this.asSequence().map { it.root }.filter { it.exists() }.map { it.canonicalPath }.distinct()
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestClasspath.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestClasspath.kt
new file mode 100644
index 0000000..4bbe437
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestClasspath.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import androidx.room.compiler.processing.util.getSystemClasspaths
+import java.io.File
+import java.net.URLClassLoader
+
+/** Test runtime classpath helper. */
+internal object TestClasspath {
+    internal val inheritedClasspath by
+        lazy(LazyThreadSafetyMode.NONE) {
+            getClasspathFromClassloader(KotlinCliRunner::class.java.classLoader)
+        }
+
+    // Ported from
+    // https://github.com/google/compile-testing/blob/master/src/main/java/com/google/testing/compile/Compiler.java#L231
+    private fun getClasspathFromClassloader(referenceClassLoader: ClassLoader): List<File> {
+        val platformClassLoader: ClassLoader = ClassLoader.getPlatformClassLoader()
+        var currentClassloader = referenceClassLoader
+        val systemClassLoader = ClassLoader.getSystemClassLoader()
+
+        // Concatenate search paths from all classloaders in the hierarchy
+        // 'till the system classloader.
+        val classpaths: MutableSet<String> = LinkedHashSet()
+        while (true) {
+            if (currentClassloader === systemClassLoader) {
+                classpaths.addAll(getSystemClasspaths())
+                break
+            }
+            if (currentClassloader === platformClassLoader) {
+                break
+            }
+            check(currentClassloader is URLClassLoader) {
+                "Classpath for compilation could not be extracted since $currentClassloader " +
+                    "is not an instance of URLClassloader"
+            }
+
+            // We only know how to extract classpaths from URLClassloaders.
+            currentClassloader.urLs.forEach { url ->
+                check(url.protocol == "file") {
+                    "Given classloader consists of classpaths which are unsupported for " +
+                        "compilation."
+                }
+                classpaths.add(url.path)
+            }
+            currentClassloader = currentClassloader.parent
+        }
+        return classpaths.map { File(it) }.filter { it.exists() }
+    }
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestDefaultOptions.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestDefaultOptions.kt
new file mode 100644
index 0000000..48ea099
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestDefaultOptions.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util.compiler
+
+import org.jetbrains.kotlin.config.ApiVersion
+import org.jetbrains.kotlin.config.JvmDefaultMode
+import org.jetbrains.kotlin.config.JvmTarget
+import org.jetbrains.kotlin.config.LanguageVersion
+
+/** Default argument / options across the steps and test compiler infra. */
+internal object TestDefaultOptions {
+    // TODO(kuanyingchou): Change it back to follow AndroidX once it's updated to K2.
+    internal val kotlinLanguageVersion = LanguageVersion.KOTLIN_2_0
+    internal val kotlinApiVersion = ApiVersion.createByLanguageVersion(kotlinLanguageVersion)
+    internal val jvmTarget = JvmTarget.JVM_1_8
+    internal val jvmDefaultMode = JvmDefaultMode.ALL_COMPATIBILITY
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt
deleted file mode 100644
index 18d556d..0000000
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright 2010-2016 JetBrains s.r.o.
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.compiler.processing.util.compiler
-
-import java.io.File
-import javax.annotation.processing.Processor
-import org.jetbrains.kotlin.base.kapt3.KaptFlag
-import org.jetbrains.kotlin.base.kapt3.KaptOptions
-import org.jetbrains.kotlin.base.kapt3.logString
-import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
-import org.jetbrains.kotlin.cli.common.messages.MessageCollector
-import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
-import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
-import org.jetbrains.kotlin.com.intellij.mock.MockProject
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
-import org.jetbrains.kotlin.config.CompilerConfiguration
-import org.jetbrains.kotlin.config.JVMConfigurationKeys
-import org.jetbrains.kotlin.container.StorageComponentContainer
-import org.jetbrains.kotlin.container.useInstance
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
-import org.jetbrains.kotlin.descriptors.ModuleDescriptor
-import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
-import org.jetbrains.kotlin.kapt3.AbstractKapt3Extension
-import org.jetbrains.kotlin.kapt3.base.LoadedProcessors
-import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
-import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
-import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
-import org.jetbrains.kotlin.kapt3.util.doOpenInternalPackagesIfRequired
-import org.jetbrains.kotlin.platform.TargetPlatform
-import org.jetbrains.kotlin.platform.jvm.isJvm
-import org.jetbrains.kotlin.resolve.jvm.ReplaceWithSupertypeAnonymousTypeTransformer
-import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
-import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
-import org.jetbrains.kotlin.types.KotlinType
-
-/**
- * Registers the KAPT component for the kotlin compilation.
- *
- * mostly taken from
- * https://github.com/JetBrains/kotlin/blob/master/plugins/kapt3/kapt3-compiler/src/
- * org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt
- */
-@Suppress("DEPRECATION") // TODO: Migrate ComponentRegistrar to CompilerPluginRegistrar
-@OptIn(ExperimentalCompilerApi::class)
-internal class TestKapt3Registrar(
-    val processors: List<Processor>,
-    val baseOptions: KaptOptions.Builder,
-    val messageCollector: MessageCollector
-) : @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
-    override fun registerProjectComponents(
-        project: MockProject,
-        configuration: CompilerConfiguration
-    ) {
-        doOpenInternalPackagesIfRequired()
-        val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
-
-        val optionsBuilder =
-            baseOptions.apply {
-                projectBaseDir = project.basePath?.let(::File)
-                compileClasspath.addAll(
-                    contentRoots.filterIsInstance<JvmClasspathRoot>().map { it.file }
-                )
-                javaSourceRoots.addAll(
-                    contentRoots.filterIsInstance<JavaSourceRoot>().map { it.file }
-                )
-                classesOutputDir =
-                    classesOutputDir ?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)
-            }
-
-        val logger =
-            MessageCollectorBackedKaptLogger(
-                isVerbose = optionsBuilder.flags.contains(KaptFlag.VERBOSE),
-                isInfoAsWarnings = optionsBuilder.flags.contains(KaptFlag.INFO_AS_WARNINGS),
-                messageCollector = messageCollector
-            )
-
-        val options = optionsBuilder.build()
-
-        options.sourcesOutputDir.mkdirs()
-
-        if (options[KaptFlag.VERBOSE]) {
-            logger.info(options.logString())
-        }
-
-        val kapt3AnalysisCompletedHandlerExtension =
-            object :
-                AbstractKapt3Extension(
-                    options = options,
-                    logger = logger,
-                    compilerConfiguration = configuration
-                ) {
-                override fun loadProcessors(): LoadedProcessors {
-                    return LoadedProcessors(
-                        processors =
-                            processors.map {
-                                IncrementalProcessor(
-                                    processor = it,
-                                    kind = DeclaredProcType.NON_INCREMENTAL,
-                                    logger = logger
-                                )
-                            },
-                        classLoader = TestKapt3Registrar::class.java.classLoader
-                    )
-                }
-            }
-
-        AnalysisHandlerExtension.registerExtension(project, kapt3AnalysisCompletedHandlerExtension)
-        StorageComponentContainerContributor.registerExtension(
-            project,
-            KaptComponentContributor(kapt3AnalysisCompletedHandlerExtension)
-        )
-    }
-
-    class KaptComponentContributor(private val analysisExtension: PartialAnalysisHandlerExtension) :
-        StorageComponentContainerContributor {
-        override fun registerModuleComponents(
-            container: StorageComponentContainer,
-            platform: TargetPlatform,
-            moduleDescriptor: ModuleDescriptor
-        ) {
-            if (!platform.isJvm()) return
-            container.useInstance(
-                object : ReplaceWithSupertypeAnonymousTypeTransformer() {
-                    override fun transformAnonymousType(
-                        descriptor: DeclarationDescriptorWithVisibility,
-                        type: KotlinType
-                    ): KotlinType? {
-                        if (!analysisExtension.analyzePartially) return null
-                        return super.transformAnonymousType(descriptor, type)
-                    }
-                }
-            )
-        }
-    }
-}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt
index 8913ac9..2849e59 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKotlinCompiler.kt
@@ -29,6 +29,8 @@
 import java.io.File
 import javax.annotation.processing.Processor
 import javax.tools.Diagnostic
+import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 
 /** Compilation runner for kotlin using kotlin CLI tool */
 data class TestCompilationArguments(
@@ -70,6 +72,13 @@
     val generatedResources: List<Resource>,
 )
 
+@OptIn(ExperimentalCompilerApi::class)
+internal class PluginRegistrarArguments(
+    @Suppress("DEPRECATION")
+    val k1Registrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>,
+    val k2Registrars: List<CompilerPluginRegistrar>
+)
+
 /** Ensures the list of sources has at least 1 kotlin file, if not, adds one. */
 internal fun TestCompilationArguments.withAtLeastOneKotlinSource(): TestCompilationArguments {
     val hasKotlinSource = sources.any { it is Source.KotlinSource }
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
index 074079e..0deef7a 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.room.compiler.processing.util.compiler
 
 import com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension
@@ -64,15 +63,12 @@
                     ?: kspWorkingDir.resolve(KspCliOption.RESOURCE_OUTPUT_DIR_OPTION.optionName)
             cachesDir =
                 cachesDir ?: kspWorkingDir.resolve(KspCliOption.CACHES_DIR_OPTION.optionName)
-
             kspOutputDir =
                 kspOutputDir ?: kspWorkingDir.resolve(KspCliOption.KSP_OUTPUT_DIR_OPTION.optionName)
             val contentRoots = configuration[CLIConfigurationKeys.CONTENT_ROOTS] ?: emptyList()
-
             compileClasspath.addAll(
                 contentRoots.filterIsInstance<JvmClasspathRoot>().map { it.file }
             )
-
             javaSourceRoots.addAll(contentRoots.filterIsInstance<JavaSourceRoot>().map { it.file })
         }
         val logger =
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt
index bf807f6..9551279 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/JavaSourceCompilationStep.kt
@@ -25,9 +25,11 @@
 import javax.tools.JavaFileObject
 
 /**
- * Compiles java sources. Note that this does not run java annotation processors. They are run in
- * the KAPT step for consistency. When a test is run with purely java sources, it uses the google
- * compile testing library directly instead of the kotlin compilation pipeline.
+ * Compiles Java sources.
+ *
+ * Note that this does not run Java annotation processors. They are run in the KAPT step for
+ * consistency. When a test is run with purely Java sources, it uses google-compile-testing library
+ * directly instead of the Kotlin compilation pipeline.
  */
 internal object JavaSourceCompilationStep : KotlinCompilationStep {
     override val name = "javaSourceCompilation"
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
index ae6d781..dc620b9 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
@@ -17,61 +17,33 @@
 package androidx.room.compiler.processing.util.compiler.steps
 
 import androidx.room.compiler.processing.util.FileResource
-import androidx.room.compiler.processing.util.compiler.DiagnosticsMessageCollector
 import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
-import androidx.room.compiler.processing.util.compiler.TestKapt3Registrar
 import androidx.room.compiler.processing.util.compiler.toSourceSet
+import java.io.ByteArrayOutputStream
 import java.io.File
+import java.io.ObjectOutputStream
+import java.util.Base64
 import javax.annotation.processing.Processor
-import org.jetbrains.kotlin.base.kapt3.AptMode
-import org.jetbrains.kotlin.base.kapt3.KaptFlag
-import org.jetbrains.kotlin.base.kapt3.KaptOptions
 import org.jetbrains.kotlin.cli.common.ExitCode
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
-import org.jetbrains.kotlin.compiler.plugin.parseLegacyPluginOption
+import org.jetbrains.kotlin.com.intellij.util.PathUtil
+import org.jetbrains.kotlin.kapt.cli.CliToolOption
+import org.jetbrains.kotlin.kapt.cli.KaptCliOption
+import org.jetbrains.kotlin.kapt3.base.AptMode
 
-/** Runs KAPT to run annotation processors. */
+/** Runs KAPT to run Java annotation processors. */
 internal class KaptCompilationStep(
     private val annotationProcessors: List<Processor>,
     private val processorOptions: Map<String, String>,
 ) : KotlinCompilationStep {
     override val name = "kapt"
 
-    private fun createKaptArgs(
-        workingDir: File,
-        javacArguments: List<String>,
-        kotlincArguments: List<String>
-    ): KaptOptions.Builder {
-        return KaptOptions.Builder().also {
-            it.stubsOutputDir = workingDir.resolve("kapt-stubs") // IGNORED
-            it.sourcesOutputDir = workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME)
-            // Compiled classes don't end up here but generated resources do.
-            it.classesOutputDir = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
-            it.projectBaseDir = workingDir
-            it.processingOptions["kapt.kotlin.generated"] =
-                workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).also { it.mkdirs() }.canonicalPath
-            it.processingOptions.putAll(processorOptions)
-            it.mode = AptMode.STUBS_AND_APT
-            it.processors.addAll(annotationProcessors.map { it::class.java.name })
-            // NOTE: this does not work very well until the following bug is fixed
-            //  https://youtrack.jetbrains.com/issue/KT-47934
-            it.flags.add(KaptFlag.MAP_DIAGNOSTIC_LOCATIONS)
-
-            if (
-                getPluginOptions(KAPT_PLUGIN_ID, kotlincArguments)
-                    .getOrDefault("correctErrorTypes", "false") == "true"
-            ) {
-                it.flags.add(KaptFlag.CORRECT_ERROR_TYPES)
-            }
-
-            javacArguments.forEach { javacArg ->
-                it.javacOptions[javacArg.substringBefore("=")] =
-                    javacArg.substringAfter("=", missingDelimiterValue = "")
-            }
+    init {
+        check(annotationProcessors.size <= 10) {
+            "Only 10 annotation processor can be loaded for test compilation for now, but " +
+                "requested ${annotationProcessors.size}. Tell Dany to support more!"
         }
     }
 
-    @OptIn(ExperimentalCompilerApi::class)
     override fun execute(
         workingDir: File,
         arguments: CompilationStepArguments
@@ -79,34 +51,38 @@
         if (annotationProcessors.isEmpty()) {
             return CompilationStepResult.skip(arguments)
         }
-        val kaptMessages = DiagnosticsMessageCollector(name)
+
+        val kaptArgs = buildList {
+            // Both 'kotlin-annotation-processing.jar' and
+            // 'kotlin-annotation-processing-embeddable.jar' contain the KAPT plugin that must
+            // specified so we use the `CliToolOption` class just as `KaptCli` does.
+            val pathToAnnotationJar = PathUtil.getJarPathForClass(CliToolOption::class.java)
+            add("-Xplugin=$pathToAnnotationJar")
+            createKaptCliOptions(workingDir, arguments).forEach { (option, value) ->
+                add("-P")
+                add("plugin:$KAPT_PLUGIN_ID:${option.optionName}=$value")
+            }
+        }
+        val argumentsWithKapt =
+            arguments.copy(kotlincArguments = arguments.kotlincArguments + kaptArgs)
         val result =
-            KotlinCliRunner.runKotlinCli(
-                arguments = arguments, // output is ignored,
-                destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
-                pluginRegistrars =
-                    listOf(
-                        TestKapt3Registrar(
-                            processors = annotationProcessors,
-                            baseOptions =
-                                createKaptArgs(
-                                    workingDir,
-                                    arguments.javacArguments,
-                                    arguments.kotlincArguments
-                                ),
-                            messageCollector = kaptMessages
-                        )
-                    )
-            )
+            try {
+                delegateProcessors.set(annotationProcessors)
+                KotlinCliRunner.runKotlinCli(
+                    arguments = argumentsWithKapt,
+                    destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME)
+                )
+            } finally {
+                delegateProcessors.remove()
+            }
         val generatedSources =
             listOfNotNull(
                 workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME).toSourceSet(),
                 workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).toSourceSet()
             )
-
         val diagnostics =
             resolveDiagnostics(
-                diagnostics = result.diagnostics + kaptMessages.getDiagnostics(),
+                diagnostics = result.diagnostics,
                 sourceSets = arguments.sourceSets + generatedSources
             )
         val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
@@ -128,28 +104,121 @@
         )
     }
 
+    private fun createKaptCliOptions(
+        workingDir: File,
+        arguments: CompilationStepArguments
+    ): List<Pair<KaptCliOption, String>> = buildList {
+        add(KaptCliOption.APT_MODE_OPTION to AptMode.STUBS_AND_APT.stringValue)
+        add(
+            KaptCliOption.SOURCE_OUTPUT_DIR_OPTION to
+                workingDir.resolve(JAVA_SRC_OUT_FOLDER_NAME).absolutePath
+        )
+        // Compiled classes don't end up here but generated resources do.
+        add(
+            KaptCliOption.CLASS_OUTPUT_DIR_OPTION to
+                workingDir.resolve(RESOURCES_OUT_FOLDER_NAME).absolutePath
+        )
+        add(
+            KaptCliOption.STUBS_OUTPUT_DIR_OPTION to
+                workingDir.resolve(STUBS_OUT_FOLDER_NAME).absolutePath
+        )
+
+        // 'apclasspath' is not used since FQN are specified in 'processors', but if left unset
+        // KAPT does not try to load processors at all.
+        add(KaptCliOption.ANNOTATION_PROCESSOR_CLASSPATH_OPTION to "empty")
+        List(annotationProcessors.size) { index ->
+            add(
+                KaptCliOption.ANNOTATION_PROCESSORS_OPTION to
+                    TestDelegateProcessor.KaptTestDelegateAP0::class.java.name.dropLast(1) + index
+            )
+        }
+
+        val apOptionsMap = buildMap {
+            // Kotlin generated source output location is specified through this special
+            // annotation processor option.
+            put(
+                "kapt.kotlin.generated",
+                workingDir.resolve(KOTLIN_SRC_OUT_FOLDER_NAME).also { it.mkdirs() }.canonicalPath
+            )
+            putAll(processorOptions)
+        }
+        // We *need* to use the deprecated 'apoptions' since it supports multiple values as
+        // opposed to 'apOption' that only accepts one value and also is
+        // allowMultipleOccurrences = false
+        @Suppress("DEPRECATION")
+        add(KaptCliOption.APT_OPTIONS_OPTION to apOptionsMap.base64Encoded())
+
+        val javacOptionsMap =
+            arguments.javacArguments.associate { rawArg ->
+                val keyValuePair = rawArg.split('=', limit = 2).takeIf { it.size == 2 }
+                if (keyValuePair != null) {
+                    keyValuePair[0] to keyValuePair[1]
+                } else {
+                    rawArg to ""
+                }
+            }
+        // We *need* to use the deprecated 'javacArguments' since it supports multiple values as
+        // opposed to 'javacOption' that only accepts one value and also is
+        // allowMultipleOccurrences = false
+        @Suppress("DEPRECATION")
+        add(KaptCliOption.JAVAC_CLI_OPTIONS_OPTION to javacOptionsMap.base64Encoded())
+
+        // NOTE: This does not work very well until the following bug is fixed
+        //  https://youtrack.jetbrains.com/issue/KT-47934
+        add(KaptCliOption.MAP_DIAGNOSTIC_LOCATIONS_OPTION to "true")
+    }
+
+    // As suggested by https://kotlinlang.org/docs/kapt.html#ap-javac-options-encoding
+    private fun Map<String, String>.base64Encoded(): String {
+        val os = ByteArrayOutputStream()
+        val oos = ObjectOutputStream(os)
+        oos.writeInt(size)
+        for ((key, value) in entries) {
+            oos.writeUTF(key)
+            oos.writeUTF(value)
+        }
+        oos.flush()
+        return Base64.getEncoder().encodeToString(os.toByteArray())
+    }
+
     companion object {
         private const val JAVA_SRC_OUT_FOLDER_NAME = "kapt-java-src-out"
         private const val KOTLIN_SRC_OUT_FOLDER_NAME = "kapt-kotlin-src-out"
+        private const val STUBS_OUT_FOLDER_NAME = "kapt-stubs-out"
         private const val RESOURCES_OUT_FOLDER_NAME = "kapt-classes-out"
         private const val CLASS_OUT_FOLDER_NAME = "class-out"
         private const val KAPT_PLUGIN_ID = "org.jetbrains.kotlin.kapt3"
-
-        internal fun getPluginOptions(
-            pluginId: String,
-            kotlincArguments: List<String>
-        ): Map<String, String> {
-            val options =
-                kotlincArguments
-                    .dropLast(1)
-                    .zip(kotlincArguments.drop(1))
-                    .filter { it.first == "-P" }
-                    .mapNotNull { parseLegacyPluginOption(it.second) }
-            val filteredOptionsMap =
-                options
-                    .filter { it.pluginId == pluginId }
-                    .associateBy({ it.optionName }, { it.value })
-            return filteredOptionsMap
-        }
     }
 }
+
+/** The list of processors to delegate to during the test compilation. */
+private val delegateProcessors = ThreadLocal<List<Processor>>()
+
+/**
+ * These delegate classes may seem unused but will be instantiated by KAPT via reflection and
+ * through their no-arg constructor, and we use them to delegate to actual processors provided for
+ * the test compilation. Note that the processor to delegate to is index based and obtained from the
+ * thread local [delegateProcessors] that is set before compiler invocation.
+ */
+@Suppress("UNUSED")
+sealed class TestDelegateProcessor(val delegate: Processor) : Processor by delegate {
+    class KaptTestDelegateAP0 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[0])
+
+    class KaptTestDelegateAP1 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[1])
+
+    class KaptTestDelegateAP2 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[2])
+
+    class KaptTestDelegateAP3 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[3])
+
+    class KaptTestDelegateAP4 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[4])
+
+    class KaptTestDelegateAP5 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[5])
+
+    class KaptTestDelegateAP6 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[6])
+
+    class KaptTestDelegateAP7 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[7])
+
+    class KaptTestDelegateAP8 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[8])
+
+    class KaptTestDelegateAP9 : TestDelegateProcessor(checkNotNull(delegateProcessors.get())[9])
+}
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt
index 1017983..558afec 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinCompilationStep.kt
@@ -25,8 +25,11 @@
 import javax.tools.Diagnostic
 
 /**
- * Kotlin compilation is run in multiple steps: process KSP process KAPT compile kotlin sources
- * compile java sources
+ * Kotlin compilation is run in multiple steps:
+ * * process KSP
+ * * process KAPT
+ * * compile kotlin sources
+ * * compile java sources
  *
  * Each step implements the [KotlinCompilationStep] interfaces and provides the arguments for the
  * following step.
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
index 779070e..17b431c 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
@@ -19,17 +19,15 @@
 import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
 import java.io.File
 import org.jetbrains.kotlin.cli.common.ExitCode
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 
 /**
- * Compiles kotlin sources.
+ * Compiles Kotlin sources.
  *
- * Note that annotation/symbol processors are not run by this step.
+ * Note that annotation / symbol processors are not run by this step.
  */
 internal object KotlinSourceCompilationStep : KotlinCompilationStep {
     override val name = "kotlinSourceCompilation"
 
-    @OptIn(ExperimentalCompilerApi::class)
     override fun execute(
         workingDir: File,
         arguments: CompilationStepArguments
@@ -41,7 +39,6 @@
             KotlinCliRunner.runKotlinCli(
                 arguments = arguments,
                 destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
-                pluginRegistrars = emptyList()
             )
         val diagnostics =
             resolveDiagnostics(diagnostics = result.diagnostics, sourceSets = arguments.sourceSets)
@@ -55,7 +52,8 @@
                         listOf(workingDir.resolve(CLASS_OUT_FOLDER_NAME)) +
                             arguments.additionalClasspaths,
                     // NOTE: ideally, we should remove kotlin sources but we know that there are no
-                    // more kotlin steps so we skip unnecessary work
+                    // more
+                    // kotlin steps so we skip unnecessary work
                     sourceSets = arguments.sourceSets
                 ),
             outputClasspath = listOf(result.compiledClasspath),
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
index 1f77c72..fdda61e 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
@@ -16,92 +16,31 @@
 
 package androidx.room.compiler.processing.util.compiler.steps
 
-import androidx.room.compiler.processing.util.FileResource
-import androidx.room.compiler.processing.util.compiler.DiagnosticsMessageCollector
 import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
-import androidx.room.compiler.processing.util.compiler.TestKspRegistrar
-import androidx.room.compiler.processing.util.compiler.toSourceSet
-import com.google.devtools.ksp.KspOptions
+import androidx.room.compiler.processing.util.compiler.Ksp1Compilation
+import androidx.room.compiler.processing.util.compiler.Ksp2Compilation
 import com.google.devtools.ksp.processing.SymbolProcessorProvider
 import java.io.File
-import org.jetbrains.kotlin.cli.common.ExitCode
-import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
+import org.jetbrains.kotlin.config.LanguageVersion
 
-/** Runs the Symbol Processors */
+/** Runs KSP to run the Symbol Processors */
 internal class KspCompilationStep(
     private val symbolProcessorProviders: List<SymbolProcessorProvider>,
     private val processorOptions: Map<String, String>
 ) : KotlinCompilationStep {
     override val name: String = "ksp"
 
-    private fun createKspOptions(workingDir: File): KspOptions.Builder {
-        return KspOptions.Builder().apply {
-            this.javaOutputDir = workingDir.resolve(JAVA_OUT_DIR)
-            this.kotlinOutputDir = workingDir.resolve(KOTLIN_OUT_DIR)
-            this.processingOptions.putAll(processorOptions)
-        }
-    }
-
-    @OptIn(ExperimentalCompilerApi::class)
     override fun execute(
         workingDir: File,
         arguments: CompilationStepArguments
     ): CompilationStepResult {
-        if (symbolProcessorProviders.isEmpty()) {
-            return CompilationStepResult.skip(arguments)
+        val languageVersion = KotlinCliRunner.getLanguageVersion(arguments.kotlincArguments)
+        return if (languageVersion < LanguageVersion.KOTLIN_2_0) {
+            Ksp1Compilation(name, symbolProcessorProviders, processorOptions)
+                .execute(workingDir, arguments)
+        } else {
+            Ksp2Compilation(name, symbolProcessorProviders, processorOptions)
+                .execute(workingDir, arguments)
         }
-        val kspMessages = DiagnosticsMessageCollector(name)
-        val result =
-            KotlinCliRunner.runKotlinCli(
-                arguments = arguments,
-                destinationDir = workingDir.resolve(CLASS_OUT_FOLDER_NAME),
-                pluginRegistrars =
-                    listOf(
-                        TestKspRegistrar(
-                            kspWorkingDir = workingDir.resolve("ksp-compiler"),
-                            baseOptions = createKspOptions(workingDir),
-                            processorProviders = symbolProcessorProviders,
-                            messageCollector = kspMessages
-                        )
-                    ),
-            )
-        // workaround for https://github.com/google/ksp/issues/623
-        val failureDueToWarnings =
-            result.kotlinCliArguments.allWarningsAsErrors && kspMessages.hasWarnings()
-
-        val generatedSources =
-            listOfNotNull(
-                workingDir.resolve(KOTLIN_OUT_DIR).toSourceSet(),
-                workingDir.resolve(JAVA_OUT_DIR).toSourceSet(),
-            )
-        val diagnostics =
-            resolveDiagnostics(
-                diagnostics = result.diagnostics + kspMessages.getDiagnostics(),
-                sourceSets = arguments.sourceSets + generatedSources
-            )
-        val outputResources = workingDir.resolve(RESOURCES_OUT_FOLDER_NAME)
-        val outputClasspath = listOf(result.compiledClasspath) + outputResources
-        val generatedResources =
-            outputResources
-                .walkTopDown()
-                .filter { it.isFile }
-                .map { FileResource(it.relativeTo(outputResources).path, it) }
-                .toList()
-        return CompilationStepResult(
-            success = result.exitCode == ExitCode.OK && !failureDueToWarnings,
-            generatedSourceRoots = generatedSources,
-            diagnostics = diagnostics,
-            nextCompilerArguments =
-                arguments.copy(sourceSets = arguments.sourceSets + generatedSources),
-            outputClasspath = outputClasspath,
-            generatedResources = generatedResources
-        )
-    }
-
-    companion object {
-        private const val JAVA_OUT_DIR = "generatedJava"
-        private const val KOTLIN_OUT_DIR = "generatedKotlin"
-        private const val CLASS_OUT_FOLDER_NAME = "class-out"
-        private const val RESOURCES_OUT_FOLDER_NAME = "ksp-compiler/resourceOutputDir"
     }
 }
diff --git a/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
new file mode 100644
index 0000000..57a62dc
--- /dev/null
+++ b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
@@ -0,0 +1,17 @@
+#
+# Copyright 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar$K2Registrar
\ No newline at end of file
diff --git a/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
index ffaca31..67086aa 100644
--- a/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+++ b/room/room-compiler-processing-testing/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
@@ -14,4 +14,4 @@
 # limitations under the License.
 #
 
-androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar
\ No newline at end of file
+androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar$K1Registrar
\ No newline at end of file
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
index 600845d..321cc80 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/TestRunnerTest.kt
@@ -25,9 +25,9 @@
 import androidx.room.compiler.processing.XProcessingStep
 import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor
 import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
+import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
 import androidx.room.compiler.processing.util.compiler.compile
-import androidx.room.compiler.processing.util.compiler.steps.KaptCompilationStep
 import com.google.common.truth.Truth.assertThat
 import com.google.devtools.ksp.processing.SymbolProcessor
 import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
@@ -318,7 +318,10 @@
             )
         runProcessorTest(
             sources = listOf(src),
-            kotlincArguments = listOf("-Werror"),
+            // TODO(kuanyingchou): Remove the "1.9" args when we move to KAPT4. Our processor
+            //  doesn't get to run with KAPT3 and K2 as we pass "-Werror" and we got warning:
+            //  "Kapt currently doesn't support language version 2.0+. Falling back to 1.9."
+            kotlincArguments = listOf("-Werror", "-language-version=1.9", "-api-version=1.9"),
             javacArguments = listOf("-Werror") // needed for kapt as it uses javac,
         ) { invocation ->
             invocation.processingEnv.messager.printMessage(Diagnostic.Kind.WARNING, "some warning")
@@ -364,47 +367,46 @@
 
     @Test
     fun testPluginOptions() {
-        KaptCompilationStep.getPluginOptions(
+        KotlinCliRunner.getPluginOptions(
                 "org.jetbrains.kotlin.kapt3",
                 listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true")
             )
             .let { options -> assertThat(options).containsExactly("correctErrorTypes", "true") }
 
         // zero args
-        KaptCompilationStep.getPluginOptions("org.jetbrains.kotlin.kapt3", emptyList()).let {
-            options ->
+        KotlinCliRunner.getPluginOptions("org.jetbrains.kotlin.kapt3", emptyList()).let { options ->
             assertThat(options).isEmpty()
         }
 
         // odd number of args
-        KaptCompilationStep.getPluginOptions(
+        KotlinCliRunner.getPluginOptions(
                 "org.jetbrains.kotlin.kapt3",
                 listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true", "-verbose")
             )
             .let { options -> assertThat(options).containsExactly("correctErrorTypes", "true") }
 
         // illegal format (missing "=")
-        KaptCompilationStep.getPluginOptions(
+        KotlinCliRunner.getPluginOptions(
                 "org.jetbrains.kotlin.kapt3",
                 listOf("-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypestrue")
             )
             .let { options -> assertThat(options).isEmpty() }
 
         // illegal format (missing "-P")
-        KaptCompilationStep.getPluginOptions(
+        KotlinCliRunner.getPluginOptions(
                 "org.jetbrains.kotlin.kapt3",
                 listOf("plugin:org.jetbrains.kotlin.kapt3:correctErrorTypestrue")
             )
             .let { options -> assertThat(options).isEmpty() }
 
         // illegal format (wrong plugin id)
-        KaptCompilationStep.getPluginOptions(
+        KotlinCliRunner.getPluginOptions(
                 "org.jetbrains.kotlin.kapt3",
                 listOf("-P", "plugin:abc:correctErrorTypes=true")
             )
             .let { options -> assertThat(options).isEmpty() }
 
-        KaptCompilationStep.getPluginOptions(
+        KotlinCliRunner.getPluginOptions(
                 "org.jetbrains.kotlin.kapt3",
                 listOf(
                     "-P",
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index 2d110d8..1890458 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -71,7 +71,7 @@
 }
 
 dependencies {
-    api(libs.kotlinStdlib)
+    api(libs.kotlinStdlibJdk8)
     api(libs.javapoet)
     api(libs.kotlinPoet)
     api(libs.kotlinPoetJavaPoet)
@@ -82,14 +82,12 @@
         exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
     }
     implementation(libs.kspApi)
-    implementation(libs.kotlinStdlibJdk8) // KSP defines older version as dependency, force update.
 
     testImplementation("androidx.annotation:annotation:1.8.1")
     testImplementation(libs.googleCompileTesting)
     testImplementation(libs.junit)
     testImplementation(libs.jsr250)
-    testImplementation(libs.ksp)
-    testImplementation(libs.kotlinMetadataJvm)
+    testImplementation(libs.kotlinMetadataJvm) // Due to being shadowed in main dependency
     testImplementation(libs.testParameterInjector)
     testImplementation(project(":room:room-compiler-processing-testing"))
     testImplementation(project(":internal-testutils-common"))
@@ -109,6 +107,9 @@
 tasks.withType(Test).configureEach { test ->
     test.maxParallelForks(2)
     test.systemProperty("androidx.room.compiler.processing.strict", "true")
+    // With the move to K2 and KSP2 the memory usage has increased so we enlarge the heap
+    // to prevent OOM while running all the tests in one go.
+    test.maxHeapSize("8g")
 }
 
 androidx {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
index 5326743..3e57c9c 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing
 
 import androidx.kruth.assertThat
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.getField
@@ -64,7 +65,11 @@
         // sources and javac fails to resolve metadata
         val placeholder = Source.kotlin("MyPlaceholder.kt", "")
         val dependency = compileFiles(listOf(kotlinSource, javaSource))
-        runProcessorTest(sources = listOf(placeholder), classpath = dependency) { invocation ->
+        runProcessorTest(
+            sources = listOf(placeholder),
+            classpath = dependency,
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+        ) { invocation ->
             val kotlinSubject = invocation.processingEnv.requireTypeElement("foo.bar.KotlinSubject")
             assertThat(kotlinSubject.getField("prop").fallbackLocationText)
                 .isEqualTo("prop in foo.bar.KotlinSubject")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
index fe3f93a..0b80372 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/InternalModifierTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.kruth.assertThat
 import androidx.room.compiler.processing.util.CompilationTestCapabilities
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.runKaptTest
@@ -141,6 +142,7 @@
         runKspTest(
             sources = listOf(source),
             classpath = classpath,
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS,
             config = config,
         ) { invocation ->
             kspResult = traverse(invocation.processingEnv)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index 4949386..6cb5d28 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -19,6 +19,7 @@
 import androidx.kruth.assertThat
 import androidx.room.compiler.processing.javac.JavacMethodElement
 import androidx.room.compiler.processing.javac.JavacTypeElement
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -171,7 +172,7 @@
             """
                     .trimIndent()
             )
-        overridesCheck(source)
+        overridesCheck(source, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS)
     }
 
     @Test
@@ -467,7 +468,11 @@
     }
 
     @Suppress("NAME_SHADOWING") // intentional
-    private fun overridesCheck(vararg sources: Source, ignoreInheritedMethods: Boolean = false) {
+    private fun overridesCheck(
+        vararg sources: Source,
+        ignoreInheritedMethods: Boolean = false,
+        kotlincArgs: List<String> = emptyList()
+    ) {
         val (sources: List<Source>, classpath: List<File>) =
             if (preCompiledCode) {
                 emptyList<Source>() to compileFiles(sources.toList())
@@ -483,7 +488,8 @@
             )
         runProcessorTest(
             sources = sources + Source.kotlin("Placeholder.kt", ""),
-            classpath = classpath
+            classpath = classpath,
+            kotlincArguments = kotlincArgs
         ) { invocation ->
             val (target, methods) = invocation.getOverrideTestTargets(ignoreInheritedMethods)
             methods.forEachIndexed { index, method ->
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
index 663815d..6b27602 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
@@ -31,6 +31,7 @@
 import androidx.room.compiler.processing.testcode.OtherAnnotation
 import androidx.room.compiler.processing.testcode.RepeatableJavaAnnotation
 import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -47,7 +48,11 @@
 
 @RunWith(Parameterized::class)
 class XAnnotationBoxTest(private val preCompiled: Boolean) {
-    private fun runTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+    private fun runTest(
+        sources: List<Source>,
+        kotlincArgs: List<String> = emptyList(),
+        handler: (XTestInvocation) -> Unit
+    ) {
         if (preCompiled) {
             val compiled = compileFiles(sources)
             val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -60,9 +65,14 @@
             val newSources =
                 kotlinSources +
                     Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
-            runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+            runProcessorTest(
+                sources = newSources,
+                handler = handler,
+                classpath = compiled,
+                kotlincArguments = kotlincArgs
+            )
         } else {
-            runProcessorTest(sources = sources, handler = handler)
+            runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
         }
     }
 
@@ -212,7 +222,7 @@
             """
                     .trimIndent()
             )
-        runTest(listOf(mySource)) { invocation ->
+        runTest(listOf(mySource), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("Subject")
             element.getAnnotation(MainAnnotation::class)!!.let { annotation ->
                 assertThat(annotation.getAsTypeList("typeList").map { it.asTypeName() })
@@ -420,7 +430,8 @@
             """
                     .trimIndent()
             )
-        runTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
+        runTest(sources = listOf(kotlinSrc, javaSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+            invocation ->
             listOf("KotlinClass", "JavaClass")
                 .map { invocation.processingEnv.requireTypeElement(it) }
                 .forEach { typeElement ->
@@ -631,7 +642,8 @@
             """
                     .trimIndent()
             )
-        runTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
+        runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+            invocation ->
             listOf("JavaSubject", "KotlinSubject")
                 .map(invocation.processingEnv::requireTypeElement)
                 .forEach { subject ->
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index e8f2e14..27be10a 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -32,6 +32,7 @@
 import androidx.room.compiler.processing.testcode.RepeatableJavaAnnotation
 import androidx.room.compiler.processing.testcode.RepeatableKotlinAnnotation
 import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.asJTypeName
@@ -55,7 +56,11 @@
 
 @RunWith(Parameterized::class)
 class XAnnotationTest(private val preCompiled: Boolean) {
-    private fun runTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+    private fun runTest(
+        sources: List<Source>,
+        kotlincArgs: List<String> = emptyList(),
+        handler: (XTestInvocation) -> Unit
+    ) {
         if (preCompiled) {
             val compiled = compileFiles(sources)
             val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -68,9 +73,14 @@
             val newSources =
                 kotlinSources +
                     Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
-            runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+            runProcessorTest(
+                sources = newSources,
+                handler = handler,
+                classpath = compiled,
+                kotlincArguments = kotlincArgs
+            )
         } else {
-            runProcessorTest(sources = sources, handler = handler)
+            runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
         }
     }
 
@@ -158,7 +168,8 @@
             """
                     .trimIndent()
             )
-        runTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
+        runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+            invocation ->
             val typeElement = invocation.processingEnv.requireTypeElement("Foo")
             val annotation =
                 typeElement.getAllAnnotations().single { it.qualifiedName == "MyAnnotation" }
@@ -464,7 +475,7 @@
             """
                     .trimIndent()
             )
-        runTest(listOf(mySource)) { invocation ->
+        runTest(listOf(mySource), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("Subject")
             val annotation = element.requireAnnotation<MainAnnotation>()
 
@@ -936,7 +947,8 @@
             """
                     .trimIndent()
             )
-        runTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
+        runTest(sources = listOf(javaSrc, kotlinSrc), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) {
+            invocation ->
             listOf("JavaSubject", "KotlinSubject")
                 .map(invocation.processingEnv::requireTypeElement)
                 .forEach { subject ->
@@ -1192,6 +1204,7 @@
                             .trimIndent()
                     )
                 ),
+            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("test.Subject")
             val myAnnotation = invocation.processingEnv.requireTypeElement("test.MyAnnotation")
@@ -1324,7 +1337,8 @@
             )
 
         listOf(javaSource, kotlinSource).forEach { source ->
-            runTest(sources = listOf(source)) { invocation ->
+            runTest(sources = listOf(source), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation
+                ->
                 fun XAnnotated.getAllAnnotationTypeElements(): List<XTypeElement> {
                     return getAllAnnotations()
                         .filter {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
index bc1a9a0..fc1bb0a 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationValueTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.kruth.assertThat
 import androidx.room.compiler.codegen.JArrayTypeName
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.asJClassName
@@ -62,6 +63,7 @@
     private fun runTest(
         javaSource: Source.JavaSource,
         kotlinSource: Source.KotlinSource,
+        kotlincArgs: List<String> = emptyList(),
         handler: (XTestInvocation) -> Unit
     ) {
         val sources =
@@ -81,9 +83,14 @@
             val newSources =
                 kotlinSources +
                     Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
-            runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+            runProcessorTest(
+                sources = newSources,
+                handler = handler,
+                classpath = compiled,
+                kotlincArguments = kotlincArgs
+            )
         } else {
-            runProcessorTest(sources = sources, handler = handler)
+            runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
         }
     }
 
@@ -1330,7 +1337,8 @@
                 MyInterface
                 """
                         .trimIndent()
-                ) as Source.KotlinSource
+                ) as Source.KotlinSource,
+            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val classJTypeName =
                 JParameterizedTypeName.get(
@@ -1451,20 +1459,33 @@
                         @MyAnnotation(stringParam = "2") MyInterface
                 """
                         .trimIndent()
-                ) as Source.KotlinSource
+                ) as Source.KotlinSource,
+            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val annotation = getAnnotation(invocation)
             // Compare the AnnotationSpec string ignoring whitespace
             assertThat(annotation.toAnnotationSpec().toString().removeWhiteSpace())
                 .isEqualTo(
-                    """
+                    // TODO(b/314151707): List values are missing in type annotations with K2. File
+                    // a bug!
+                    if (sourceKind == SourceKind.KOTLIN && isTypeAnnotation && isPreCompiled) {
+                        """
+                        @test.MyAnnotation(
+                            stringParam="2",
+                            stringParam2="1"
+                        )
+                        """
+                            .removeWhiteSpace()
+                    } else {
+                        """
                         @test.MyAnnotation(
                             stringParam="2",
                             stringParam2="1",
                             stringArrayParam={"3","5","7"}
                         )
                         """
-                        .removeWhiteSpace()
+                            .removeWhiteSpace()
+                    }
                 )
 
             assertThat(
@@ -1488,7 +1509,15 @@
                         .firstOrNull()
                         ?.value
                 )
-                .isEqualTo("3")
+                .isEqualTo(
+                    // TODO(b/314151707): List values are missing in type annotations with K2. File
+                    // a bug!
+                    if (sourceKind == SourceKind.KOTLIN && isTypeAnnotation && isPreCompiled) {
+                        null
+                    } else {
+                        "3"
+                    }
+                )
         }
     }
 
@@ -1554,7 +1583,8 @@
                 MyInterface
                 """
                         .trimIndent()
-                ) as Source.KotlinSource
+                ) as Source.KotlinSource,
+            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val aJTypeName = JClassName.get("test", "A")
             val aKTypeName = KClassName("test", "A")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
index b736a07..15781b1 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XElementTest.kt
@@ -26,6 +26,7 @@
 import androidx.room.compiler.processing.ksp.KspFileMemberContainer
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticFileMemberContainer
 import androidx.room.compiler.processing.testcode.OtherAnnotation
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.asJClassName
@@ -866,7 +867,8 @@
             """
                         .trimIndent()
                 )
-            )
+            ),
+            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation, precompiled ->
             val enclosingElement =
                 invocation.processingEnv.requireTypeElement("foo.bar.KotlinClass")
@@ -1367,9 +1369,12 @@
 
     private fun runProcessorTestHelper(
         sources: List<Source>,
+        kotlincArgs: List<String> = emptyList(),
         handler: (XTestInvocation, Boolean) -> Unit
     ) {
-        runProcessorTest(sources = sources) { handler(it, false) }
-        runProcessorTest(classpath = compileFiles(sources)) { handler(it, true) }
+        runProcessorTest(sources = sources, kotlincArguments = kotlincArgs) { handler(it, false) }
+        runProcessorTest(classpath = compileFiles(sources), kotlincArguments = kotlincArgs) {
+            handler(it, true)
+        }
     }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 1312ac5..3166c55 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.util.CONTINUATION_JCLASS_NAME
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.UNIT_JCLASS_NAME
 import androidx.room.compiler.processing.util.className
@@ -505,7 +506,8 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(src)) { invocation ->
+        runProcessorTest(sources = listOf(src), kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS) {
+            invocation ->
             val klass = invocation.processingEnv.requireTypeElement("MyDataClass")
             val methodNames = klass.getAllMethods().map { it.jvmName }.toList()
             assertThat(methodNames)
@@ -1346,7 +1348,11 @@
 
         val sources = buildSources("app")
         val classpath = compileFiles(buildSources("lib"))
-        runProcessorTest(sources = sources, classpath = classpath) { invocation ->
+        runProcessorTest(
+            sources = sources,
+            classpath = classpath,
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+        ) { invocation ->
             // we use this to remove the hash added by the compiler for function names that don't
             // have valid JVM names
             // regex: match 7 characters after -
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
index be06f3c..eef3af7 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.codegen.asMutableClassName
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.util.CONTINUATION_JCLASS_NAME
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.UNIT_JCLASS_NAME
 import androidx.room.compiler.processing.util.XTestInvocation
@@ -61,7 +62,8 @@
                     """
                             .trimIndent()
                     )
-                )
+                ),
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             fun checkConstructor(className: String) {
                 val typeElement = invocation.processingEnv.requireTypeElement(className)
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
index 6e65c40..113d34ab 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XMessagerTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing
 
 import androidx.kruth.assertThat
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.runProcessorTest
 import javax.tools.Diagnostic
@@ -193,7 +194,8 @@
                     """
                             .trimIndent()
                     )
-                )
+                ),
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) {
             val fooElement = it.processingEnv.requireTypeElement("test.Foo")
             val fooAnnotations =
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 ce9c840..8544d232 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
@@ -23,6 +23,7 @@
 import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.javac.JavacType
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.asKClassName
@@ -53,7 +54,11 @@
 class XTypeElementTest(
     private val isPreCompiled: Boolean,
 ) {
-    private fun runTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+    private fun runTest(
+        sources: List<Source>,
+        kotlincArgs: List<String> = emptyList(),
+        handler: (XTestInvocation) -> Unit
+    ) {
         if (isPreCompiled) {
             val compiled = compileFiles(sources)
             val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -66,9 +71,14 @@
             val newSources =
                 kotlinSources +
                     Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
-            runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+            runProcessorTest(
+                sources = newSources,
+                handler = handler,
+                classpath = compiled,
+                kotlincArguments = kotlincArgs
+            )
         } else {
-            runProcessorTest(sources = sources, handler = handler)
+            runProcessorTest(sources = sources, handler = handler, kotlincArguments = kotlincArgs)
         }
     }
 
@@ -1348,6 +1358,7 @@
                             .trimIndent()
                     )
                 ),
+            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val appSubject = invocation.processingEnv.requireTypeElement("test.Subject")
             val methodNames = appSubject.getAllMethods().map { it.name }.toList()
@@ -1614,7 +1625,7 @@
             """
                     .trimIndent()
             )
-        runTest(sources = listOf(src)) { invocation ->
+        runTest(sources = listOf(src), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
             val defaultArgsConstructors =
                 invocation.processingEnv
                     .requireTypeElement("DefaultArgs")
@@ -1648,24 +1659,45 @@
                     )
                     .inOrder()
             } else {
-                assertThat(defaultArgsConstructors)
-                    .containsExactly(
-                        "DefaultArgs(int,double,long)",
-                        "DefaultArgs(double)",
-                        "DefaultArgs(int,double)"
-                    )
-                    .inOrder()
+                if (invocation.isKsp) {
+                    assertThat(defaultArgsConstructors)
+                        .containsExactly(
+                            "DefaultArgs(int,double,long)",
+                            "DefaultArgs(double)",
+                            "DefaultArgs(int,double)",
+                        )
+                        .inOrder()
+                } else {
+                    assertThat(defaultArgsConstructors)
+                        .containsExactly(
+                            "DefaultArgs(double)",
+                            "DefaultArgs(int,double)",
+                            "DefaultArgs(int,double,long)",
+                        )
+                        .inOrder()
+                }
                 assertThat(noDefaultArgsConstructors)
                     .containsExactly("NoDefaultArgs(int,double,long)")
                     .inOrder()
-                assertThat(allDefaultArgsConstructors)
-                    .containsExactly(
-                        "AllDefaultArgs(int,double,long)",
-                        "AllDefaultArgs()",
-                        "AllDefaultArgs(int)",
-                        "AllDefaultArgs(int,double)"
-                    )
-                    .inOrder()
+                if (invocation.isKsp) {
+                    assertThat(allDefaultArgsConstructors)
+                        .containsExactly(
+                            "AllDefaultArgs(int,double,long)",
+                            "AllDefaultArgs()",
+                            "AllDefaultArgs(int)",
+                            "AllDefaultArgs(int,double)",
+                        )
+                        .inOrder()
+                } else {
+                    assertThat(allDefaultArgsConstructors)
+                        .containsExactly(
+                            "AllDefaultArgs()",
+                            "AllDefaultArgs(int)",
+                            "AllDefaultArgs(int,double)",
+                            "AllDefaultArgs(int,double,long)",
+                        )
+                        .inOrder()
+                }
             }
 
             val subjects = listOf("DefaultArgs", "NoDefaultArgs", "AllDefaultArgs")
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index c3172f6..840c0e7 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -27,6 +27,7 @@
 import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
 import androidx.room.compiler.processing.ksp.ERROR_KTYPE_NAME
 import androidx.room.compiler.processing.ksp.KspTypeArgumentType
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.asJClassName
@@ -1084,7 +1085,10 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(src, javaSource)) { invocation ->
+        runProcessorTest(
+            sources = listOf(src, javaSource),
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+        ) { invocation ->
             val styleApplier = invocation.processingEnv.requireType("StyleApplier")
             val styleBuilder = invocation.processingEnv.requireType("StyleBuilder")
             assertThat(styleApplier.typeName.dumpToString(5))
@@ -1618,7 +1622,8 @@
                             .trimIndent()
                     )
                 ),
-            createProcessingSteps = { listOf(WildcardProcessingStep()) }
+            createProcessingSteps = { listOf(WildcardProcessingStep()) },
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { result ->
             result.hasError()
             result.hasErrorCount(1)
@@ -2326,7 +2331,8 @@
                     compileFiles(listOf(kotlinSrc, javaSrc))
                 } else {
                     emptyList()
-                }
+                },
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
         ) { invocation ->
             val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass")
             kotlinElm.getMethodByJvmName("kotlinValueClassDirectUsage").apply {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
index db986f6..11f0abf 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
@@ -26,6 +26,7 @@
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
 import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.getDeclaredField
@@ -34,7 +35,6 @@
 import androidx.room.compiler.processing.util.runProcessorTest
 import com.google.auto.common.MoreElements
 import com.google.auto.common.MoreTypes
-import com.google.devtools.ksp.common.impl.KSNameImpl
 import com.google.devtools.ksp.getDeclaredFunctions
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.JavaFile
@@ -562,7 +562,10 @@
 
     @Test
     fun annotationValues() {
-        runProcessorTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
+        runProcessorTest(
+            sources = listOf(kotlinSrc, javaSrc),
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+        ) { invocation ->
             val kotlinClass = invocation.processingEnv.requireTypeElement("KotlinClass")
             val javaClass = invocation.processingEnv.requireTypeElement("JavaClass")
 
@@ -858,7 +861,7 @@
         (this.processingEnv as JavacProcessingEnv).delegate.elementUtils.getTypeElement(fqn)
 
     private fun XTestInvocation.getKspTypeElement(fqn: String) =
-        (this.processingEnv as KspProcessingEnv)
-            .resolver
-            .getClassDeclarationByName(KSNameImpl.getCached(fqn))!!
+        (this.processingEnv as KspProcessingEnv).resolver.let { resolver ->
+            resolver.getClassDeclarationByName(resolver.getKSNameFromString(fqn))
+        }!!
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
index 8aa8de5..f176019 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
@@ -20,6 +20,7 @@
 import androidx.kruth.assertWithMessage
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.javac.JavacProcessingEnv
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -818,11 +819,15 @@
             """
                     .trimIndent()
             )
-        simpleRun(sources = listOf(src)) { env ->
+        simpleRun(sources = listOf(src), kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { env ->
             val subject = env.requireTypeElement("Subject")
             subject.getDeclaredFields().forEach { assertThat(it.getter).isNotNull() }
             subject.getDeclaredMethods().forEach {
-                assertThat(it.isKotlinPropertyMethod()).isTrue()
+                // A private static function was generated for the lambda passed to lazy() with K2
+                // so we filter these out.
+                if (!it.jvmName.contains("$")) {
+                    assertThat(it.isKotlinPropertyMethod()).isTrue()
+                }
             }
         }
     }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
index 6efe0df..d6ea9e1 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
@@ -23,6 +23,7 @@
 import androidx.room.compiler.processing.isField
 import androidx.room.compiler.processing.isMethod
 import androidx.room.compiler.processing.isTypeElement
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -51,7 +52,7 @@
     @Test
     fun descriptor_method_simple() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly("emptyMethod()V")
             }
@@ -82,8 +83,8 @@
 
     @Test
     fun descriptor_field() {
-        fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+        fun checkSources(vararg sources: Source, kotlincArgs: List<String> = emptyList()) {
+            runTest(sources = sources, kotlincArgs = kotlincArgs) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
                         "field1:I",
@@ -108,7 +109,8 @@
                     @Describe List<String> field4;
                 }
                 """
-            )
+            ),
+            kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS
         )
         checkSources(
             Source.kotlin(
@@ -129,7 +131,7 @@
     @Test
     fun descriptor_method_erasured() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsAtLeast(
                         "method1(Landroidx/room/test/Foo;)V",
@@ -204,7 +206,7 @@
     @Test
     fun descriptor_class_erasured() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
                         "method1(Ljava/lang/Object;)Ljava/lang/Object;",
@@ -277,7 +279,7 @@
     @Test
     fun descriptor_method_primitiveParams() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V")
             }
@@ -315,7 +317,7 @@
     @Test
     fun descriptor_method_classParam_javaTypes() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
                         "method1(Ljava/lang/Object;)V",
@@ -363,7 +365,7 @@
     @Test
     fun descriptor_method_classParam_testClass() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
                         "method1(Landroidx/room/test/DataClass;)V",
@@ -408,7 +410,7 @@
     @Test
     fun descriptor_method_classParam_innerTestClass() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
                         "method1(Landroidx/room/test/DataClass\$MemberInnerData;)V",
@@ -467,7 +469,7 @@
     @Test
     fun descriptor_method_arrayParams() {
         fun checkSources(vararg sources: Source) {
-            runTest(sources = sources) { invocation ->
+            runTest(sources = sources, kotlincArgs = KOTLINC_LANGUAGE_1_9_ARGS) { invocation ->
                 assertThat(invocation.annotatedElements().map(this::descriptor))
                     .containsExactly(
                         "method1([Landroidx/room/test/DataClass;)V",
@@ -515,7 +517,11 @@
         )
     }
 
-    private fun runTest(vararg sources: Source, handler: (XTestInvocation) -> Unit) {
+    private fun runTest(
+        vararg sources: Source,
+        kotlincArgs: List<String> = emptyList(),
+        handler: (XTestInvocation) -> Unit
+    ) {
         if (isPreCompiled) {
             val compiled = compileFiles(listOf(*sources) + describeAnnotation)
             val hasKotlinSources = sources.any { it is Source.KotlinSource }
@@ -528,9 +534,18 @@
             val newSources =
                 kotlinSources +
                     Source.java("PlaceholderJava", "public class " + "PlaceholderJava {}")
-            runProcessorTest(sources = newSources, handler = handler, classpath = compiled)
+            runProcessorTest(
+                sources = newSources,
+                handler = handler,
+                classpath = compiled,
+                kotlincArguments = kotlincArgs
+            )
         } else {
-            runProcessorTest(sources = listOf(*sources) + describeAnnotation, handler = handler)
+            runProcessorTest(
+                sources = listOf(*sources) + describeAnnotation,
+                handler = handler,
+                kotlincArguments = kotlincArgs
+            )
         }
     }
 
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
index 3b8cc6f..98645e6 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.processing.XExecutableElement
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.util.CompilationTestCapabilities
+import androidx.room.compiler.processing.util.KOTLINC_LANGUAGE_1_9_ARGS
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -103,7 +104,11 @@
             collectSignaturesInto(invocation, golden)
         }
         val ksp = mutableMapOf<String, List<TypeName>>()
-        runKspTest(sources = sources, classpath = classpath) { invocation ->
+        runKspTest(
+            sources = sources,
+            classpath = classpath,
+            kotlincArguments = KOTLINC_LANGUAGE_1_9_ARGS
+        ) { invocation ->
             collectSignaturesInto(invocation, ksp)
         }
 
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index 136bd39..ff0e1d5 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -87,7 +87,6 @@
     testImplementation(libs.jsr250)
     testImplementation(libs.mockitoCore4)
     testImplementation(libs.antlr4)
-    testImplementation(libs.kotlinCompilerEmbeddable)
     testImplementation(SdkHelperKt.getSdkDependency(project))
     testImplementationAarAsJar(project(":room:room-runtime"))
     testImplementationAarAsJar(project(":sqlite:sqlite"))
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index f57ed83..c73de92 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -222,20 +222,39 @@
 }
 
 object RoomRxJava2TypeNames {
-    val RX_ROOM = XClassName.get(ROOM_PACKAGE, "RxRoom")
-    val RX_ROOM_CREATE_FLOWABLE = "createFlowable"
-    val RX_ROOM_CREATE_OBSERVABLE = "createObservable"
-    val RX_EMPTY_RESULT_SET_EXCEPTION = XClassName.get(ROOM_PACKAGE, "EmptyResultSetException")
+    val RX2_ROOM = XClassName.get(ROOM_PACKAGE, "RxRoom")
+    val RX2_EMPTY_RESULT_SET_EXCEPTION = XClassName.get(ROOM_PACKAGE, "EmptyResultSetException")
+}
+
+object RoomRxJava2MemberNames {
+    val RX_ROOM_CREATE_FLOWABLE =
+        RoomRxJava2TypeNames.RX2_ROOM.companionMember("createFlowable", isJvmStatic = true)
+    val RX_ROOM_CREATE_OBSERVABLE =
+        RoomRxJava2TypeNames.RX2_ROOM.companionMember("createObservable", isJvmStatic = true)
+    val RX_ROOM_CREATE_SINGLE =
+        RoomRxJava2TypeNames.RX2_ROOM.companionMember("createSingle", isJvmStatic = true)
+    val RX_ROOM_CREATE_MAYBE =
+        RoomRxJava2TypeNames.RX2_ROOM.companionMember("createMaybe", isJvmStatic = true)
+    val RX_ROOM_CREATE_COMPLETABLE =
+        RoomRxJava2TypeNames.RX2_ROOM.companionMember("createCompletable", isJvmStatic = true)
 }
 
 object RoomRxJava3TypeNames {
-    val RX_ROOM = XClassName.get("$ROOM_PACKAGE.rxjava3", "RxRoom")
-    val RX_ROOM_CREATE_FLOWABLE = "createFlowable"
-    val RX_ROOM_CREATE_OBSERVABLE = "createObservable"
-    val RX_EMPTY_RESULT_SET_EXCEPTION =
+    val RX3_ROOM = XClassName.get("$ROOM_PACKAGE.rxjava3", "RxRoom")
+    val RX3_ROOM_MARKER = XClassName.get("$ROOM_PACKAGE.rxjava3", "Rx3RoomArtifactMarker")
+    val RX3_EMPTY_RESULT_SET_EXCEPTION =
         XClassName.get("$ROOM_PACKAGE.rxjava3", "EmptyResultSetException")
 }
 
+object RoomRxJava3MemberNames {
+    val RX_ROOM_CREATE_FLOWABLE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createFlowable")
+    val RX_ROOM_CREATE_OBSERVABLE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createObservable")
+    val RX_ROOM_CREATE_SINGLE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createSingle")
+    val RX_ROOM_CREATE_MAYBE = RoomRxJava3TypeNames.RX3_ROOM.packageMember("createMaybe")
+    val RX_ROOM_CREATE_COMPLETABLE =
+        RoomRxJava3TypeNames.RX3_ROOM.packageMember("createCompletable")
+}
+
 object RoomPagingTypeNames {
     val LIMIT_OFFSET_PAGING_SOURCE =
         XClassName.get("$ROOM_PACKAGE.paging", "LimitOffsetPagingSource")
@@ -391,29 +410,56 @@
         .build()
 
 /**
+ * Short-hand of [InvokeWithLambdaParameter] whose function call is a member function, i.e. a
+ * top-level function or a companion object function.
+ */
+fun InvokeWithLambdaParameter(
+    scope: CodeGenScope,
+    functionName: XMemberName,
+    argFormat: List<String>,
+    args: List<Any>,
+    continuationParamName: String? = null,
+    lambdaSpec: LambdaSpec
+): XCodeBlock {
+    val functionCall = XCodeBlock.of(scope.language, "%M", functionName)
+    return InvokeWithLambdaParameter(
+        scope,
+        functionCall,
+        argFormat,
+        args,
+        continuationParamName,
+        lambdaSpec
+    )
+}
+
+/**
  * Generates a code block that invokes a function with a functional type as last parameter.
  *
  * For Java (jvmTarget >= 8) it will generate:
  * ```
- * <functionName>(<args>, (<lambdaSpec.paramName>) -> <lambdaSpec.body>);
+ * <functionCall>(<args>, (<lambdaSpec.paramName>) -> <lambdaSpec.body>);
  * ```
  *
  * For Java (jvmTarget < 8) it will generate:
  * ```
- * <functionName>(<args>, new Function1<>() { <lambdaSpec.body> });
+ * <functionCall>(<args>, new Function1<>() { <lambdaSpec.body> });
  * ```
  *
  * For Kotlin it will generate:
  * ```
- * <functionName>(<args>) { <lambdaSpec.body> }
+ * <functionCall>(<args>) { <lambdaSpec.body> }
  * ```
  *
+ * The [functionCall] must only be an expression up to a function name without the parenthesis. Its
+ * last parameter must also be a functional type. The [argFormat] and [args] are for the arguments
+ * of the function excluding the functional parameter.
+ *
  * The ideal usage of this utility function is to generate code that invokes the various
  * `DBUtil.perform*()` APIs for interacting with the database connection in DAOs.
  */
 fun InvokeWithLambdaParameter(
     scope: CodeGenScope,
-    functionName: XMemberName,
+    functionCall: XCodeBlock,
     argFormat: List<String>,
     args: List<Any>,
     continuationParamName: String? = null,
@@ -427,8 +473,8 @@
                     if (lambdaSpec.javaLambdaSyntaxAvailable) {
                         val argsFormatString = argFormat.joinToString(separator = ", ")
                         add(
-                            "%M($argsFormatString, (%L) -> {\n",
-                            functionName,
+                            "%L($argsFormatString, (%L) -> {\n",
+                            functionCall,
                             *args.toTypedArray(),
                             lambdaSpec.parameterName
                         )
@@ -472,8 +518,8 @@
                             }
                         }
                         add(
-                            "%M($adjustedArgsFormatString);\n",
-                            functionName,
+                            "%L($adjustedArgsFormatString);\n",
+                            functionCall,
                             *adjustedArgs.toTypedArray(),
                         )
                     }
@@ -482,15 +528,15 @@
                     val argsFormatString = argFormat.joinToString(separator = ", ")
                     if (lambdaSpec.parameterTypeName.rawTypeName != KotlinTypeNames.CONTINUATION) {
                         add(
-                            "%M($argsFormatString) { %L ->\n",
-                            functionName,
+                            "%L($argsFormatString) { %L ->\n",
+                            functionCall,
                             *args.toTypedArray(),
                             lambdaSpec.parameterName
                         )
                     } else {
                         add(
-                            "%M($argsFormatString) {\n",
-                            functionName,
+                            "%L($argsFormatString) {\n",
+                            functionCall,
                             *args.toTypedArray(),
                         )
                     }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
index 8d375ac..1f2433c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
@@ -20,8 +20,6 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XProcessingEnv
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CommonTypeNames
 import androidx.room.log.RLog
 import androidx.room.parser.expansion.ProjectionExpander
 import androidx.room.parser.optimization.RemoveUnusedColumnQueryRewriter
@@ -42,7 +40,6 @@
     private val canRewriteQueriesToDropUnusedColumns: Boolean,
 ) {
     val checker: Checks = Checks(logger)
-    val COMMON_TYPES = CommonTypes(processingEnv)
 
     /**
      * Checks whether we should use the TypeConverter store that has a specific heuristic for
@@ -131,16 +128,6 @@
         canRewriteQueriesToDropUnusedColumns = false
     )
 
-    class CommonTypes(val processingEnv: XProcessingEnv) {
-        val VOID: XType by lazy { processingEnv.requireType(CommonTypeNames.VOID) }
-        val STRING: XType by lazy { processingEnv.requireType(CommonTypeNames.STRING) }
-        val READONLY_COLLECTION: XType by lazy {
-            processingEnv.requireType(CommonTypeNames.COLLECTION)
-        }
-        val LIST: XType by lazy { processingEnv.requireType(CommonTypeNames.LIST) }
-        val SET: XType by lazy { processingEnv.requireType(CommonTypeNames.SET) }
-    }
-
     val schemaInFolderPath by lazy {
         val internalInputFolder =
             processingEnv.options[ProcessorOptions.INTERNAL_SCHEMA_INPUT_FOLDER.argName]
@@ -323,4 +310,10 @@
             targetPlatforms.contains(XProcessingEnv.Platform.JVM) &&
             this.processingEnv.findType("android.content.Context") != null
     }
+
+    /** Check if the target platform is JVM. */
+    fun isJvmOnlyTarget(): Boolean {
+        val targetPlatforms = this.processingEnv.targetPlatforms
+        return targetPlatforms.size == 1 && targetPlatforms.contains(XProcessingEnv.Platform.JVM)
+    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index 7f1f632..b49a6c2 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -546,8 +546,11 @@
     private fun processConstructorObject(element: XTypeElement): XTypeElement? {
         val annotation = element.getAnnotation(androidx.room.ConstructedBy::class)
         if (annotation == null) {
+            // If no @ConstructedBy is present then validate target is JVM (including Android)
+            // since reflection is available in those platforms and a database constructor is not
+            // needed.
             context.checker.check(
-                predicate = context.isAndroidOnlyTarget(),
+                predicate = context.isJvmOnlyTarget(),
                 element = element,
                 errorMsg = ProcessorErrors.MISSING_CONSTRUCTED_BY_ANNOTATION
             )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt
index 762028e..b6b1fd6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/RxTypes.kt
@@ -17,7 +17,10 @@
 package androidx.room.solver
 
 import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XMemberName
+import androidx.room.ext.RoomRxJava2MemberNames
 import androidx.room.ext.RoomRxJava2TypeNames
+import androidx.room.ext.RoomRxJava3MemberNames
 import androidx.room.ext.RoomRxJava3TypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
@@ -26,54 +29,80 @@
 internal enum class RxType(
     val version: RxVersion,
     val className: XClassName,
-    val factoryMethodName: String? = null,
+    val factoryMethodName: XMemberName,
     val canBeNull: Boolean = false
 ) {
     // RxJava2 types
     RX2_FLOWABLE(
         version = RxVersion.TWO,
         className = RxJava2TypeNames.FLOWABLE,
-        factoryMethodName = RoomRxJava2TypeNames.RX_ROOM_CREATE_FLOWABLE
+        factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_FLOWABLE
     ),
     RX2_OBSERVABLE(
         version = RxVersion.TWO,
         className = RxJava2TypeNames.OBSERVABLE,
-        factoryMethodName = RoomRxJava2TypeNames.RX_ROOM_CREATE_OBSERVABLE
+        factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_OBSERVABLE
     ),
-    RX2_SINGLE(version = RxVersion.TWO, className = RxJava2TypeNames.SINGLE),
-    RX2_MAYBE(version = RxVersion.TWO, className = RxJava2TypeNames.MAYBE, canBeNull = true),
-    RX2_COMPLETABLE(version = RxVersion.TWO, className = RxJava2TypeNames.COMPLETABLE),
+    RX2_SINGLE(
+        version = RxVersion.TWO,
+        className = RxJava2TypeNames.SINGLE,
+        factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_SINGLE
+    ),
+    RX2_MAYBE(
+        version = RxVersion.TWO,
+        className = RxJava2TypeNames.MAYBE,
+        factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_MAYBE,
+        canBeNull = true
+    ),
+    RX2_COMPLETABLE(
+        version = RxVersion.TWO,
+        className = RxJava2TypeNames.COMPLETABLE,
+        factoryMethodName = RoomRxJava2MemberNames.RX_ROOM_CREATE_COMPLETABLE
+    ),
     // RxJava3 types
     RX3_FLOWABLE(
         version = RxVersion.THREE,
         className = RxJava3TypeNames.FLOWABLE,
-        factoryMethodName = RoomRxJava3TypeNames.RX_ROOM_CREATE_FLOWABLE
+        factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_FLOWABLE
     ),
     RX3_OBSERVABLE(
         version = RxVersion.THREE,
         className = RxJava3TypeNames.OBSERVABLE,
-        factoryMethodName = RoomRxJava3TypeNames.RX_ROOM_CREATE_OBSERVABLE
+        factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_OBSERVABLE
     ),
-    RX3_SINGLE(version = RxVersion.THREE, className = RxJava3TypeNames.SINGLE),
-    RX3_MAYBE(version = RxVersion.THREE, className = RxJava3TypeNames.MAYBE, canBeNull = true),
-    RX3_COMPLETABLE(version = RxVersion.THREE, className = RxJava3TypeNames.COMPLETABLE);
+    RX3_SINGLE(
+        version = RxVersion.THREE,
+        className = RxJava3TypeNames.SINGLE,
+        factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_SINGLE
+    ),
+    RX3_MAYBE(
+        version = RxVersion.THREE,
+        className = RxJava3TypeNames.MAYBE,
+        factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_MAYBE,
+        canBeNull = true
+    ),
+    RX3_COMPLETABLE(
+        version = RxVersion.THREE,
+        className = RxJava3TypeNames.COMPLETABLE,
+        factoryMethodName = RoomRxJava3MemberNames.RX_ROOM_CREATE_COMPLETABLE
+    );
 
     fun isSingle() = this == RX2_SINGLE || this == RX3_SINGLE
 }
 
 internal enum class RxVersion(
-    val rxRoomClassName: XClassName,
+    val rxMarkerClassName: XClassName,
     val emptyResultExceptionClassName: XClassName,
     val missingArtifactMessage: String
 ) {
     TWO(
-        rxRoomClassName = RoomRxJava2TypeNames.RX_ROOM,
-        emptyResultExceptionClassName = RoomRxJava2TypeNames.RX_EMPTY_RESULT_SET_EXCEPTION,
+        rxMarkerClassName = RoomRxJava2TypeNames.RX2_ROOM,
+        emptyResultExceptionClassName = RoomRxJava2TypeNames.RX2_EMPTY_RESULT_SET_EXCEPTION,
         missingArtifactMessage = ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT
     ),
     THREE(
-        rxRoomClassName = RoomRxJava3TypeNames.RX_ROOM,
-        emptyResultExceptionClassName = RoomRxJava3TypeNames.RX_EMPTY_RESULT_SET_EXCEPTION,
+        rxMarkerClassName = RoomRxJava3TypeNames.RX3_ROOM_MARKER,
+        emptyResultExceptionClassName = RoomRxJava3TypeNames.RX3_EMPTY_RESULT_SET_EXCEPTION,
         missingArtifactMessage = ProcessorErrors.MISSING_ROOM_RXJAVA3_ARTIFACT
     )
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 4523c50..b6d986f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -54,9 +54,9 @@
 import androidx.room.solver.binderprovider.ListenableFuturePagingSourceQueryResultBinderProvider
 import androidx.room.solver.binderprovider.LiveDataQueryResultBinderProvider
 import androidx.room.solver.binderprovider.PagingSourceQueryResultBinderProvider
-import androidx.room.solver.binderprovider.RxCallableQueryResultBinderProvider
 import androidx.room.solver.binderprovider.RxJava2PagingSourceQueryResultBinderProvider
 import androidx.room.solver.binderprovider.RxJava3PagingSourceQueryResultBinderProvider
+import androidx.room.solver.binderprovider.RxLambdaQueryResultBinderProvider
 import androidx.room.solver.binderprovider.RxQueryResultBinderProvider
 import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
 import androidx.room.solver.prepared.binderprovider.GuavaListenableFuturePreparedQueryResultBinderProvider
@@ -208,7 +208,7 @@
             add(LiveDataQueryResultBinderProvider(context))
             add(GuavaListenableFutureQueryResultBinderProvider(context))
             addAll(RxQueryResultBinderProvider.getAll(context))
-            addAll(RxCallableQueryResultBinderProvider.getAll(context))
+            addAll(RxLambdaQueryResultBinderProvider.getAll(context))
             add(DataSourceQueryResultBinderProvider(context))
             add(DataSourceFactoryQueryResultBinderProvider(context))
             add(RxJava2PagingSourceQueryResultBinderProvider(context))
@@ -795,12 +795,13 @@
         mapInfo: MapInfo?,
         mapValueTypeArg: XType
     ): MapValueResultAdapter? {
-        val collectionTypeRaw = context.COMMON_TYPES.READONLY_COLLECTION.rawType
+        val collectionTypeRaw =
+            context.processingEnv.requireType(CommonTypeNames.COLLECTION).rawType
         if (collectionTypeRaw.isAssignableFrom(mapValueTypeArg.rawType)) {
             // The Map's value type argument is assignable to a Collection, we need to make
             // sure it is either a list or a set.
-            val listTypeRaw = context.COMMON_TYPES.LIST.rawType
-            val setTypeRaw = context.COMMON_TYPES.SET.rawType
+            val listTypeRaw = context.processingEnv.requireType(CommonTypeNames.LIST).rawType
+            val setTypeRaw = context.processingEnv.requireType(CommonTypeNames.SET).rawType
             val collectionValueType =
                 when {
                     mapValueTypeArg.rawType.isAssignableFrom(listTypeRaw) ->
@@ -1021,7 +1022,8 @@
         typeMirror: XType,
         isMultipleParameter: Boolean
     ): QueryParameterAdapter? {
-        if (context.COMMON_TYPES.READONLY_COLLECTION.rawType.isAssignableFrom(typeMirror)) {
+        val collectionType = context.processingEnv.requireType(CommonTypeNames.COLLECTION)
+        if (collectionType.rawType.isAssignableFrom(typeMirror)) {
             val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
             // An adapter for the collection type arg wrapped in the built-in collection adapter.
             val wrappedCollectionAdapter =
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
index 3d1c20f..edd1307 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
@@ -20,6 +20,7 @@
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.ParsedQuery
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors
@@ -64,7 +65,8 @@
     }
 
     override fun matches(declared: XType): Boolean {
-        val collectionTypeRaw = context.COMMON_TYPES.READONLY_COLLECTION.rawType
+        val collectionTypeRaw =
+            context.processingEnv.requireType(CommonTypeNames.COLLECTION).rawType
 
         if (pagingSourceType == null) {
             return false
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxLambdaQueryResultBinderProvider.kt
similarity index 89%
rename from room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxLambdaQueryResultBinderProvider.kt
index 129241d..701cf87 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxLambdaQueryResultBinderProvider.kt
@@ -24,9 +24,9 @@
 import androidx.room.solver.RxType
 import androidx.room.solver.TypeAdapterExtras
 import androidx.room.solver.query.result.QueryResultBinder
-import androidx.room.solver.query.result.RxCallableQueryResultBinder
+import androidx.room.solver.query.result.RxLambdaQueryResultBinder
 
-class RxCallableQueryResultBinderProvider
+class RxLambdaQueryResultBinderProvider
 private constructor(val context: Context, private val rxType: RxType) : QueryResultBinderProvider {
     override fun provide(
         declared: XType,
@@ -40,7 +40,7 @@
         )
         val typeArg = extractTypeArg(declared)
         val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query, extras)
-        return RxCallableQueryResultBinder(rxType, typeArg, adapter)
+        return RxLambdaQueryResultBinder(rxType, typeArg, adapter)
     }
 
     override fun matches(declared: XType): Boolean =
@@ -62,10 +62,10 @@
     companion object {
         fun getAll(context: Context) =
             listOf(RxType.RX2_SINGLE, RxType.RX2_MAYBE, RxType.RX3_SINGLE, RxType.RX3_MAYBE).map {
-                RxCallableQueryResultBinderProvider(context, it)
+                RxLambdaQueryResultBinderProvider(context, it)
                     .requireArtifact(
                         context = context,
-                        requiredType = it.version.rxRoomClassName,
+                        requiredType = it.version.rxMarkerClassName,
                         missingArtifactErrorMsg = it.version.missingArtifactMessage
                     )
             }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt
index a592faaf..aad54a6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxQueryResultBinderProvider.kt
@@ -25,6 +25,7 @@
 import androidx.room.solver.query.result.QueryResultBinder
 import androidx.room.solver.query.result.RxQueryResultBinder
 
+/** Generic result binder for Rx classes that are reactive. */
 class RxQueryResultBinderProvider
 private constructor(context: Context, private val rxType: RxType) :
     ObservableQueryResultBinderProvider(context) {
@@ -69,7 +70,7 @@
                     RxQueryResultBinderProvider(context, it)
                         .requireArtifact(
                             context = context,
-                            requiredType = it.version.rxRoomClassName,
+                            requiredType = it.version.rxMarkerClassName,
                             missingArtifactErrorMsg = it.version.missingArtifactMessage
                         )
                 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CallablePreparedQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CallablePreparedQueryResultBinder.kt
deleted file mode 100644
index 622bb16..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binder/CallablePreparedQueryResultBinder.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.solver.prepared.binder
-
-import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CallableTypeSpecBuilder
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.prepared.result.PreparedQueryResultAdapter
-
-/**
- * Binder for deferred queries.
- *
- * This binder will create a Callable implementation that delegates to the
- * [PreparedQueryResultAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
- * function.
- */
-class CallablePreparedQueryResultBinder
-private constructor(
-    val returnType: XType,
-    val addStmntBlock:
-        XCodeBlock.Builder.(callableImpl: XTypeSpec, dbProperty: XPropertySpec) -> Unit,
-    adapter: PreparedQueryResultAdapter?
-) : PreparedQueryResultBinder(adapter) {
-
-    companion object {
-        fun createPreparedBinder(
-            returnType: XType,
-            adapter: PreparedQueryResultAdapter?,
-            addCodeBlock:
-                XCodeBlock.Builder.(callableImpl: XTypeSpec, dbProperty: XPropertySpec) -> Unit
-        ) = CallablePreparedQueryResultBinder(returnType, addCodeBlock, adapter)
-    }
-
-    override fun executeAndReturn(
-        prepareQueryStmtBlock: CodeGenScope.() -> String,
-        preparedStmtProperty: XPropertySpec?,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        val binderScope = scope.fork()
-        val callableImpl =
-            CallableTypeSpecBuilder(scope.language, returnType.asTypeName()) {
-                    adapter?.executeAndReturn(
-                        binderScope.prepareQueryStmtBlock(),
-                        preparedStmtProperty,
-                        dbProperty,
-                        binderScope
-                    )
-                    addCode(binderScope.generate())
-                }
-                .build()
-
-        scope.builder.apply { addStmntBlock(callableImpl, dbProperty) }
-    }
-}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt
index 5b5a248..d7153fc 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/RxPreparedQueryResultBinderProvider.kt
@@ -18,10 +18,11 @@
 
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.KotlinTypeNames
 import androidx.room.parser.ParsedQuery
 import androidx.room.processor.Context
 import androidx.room.solver.RxType
-import androidx.room.solver.prepared.binder.CallablePreparedQueryResultBinder.Companion.createPreparedBinder
+import androidx.room.solver.prepared.binder.LambdaPreparedQueryResultBinder
 import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
 
 open class RxPreparedQueryResultBinderProvider
@@ -29,7 +30,8 @@
     PreparedQueryResultBinderProvider {
 
     private val hasRxJavaArtifact by lazy {
-        context.processingEnv.findTypeElement(rxType.version.rxRoomClassName.canonicalName) != null
+        context.processingEnv.findTypeElement(rxType.version.rxMarkerClassName.canonicalName) !=
+            null
     }
 
     override fun matches(declared: XType): Boolean =
@@ -44,12 +46,11 @@
             context.logger.e(rxType.version.missingArtifactMessage)
         }
         val typeArg = extractTypeArg(declared)
-        return createPreparedBinder(
+        return LambdaPreparedQueryResultBinder(
             returnType = typeArg,
+            functionName = rxType.factoryMethodName,
             adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(typeArg, query)
-        ) { callableImpl, _ ->
-            addStatement("return %T.fromCallable(%L)", rxType.className, callableImpl)
-        }
+        )
     }
 
     open fun extractTypeArg(declared: XType): XType = declared.typeArguments.first()
@@ -82,16 +83,17 @@
     }
 
     /**
-     * Since Completable is not a generic, the supported return type should be Void (nullable). Like
-     * this, the generated Callable.call method will return Void.
+     * Since Completable has no type argument, the supported return type is Unit (non-nullable)
+     * since the 'createCompletable" factory method take a Kotlin lambda.
      */
-    override fun extractTypeArg(declared: XType): XType = context.COMMON_TYPES.VOID.makeNullable()
+    override fun extractTypeArg(declared: XType): XType =
+        context.processingEnv.requireType(KotlinTypeNames.UNIT)
 }
 
 private class RxSingleOrMaybePreparedQueryResultBinderProvider(context: Context, rxType: RxType) :
     RxPreparedQueryResultBinderProvider(context, rxType) {
 
-    /** Since Maybe can have null values, the Callable returned must allow for null values. */
+    /** Since Maybe can have null values, the lambda returned must allow for null values. */
     override fun extractTypeArg(declared: XType): XType =
         declared.typeArguments.first().makeNullable()
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
index 06d49c8..e4898ee 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -18,11 +18,16 @@
 
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.ArrayLiteral
 import androidx.room.ext.CallableTypeSpecBuilder
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
 
 /** Converts the query into a LiveData and returns it. No query is run until necessary. */
@@ -31,7 +36,6 @@
     val tableNames: Set<String>,
     adapter: QueryResultAdapter?
 ) : BaseObservableQueryResultBinder(adapter) {
-    @Suppress("JoinDeclarationAndAssignment")
     override fun convertAndReturn(
         roomSQLiteQueryVar: String,
         canReleaseQuery: Boolean,
@@ -83,4 +87,68 @@
             )
         }
     }
+
+    override fun isMigratedToDriver() = adapter?.isMigratedToDriver() == true
+
+    override fun convertAndReturn(
+        sqlQueryVar: String,
+        dbProperty: XPropertySpec,
+        bindStatement: CodeGenScope.(String) -> Unit,
+        returnTypeName: XTypeName,
+        inTransaction: Boolean,
+        scope: CodeGenScope
+    ) {
+        val arrayOfTableNamesLiteral =
+            ArrayLiteral(scope.language, CommonTypeNames.STRING, *tableNames.toTypedArray())
+        val connectionVar = scope.getTmpVar("_connection")
+        val createBlock =
+            InvokeWithLambdaParameter(
+                scope = scope,
+                functionCall =
+                    XCodeBlock.of(
+                        scope.language,
+                        "%N.%L.createLiveData",
+                        dbProperty,
+                        when (scope.language) {
+                            CodeLanguage.JAVA -> "getInvalidationTracker()"
+                            CodeLanguage.KOTLIN -> "invalidationTracker"
+                        }
+                    ),
+                argFormat = listOf("%L", "%L"),
+                args = listOf(arrayOfTableNamesLiteral, inTransaction),
+                lambdaSpec =
+                    object :
+                        LambdaSpec(
+                            parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                            parameterName = connectionVar,
+                            returnTypeName = typeArg.asTypeName(),
+                            javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+                        ) {
+                        override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                            val returnPrefix =
+                                when (language) {
+                                    CodeLanguage.JAVA -> "return "
+                                    CodeLanguage.KOTLIN -> ""
+                                }
+                            val statementVar = scope.getTmpVar("_stmt")
+                            addLocalVal(
+                                statementVar,
+                                SQLiteDriverTypeNames.STATEMENT,
+                                "%L.prepare(%L)",
+                                connectionVar,
+                                sqlQueryVar
+                            )
+                            beginControlFlow("try")
+                            bindStatement(scope, statementVar)
+                            val outVar = scope.getTmpVar("_result")
+                            adapter?.convert(outVar, statementVar, scope)
+                            addStatement("$returnPrefix%L", outVar)
+                            nextControlFlow("finally")
+                            addStatement("%L.close()", statementVar)
+                            endControlFlow()
+                        }
+                    }
+            )
+        scope.builder.add("return %L", createBlock)
+    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
similarity index 68%
rename from room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
rename to room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
index a16c91f..10973f37 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxLambdaQueryResultBinder.kt
@@ -19,20 +19,25 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.VisibilityModifier
 import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XFunSpec
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.AndroidTypeNames.CURSOR
 import androidx.room.ext.CallableTypeSpecBuilder
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
 import androidx.room.ext.RoomMemberNames.DB_UTIL_QUERY
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.RxType
 
-/** Generic Result binder for Rx classes that accept a callable. */
-internal class RxCallableQueryResultBinder(
+/** Generic result binder for Rx classes that are not reactive. */
+internal class RxLambdaQueryResultBinder(
     private val rxType: RxType,
     val typeArg: XType,
     adapter: QueryResultAdapter?
@@ -68,7 +73,7 @@
                 .build()
         scope.builder.apply {
             if (rxType.isSingle()) {
-                addStatement("return %T.createSingle(%L)", rxType.version.rxRoomClassName, callable)
+                addStatement("return %M(%L)", rxType.factoryMethodName, callable)
             } else {
                 addStatement("return %T.fromCallable(%L)", rxType.className, callable)
             }
@@ -160,4 +165,57 @@
                 .build()
         )
     }
+
+    override fun isMigratedToDriver() = adapter?.isMigratedToDriver() == true
+
+    override fun convertAndReturn(
+        sqlQueryVar: String,
+        dbProperty: XPropertySpec,
+        bindStatement: CodeGenScope.(String) -> Unit,
+        returnTypeName: XTypeName,
+        inTransaction: Boolean,
+        scope: CodeGenScope
+    ) {
+        val connectionVar = scope.getTmpVar("_connection")
+        val performBlock =
+            InvokeWithLambdaParameter(
+                scope = scope,
+                functionName = rxType.factoryMethodName,
+                argFormat = listOf("%N", "%L", "%L"),
+                args = listOf(dbProperty, /* isReadOnly= */ true, inTransaction),
+                lambdaSpec =
+                    object :
+                        LambdaSpec(
+                            parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                            parameterName = connectionVar,
+                            returnTypeName = typeArg.asTypeName(),
+                            javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+                        ) {
+                        override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                            val returnPrefix =
+                                when (language) {
+                                    CodeLanguage.JAVA -> "return "
+                                    CodeLanguage.KOTLIN -> ""
+                                }
+                            val statementVar = scope.getTmpVar("_stmt")
+                            addLocalVal(
+                                statementVar,
+                                SQLiteDriverTypeNames.STATEMENT,
+                                "%L.prepare(%L)",
+                                connectionVar,
+                                sqlQueryVar
+                            )
+                            beginControlFlow("try")
+                            bindStatement(scope, statementVar)
+                            val outVar = scope.getTmpVar("_result")
+                            adapter?.convert(outVar, statementVar, scope)
+                            addStatement("$returnPrefix%L", outVar)
+                            nextControlFlow("finally")
+                            addStatement("%L.close()", statementVar)
+                            endControlFlow()
+                        }
+                    }
+            )
+        scope.builder.add("return %L", performBlock)
+    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
index 838c502..bc8877e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
@@ -16,12 +16,18 @@
 
 package androidx.room.solver.query.result
 
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.ArrayLiteral
 import androidx.room.ext.CallableTypeSpecBuilder
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.InvokeWithLambdaParameter
+import androidx.room.ext.LambdaSpec
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.RxType
 
@@ -69,9 +75,8 @@
                     *queryTableNames.toTypedArray()
                 )
             addStatement(
-                "return %T.%N(%N, %L, %L, %L)",
-                rxType.version.rxRoomClassName,
-                rxType.factoryMethodName!!,
+                "return %M(%N, %L, %L, %L)",
+                rxType.factoryMethodName,
                 dbProperty,
                 if (inTransaction) "true" else "false",
                 arrayOfTableNamesLiteral,
@@ -79,4 +84,66 @@
             )
         }
     }
+
+    override fun isMigratedToDriver() = adapter?.isMigratedToDriver() ?: false
+
+    override fun convertAndReturn(
+        sqlQueryVar: String,
+        dbProperty: XPropertySpec,
+        bindStatement: CodeGenScope.(String) -> Unit,
+        returnTypeName: XTypeName,
+        inTransaction: Boolean,
+        scope: CodeGenScope
+    ) {
+        val connectionVar = scope.getTmpVar("_connection")
+        val performBlock =
+            InvokeWithLambdaParameter(
+                scope = scope,
+                functionName = rxType.factoryMethodName,
+                argFormat = listOf("%N", "%L", "%L"),
+                args =
+                    listOf(
+                        dbProperty,
+                        inTransaction,
+                        ArrayLiteral(
+                            scope.language,
+                            CommonTypeNames.STRING,
+                            *queryTableNames.toTypedArray()
+                        )
+                    ),
+                lambdaSpec =
+                    object :
+                        LambdaSpec(
+                            parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                            parameterName = connectionVar,
+                            returnTypeName = typeArg.asTypeName(),
+                            javaLambdaSyntaxAvailable = scope.javaLambdaSyntaxAvailable
+                        ) {
+                        override fun XCodeBlock.Builder.body(scope: CodeGenScope) {
+                            val returnPrefix =
+                                when (language) {
+                                    CodeLanguage.JAVA -> "return "
+                                    CodeLanguage.KOTLIN -> ""
+                                }
+                            val statementVar = scope.getTmpVar("_stmt")
+                            addLocalVal(
+                                statementVar,
+                                SQLiteDriverTypeNames.STATEMENT,
+                                "%L.prepare(%L)",
+                                connectionVar,
+                                sqlQueryVar
+                            )
+                            beginControlFlow("try")
+                            bindStatement(scope, statementVar)
+                            val outVar = scope.getTmpVar("_result")
+                            adapter?.convert(outVar, statementVar, scope)
+                            addStatement("$returnPrefix%L", outVar)
+                            nextControlFlow("finally")
+                            addStatement("%L.close()", statementVar)
+                            endControlFlow()
+                        }
+                    }
+            )
+        scope.builder.add("return %L", performBlock)
+    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt
deleted file mode 100644
index 58f383f..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableDeleteOrUpdateMethodBinder.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.solver.shortcut.binder
-
-import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CallableTypeSpecBuilder
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.DeleteOrUpdateMethodAdapter
-import androidx.room.vo.ShortcutQueryParameter
-
-/**
- * Binder for deferred delete and update methods.
- *
- * This binder will create a Callable implementation that delegates to the
- * [DeleteOrUpdateMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
- * function.
- */
-class CallableDeleteOrUpdateMethodBinder
-private constructor(
-    val typeArg: XType,
-    val addStmntBlock: XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit,
-    adapter: DeleteOrUpdateMethodAdapter?
-) : DeleteOrUpdateMethodBinder(adapter) {
-
-    companion object {
-        fun createDeleteOrUpdateBinder(
-            typeArg: XType,
-            adapter: DeleteOrUpdateMethodAdapter?,
-            addCodeBlock:
-                XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit
-        ) = CallableDeleteOrUpdateMethodBinder(typeArg, addCodeBlock, adapter)
-    }
-
-    override fun convertAndReturn(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        convertAndReturnCompat(parameters, adapters, dbProperty, scope)
-    }
-
-    override fun convertAndReturnCompat(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, XTypeSpec>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        val adapterScope = scope.fork()
-        val callableImpl =
-            CallableTypeSpecBuilder(scope.language, typeArg.asTypeName()) {
-                    adapter?.generateMethodBodyCompat(
-                        parameters = parameters,
-                        adapters = adapters,
-                        dbProperty = dbProperty,
-                        scope = adapterScope
-                    )
-                    addCode(adapterScope.generate())
-                }
-                .build()
-
-        scope.builder.apply { addStmntBlock(callableImpl, dbProperty) }
-    }
-}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertOrUpsertMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertOrUpsertMethodBinder.kt
deleted file mode 100644
index 7dee268..0000000
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binder/CallableInsertOrUpsertMethodBinder.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.solver.shortcut.binder
-
-import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.processing.XType
-import androidx.room.ext.CallableTypeSpecBuilder
-import androidx.room.solver.CodeGenScope
-import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
-import androidx.room.vo.ShortcutQueryParameter
-
-/**
- * Binder for deferred insert methods.
- *
- * This binder will create a Callable implementation that delegates to the
- * [InsertOrUpsertMethodAdapter]. Usage of the Callable impl is then delegate to the [addStmntBlock]
- * function.
- */
-class CallableInsertOrUpsertMethodBinder(
-    val typeArg: XType,
-    val addStmntBlock: XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit,
-    adapter: InsertOrUpsertMethodAdapter?
-) : InsertOrUpsertMethodBinder(adapter) {
-
-    companion object {
-        fun createInsertOrUpsertBinder(
-            typeArg: XType,
-            adapter: InsertOrUpsertMethodAdapter?,
-            addCodeBlock:
-                XCodeBlock.Builder.(callableImpl: XTypeSpec, dbField: XPropertySpec) -> Unit
-        ) = CallableInsertOrUpsertMethodBinder(typeArg, addCodeBlock, adapter)
-    }
-
-    override fun convertAndReturn(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        convertAndReturnCompat(parameters, adapters, dbProperty, scope)
-    }
-
-    override fun convertAndReturnCompat(
-        parameters: List<ShortcutQueryParameter>,
-        adapters: Map<String, Pair<XPropertySpec, Any>>,
-        dbProperty: XPropertySpec,
-        scope: CodeGenScope
-    ) {
-        val adapterScope = scope.fork()
-        val callableImpl =
-            CallableTypeSpecBuilder(scope.language, typeArg.asTypeName()) {
-                    addCode(
-                        XCodeBlock.builder(language)
-                            .apply {
-                                adapter?.generateMethodBodyCompat(
-                                    parameters = parameters,
-                                    adapters = adapters,
-                                    dbProperty = dbProperty,
-                                    scope = adapterScope
-                                )
-                                addCode(adapterScope.generate())
-                            }
-                            .build()
-                    )
-                }
-                .build()
-
-        scope.builder.apply { addStmntBlock(callableImpl, dbProperty) }
-    }
-}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt
index fa50cc4..535e608 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableDeleteOrUpdateMethodBinderProvider.kt
@@ -18,10 +18,11 @@
 
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.KotlinTypeNames
 import androidx.room.processor.Context
 import androidx.room.solver.RxType
-import androidx.room.solver.shortcut.binder.CallableDeleteOrUpdateMethodBinder.Companion.createDeleteOrUpdateBinder
 import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
+import androidx.room.solver.shortcut.binder.LambdaDeleteOrUpdateMethodBinder
 
 /** Provider for Rx Callable binders. */
 open class RxCallableDeleteOrUpdateMethodBinderProvider
@@ -44,9 +45,11 @@
     override fun provide(declared: XType): DeleteOrUpdateMethodBinder {
         val typeArg = extractTypeArg(declared)
         val adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(typeArg)
-        return createDeleteOrUpdateBinder(typeArg, adapter) { callableImpl, _ ->
-            addStatement("return %T.fromCallable(%L)", rxType.className, callableImpl)
-        }
+        return LambdaDeleteOrUpdateMethodBinder(
+            typeArg = typeArg,
+            functionName = rxType.factoryMethodName,
+            adapter = adapter
+        )
     }
 
     companion object {
@@ -70,25 +73,24 @@
     }
 
     /**
-     * Since Completable is not a generic, the supported return type should be Void (nullable). Like
-     * this, the generated Callable.call method will return Void.
+     * Since Completable has no type argument, the supported return type is Unit (non-nullable)
+     * since the 'createCompletable" factory method take a Kotlin lambda.
      */
-    override fun extractTypeArg(declared: XType): XType = context.COMMON_TYPES.VOID.makeNullable()
+    override fun extractTypeArg(declared: XType): XType =
+        context.processingEnv.requireType(KotlinTypeNames.UNIT)
 
     override fun matches(declared: XType): Boolean = isCompletable(declared)
 
     private fun isCompletable(declared: XType): Boolean {
-        if (completableType == null) {
-            return false
-        }
-        return declared.rawType.isAssignableFrom(completableType!!)
+        val completableType = this.completableType ?: return false
+        return declared.rawType.isAssignableFrom(completableType)
     }
 }
 
 private class RxSingleOrMaybeDeleteOrUpdateMethodBinderProvider(context: Context, rxType: RxType) :
     RxCallableDeleteOrUpdateMethodBinderProvider(context, rxType) {
 
-    /** Since Maybe can have null values, the Callable returned must allow for null values. */
+    /** Since Maybe can have null values, the lambda returned must allow for null values. */
     override fun extractTypeArg(declared: XType): XType =
         declared.typeArguments.first().makeNullable()
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt
index fc23f43..9b0223d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/RxCallableInsertOrUpsertMethodBinderProvider.kt
@@ -18,10 +18,11 @@
 
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.KotlinTypeNames
 import androidx.room.processor.Context
 import androidx.room.solver.RxType
-import androidx.room.solver.shortcut.binder.CallableInsertOrUpsertMethodBinder.Companion.createInsertOrUpsertBinder
 import androidx.room.solver.shortcut.binder.InsertOrUpsertMethodBinder
+import androidx.room.solver.shortcut.binder.LambdaInsertOrUpsertMethodBinder
 import androidx.room.vo.ShortcutQueryParameter
 
 /** Provider for Rx Callable binders. */
@@ -54,9 +55,11 @@
             } else {
                 context.typeAdapterStore.findInsertAdapter(typeArg, params)
             }
-        return createInsertOrUpsertBinder(typeArg, adapter) { callableImpl, _ ->
-            addStatement("return %T.fromCallable(%L)", rxType.className, callableImpl)
-        }
+        return LambdaInsertOrUpsertMethodBinder(
+            typeArg = typeArg,
+            functionName = rxType.factoryMethodName,
+            adapter = adapter
+        )
     }
 
     companion object {
@@ -80,25 +83,24 @@
     }
 
     /**
-     * Since Completable is not a generic, the supported return type should be Void (nullable). Like
-     * this, the generated Callable.call method will return Void.
+     * Since Completable has no type argument, the supported return type is Unit (non-nullable)
+     * since the 'createCompletable" factory method take a Kotlin lambda.
      */
-    override fun extractTypeArg(declared: XType): XType = context.COMMON_TYPES.VOID.makeNullable()
+    override fun extractTypeArg(declared: XType): XType =
+        context.processingEnv.requireType(KotlinTypeNames.UNIT)
 
     override fun matches(declared: XType): Boolean = isCompletable(declared)
 
     private fun isCompletable(declared: XType): Boolean {
-        if (completableType == null) {
-            return false
-        }
-        return declared.rawType.isAssignableFrom(completableType!!)
+        val completableType = this.completableType ?: return false
+        return declared.rawType.isAssignableFrom(completableType)
     }
 }
 
 private class RxSingleOrMaybeInsertOrUpsertMethodBinderProvider(context: Context, rxType: RxType) :
     RxCallableInsertOrUpsertMethodBinderProvider(context, rxType) {
 
-    /** Since Maybe can have null values, the Callable returned must allow for null values. */
+    /** Since Maybe can have null values, the lambda returned must allow for null values. */
     override fun extractTypeArg(declared: XType): XType =
         declared.typeArguments.first().makeNullable()
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
index 295e26f..bcfe429 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -410,7 +410,7 @@
                             )
                         } else {
                             val keyTypeMirror = context.processingEnv.requireType(keyTypeName)
-                            val set = checkNotNull(context.COMMON_TYPES.SET.typeElement)
+                            val set = context.processingEnv.requireTypeElement(CommonTypeNames.SET)
                             val keySet = context.processingEnv.getDeclaredType(set, keyTypeMirror)
                             QueryParameter(
                                 name = RelationCollectorFunctionWriter.KEY_SET_VARIABLE,
@@ -575,8 +575,9 @@
             relation.field.type.let { fieldType ->
                 if (fieldType.typeArguments.isNotEmpty()) {
                     val rawType = fieldType.rawType
+                    val setType = context.processingEnv.requireType(CommonTypeNames.SET)
                     val paramTypeName =
-                        if (context.COMMON_TYPES.SET.rawType.isAssignableFrom(rawType)) {
+                        if (setType.rawType.isAssignableFrom(rawType)) {
                             when (context.codeLanguage) {
                                 CodeLanguage.KOTLIN ->
                                     CommonTypeNames.MUTABLE_SET.parametrizedBy(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt b/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
new file mode 100644
index 0000000..ecb2da5
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ProcessorTestWrapper.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import androidx.room.compiler.processing.XProcessingEnvConfig
+import androidx.room.compiler.processing.XProcessingStep
+import androidx.room.compiler.processing.util.CompilationResultSubject
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runKspTest
+import androidx.room.compiler.processing.util.runProcessorTest
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import java.io.File
+import javax.annotation.processing.Processor
+
+fun runProcessorTestWithK1(
+    sources: List<Source> = emptyList(),
+    classpath: List<File> = emptyList(),
+    options: Map<String, String> = emptyMap(),
+    javacArguments: List<String> = emptyList(),
+    kotlincArguments: List<String> = emptyList(),
+    handler: (XTestInvocation) -> Unit
+) {
+    androidx.room.compiler.processing.util.runProcessorTest(
+        sources = sources,
+        classpath = classpath,
+        options = options,
+        javacArguments = javacArguments,
+        kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+        handler = handler
+    )
+}
+
+fun runProcessorTestWithK1(
+    sources: List<Source> = emptyList(),
+    classpath: List<File> = emptyList(),
+    options: Map<String, String> = emptyMap(),
+    javacArguments: List<String> = emptyList(),
+    kotlincArguments: List<String> = emptyList(),
+    createProcessingSteps: () -> Iterable<XProcessingStep>,
+    onCompilationResult: (CompilationResultSubject) -> Unit
+) {
+    androidx.room.compiler.processing.util.runProcessorTest(
+        sources = sources,
+        classpath = classpath,
+        options = options,
+        javacArguments = javacArguments,
+        kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+        createProcessingSteps = createProcessingSteps,
+        onCompilationResult = onCompilationResult
+    )
+}
+
+fun runProcessorTestWithK1(
+    sources: List<Source> = emptyList(),
+    classpath: List<File> = emptyList(),
+    options: Map<String, String> = emptyMap(),
+    javacArguments: List<String> = emptyList(),
+    kotlincArguments: List<String> = emptyList(),
+    javacProcessors: List<Processor>,
+    symbolProcessorProviders: List<SymbolProcessorProvider>,
+    onCompilationResult: (CompilationResultSubject) -> Unit
+) {
+    runProcessorTest(
+        sources = sources,
+        classpath = classpath,
+        options = options,
+        javacArguments = javacArguments,
+        kotlincArguments = listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+        javacProcessors = javacProcessors,
+        symbolProcessorProviders = symbolProcessorProviders,
+        onCompilationResult = onCompilationResult
+    )
+}
+
+fun runKspTestWithK1(
+    sources: List<Source>,
+    classpath: List<File> = emptyList(),
+    options: Map<String, String> = emptyMap(),
+    javacArguments: List<String> = emptyList(),
+    kotlincArguments: List<String> = emptyList(),
+    config: XProcessingEnvConfig? = null,
+    handler: (XTestInvocation) -> Unit
+) {
+    if (config != null) {
+        runKspTest(
+            sources = sources,
+            classpath = classpath,
+            options = options,
+            javacArguments = javacArguments,
+            kotlincArguments =
+                listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+            config = config,
+            handler = handler
+        )
+    } else {
+        runKspTest(
+            sources = sources,
+            classpath = classpath,
+            options = options,
+            javacArguments = javacArguments,
+            kotlincArguments =
+                listOf("-language-version=1.9", "-api-version=1.9") + kotlincArguments,
+            handler = handler
+        )
+    }
+}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
index f1d3a45..1e314bf 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
@@ -24,8 +24,8 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -232,7 +232,7 @@
             """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(src),
             config =
                 XProcessingEnvConfig.DEFAULT.copy(excludeMethodsWithInvalidJvmSourceNames = false)
@@ -255,7 +255,7 @@
             } else {
                 sources to emptyList()
             }
-        runProcessorTest(sources = sources, classpath = classpath, handler = handler)
+        runProcessorTestWithK1(sources = sources, classpath = classpath, handler = handler)
     }
 
     private fun XTestInvocation.objectMethodNames(): List<String> {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
index 9927769..cc19974 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
@@ -20,7 +20,7 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 
 class SQLTypeAffinityTest {
@@ -33,7 +33,7 @@
      */
     @Test
     fun affinityTypes() {
-        runProcessorTest(sources = emptyList()) { invocation ->
+        runProcessorTestWithK1(sources = emptyList()) { invocation ->
             fun XNullability.toSignature() =
                 if (invocation.isKsp) {
                     when (this) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
index c567a4d..5c21636 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
@@ -17,7 +17,6 @@
 package androidx.room.processor
 
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.migration.bundle.EntityBundle
 import androidx.room.migration.bundle.FieldBundle
@@ -25,6 +24,7 @@
 import androidx.room.migration.bundle.SchemaBundle
 import androidx.room.processor.ProcessorErrors.AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR
 import androidx.room.processor.ProcessorErrors.INNER_CLASS_AUTOMIGRATION_SPEC_MUST_BE_STATIC
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import org.junit.Test
 
@@ -44,7 +44,7 @@
                     .trimIndent()
             )
 
-        runProcessorTest(listOf(source)) { invocation ->
+        runProcessorTestWithK1(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
@@ -71,7 +71,7 @@
                     .trimIndent()
             )
 
-        runProcessorTest(listOf(source)) { invocation ->
+        runProcessorTestWithK1(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
@@ -100,7 +100,7 @@
                     .trimIndent()
             )
 
-        runProcessorTest(listOf(source)) { invocation ->
+        runProcessorTestWithK1(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec =
@@ -132,7 +132,7 @@
                     .trimIndent()
             )
 
-        runProcessorTest(listOf(source)) { invocation ->
+        runProcessorTestWithK1(listOf(source)) { invocation ->
             AutoMigrationProcessor(
                     context = invocation.context,
                     spec = invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
index 0d0484c..95779d9 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
@@ -4,7 +4,7 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.Dao
 import androidx.room.writer.DaoWriter
@@ -209,7 +209,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val dbElm = invocation.context.processingEnv.requireTypeElement("MyDb")
             val dbType = dbElm.type
             // if we could create valid code, it is good, no need for assertions.
@@ -268,8 +268,8 @@
                 }
             """
             )
-        runProcessorTest(sources = listOf(baseClass, extension, COMMON.USER, fakeDb)) { invocation
-            ->
+        runProcessorTestWithK1(sources = listOf(baseClass, extension, COMMON.USER, fakeDb)) {
+            invocation ->
             val daoElm = invocation.processingEnv.requireTypeElement("foo.bar.MyDao")
             val dbElm = invocation.context.processingEnv.requireTypeElement("foo.bar.MyDb")
             val dbType = dbElm.type
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
index 657c1f7..d70520f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
@@ -19,7 +19,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.Entity
 import java.io.File
@@ -61,7 +61,7 @@
         } else {
             baseClassReplacement = " extends $baseClass"
         }
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 sources +
                     Source.java(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
index 5d44695..9ac795b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/BaseFtsEntityParserTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.FtsEntity
 import java.io.File
@@ -76,7 +76,8 @@
                     baseClassReplacement
                 ) + input + ENTITY_SUFFIX
             )
-        runProcessorTest(sources = sources + entitySource, classpath = classpath) { invocation ->
+        runProcessorTestWithK1(sources = sources + entitySource, classpath = classpath) { invocation
+            ->
             val entity = invocation.processingEnv.requireTypeElement("foo.bar.MyEntity")
             val processor = FtsTableEntityProcessor(invocation.context, entity)
             val processedEntity = processor.process()
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
index ea33b1c..e48920a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
@@ -28,7 +28,6 @@
 import androidx.room.compiler.codegen.XTypeSpec.Builder.Companion.apply
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.CommonTypeNames.MUTABLE_LIST
 import androidx.room.ext.CommonTypeNames.STRING
@@ -37,6 +36,7 @@
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CustomTypeConverter
 import com.squareup.javapoet.TypeVariableName
@@ -256,7 +256,7 @@
                         .build()
                         .toString()
             )
-        runProcessorTest(sources = listOf(baseConverter, extendingClass)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(baseConverter, extendingClass)) { invocation ->
             val element =
                 invocation.processingEnv.requireTypeElement(extendingClassName.canonicalName)
             val converter =
@@ -339,7 +339,7 @@
                 public class Container {}
                 """
             )
-        runProcessorTest(listOf(source)) { invocation ->
+        runProcessorTestWithK1(listOf(source)) { invocation ->
             val result =
                 CustomConverterProcessor.findConverters(
                     invocation.context,
@@ -405,7 +405,7 @@
         vararg sources: Source,
         handler: (CustomTypeConverter?, XTestInvocation) -> Unit
     ) {
-        runProcessorTest(sources = sources.toList() + CONTAINER) { invocation ->
+        runProcessorTestWithK1(sources = sources.toList() + CONTAINER) { invocation ->
             val processed =
                 CustomConverterProcessor.findConverters(
                     invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
index 4306440..8956655 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -22,11 +22,11 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.ProcessorErrors.nullableCollectionOrArrayReturnTypeInDaoMethod
 import androidx.room.processor.ProcessorErrors.nullableComponentInDaoMethodReturnType
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.Dao
 import androidx.room.vo.ReadQueryMethod
@@ -263,7 +263,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(daoSrc) + COMMON.USER) { invocation ->
+        runProcessorTestWithK1(sources = listOf(daoSrc) + COMMON.USER) { invocation ->
             val dao =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Dao::class.qualifiedName!!)
@@ -452,7 +452,7 @@
         """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val dao = invocation.processingEnv.requireTypeElement("MyDao")
             val dbType = invocation.context.processingEnv.requireType(ROOM_DB)
             DaoProcessor(
@@ -490,7 +490,7 @@
             """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -528,7 +528,7 @@
             """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -569,7 +569,7 @@
             """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -641,7 +641,7 @@
             """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -767,7 +767,7 @@
             """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -838,7 +838,7 @@
         classpathFiles: List<File> = emptyList(),
         handler: (Dao, XTestInvocation) -> Unit
     ) {
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     Source.java("foo.bar.MyDao", DAO_PREFIX + inputs.joinToString("\n")),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
index 44ec92f..d66a941 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseConstructorProcessorTest.kt
@@ -19,7 +19,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
+import androidx.room.runKspTestWithK1
 import androidx.room.testing.context
 import org.junit.Test
 
@@ -136,7 +136,7 @@
     }
 
     private fun runTest(constructorSource: Source, handler: (XTestInvocation) -> Unit = { _ -> }) {
-        runKspTest(sources = listOf(databaseSource, constructorSource)) { invocation ->
+        runKspTestWithK1(sources = listOf(databaseSource, constructorSource)) { invocation ->
             val entity =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 5885790..885b353 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -29,12 +29,12 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.compileFilesIntoJar
-import androidx.room.compiler.processing.util.runKspTest
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.ParsedQuery
 import androidx.room.parser.QueryType
 import androidx.room.parser.Table
 import androidx.room.processor.ProcessorErrors.invalidAutoMigrationSchema
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.query.result.EntityRowAdapter
 import androidx.room.solver.query.result.PojoRowAdapter
 import androidx.room.testing.context
@@ -508,7 +508,7 @@
                 }
                 """
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
             createProcessingSteps = { listOf(DatabaseProcessingStep()) }
@@ -1257,7 +1257,7 @@
                 }
                 """
             )
-        runProcessorTest(sources = listOf(badDaoType)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(badDaoType)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
             val result =
                 DatabaseProcessor(baseContext = invocation.context, element = element).process()
@@ -1281,7 +1281,7 @@
                 }
                 """
             )
-        runProcessorTest(listOf(badDaoType)) { invocation ->
+        runProcessorTestWithK1(listOf(badDaoType)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
             val result =
                 DatabaseProcessor(baseContext = invocation.context, element = element).process()
@@ -1503,7 +1503,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(dbSource, USER),
             options = mapOf("room.schemaLocation" to "schemas/", "room.generateKotlin" to "false")
         ) { invocation ->
@@ -1561,7 +1561,7 @@
                         .trimIndent()
                 )
             }
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(dbSource, USER),
             javacProcessors = listOf(RoomProcessor()),
             symbolProcessorProviders = listOf(RoomKspProcessor.Provider()),
@@ -1591,7 +1591,7 @@
                 }
                 """
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(jvmNameInDaoGetter),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -1630,7 +1630,7 @@
             """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(src),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) { invocation ->
@@ -1663,7 +1663,7 @@
         views: Map<String, Set<String>>,
         body: (List<DatabaseView>, XTestInvocation) -> Unit
     ) {
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(DB3, BOOK),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -1716,7 +1716,7 @@
                 }
                 """
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(BOOK, bookDao) + dbs,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
             createProcessingSteps = { listOf(DatabaseProcessingStep()) },
@@ -1731,7 +1731,7 @@
         classpath: List<File> = emptyList(),
         handler: (Database, XTestInvocation) -> Unit
     ) {
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = otherFiles.toList() + Source.java("foo.bar.MyDb", DATABASE_PREFIX + input),
             classpath = classpath,
             options =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
index d9d5117..a00de28 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseViewProcessorTest.kt
@@ -19,9 +19,9 @@
 import androidx.kruth.assertThat
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.ParserErrors
 import androidx.room.parser.SQLTypeAffinity
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.verifier.ColumnInfo
 import androidx.room.vo.DatabaseView
@@ -235,7 +235,7 @@
         verify: Boolean = true,
         handler: (view: DatabaseView, invocation: XTestInvocation) -> Unit
     ) {
-        runProcessorTest(sources = sources + Source.java(name, DATABASE_PREFIX + input)) {
+        runProcessorTestWithK1(sources = sources + Source.java(name, DATABASE_PREFIX + input)) {
             invocation ->
             val view = invocation.processingEnv.requireTypeElement(name)
             val verifier =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
index 4c5d5a1..18f90c1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
@@ -28,7 +28,6 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
@@ -36,6 +35,7 @@
 import androidx.room.ext.ReactiveStreamsTypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.DeleteOrUpdateShortcutMethod
 import kotlin.reflect.KClass
@@ -770,7 +770,7 @@
                 COMMON.LISTENABLE_FUTURE,
                 COMMON.GUAVA_ROOM
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -826,7 +826,7 @@
                 COMMON.GUAVA_ROOM
             )
 
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
index 0cfc88a4..63256c3 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
@@ -24,8 +24,10 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.Collate
 import androidx.room.parser.SQLTypeAffinity
+import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.types.ColumnTypeAdapter
 import androidx.room.testing.context
 import androidx.room.vo.Field
@@ -600,7 +602,10 @@
                     `is`(
                         Field(
                             name = "code",
-                            type = invocation.context.COMMON_TYPES.STRING.makeNullable(),
+                            type =
+                                invocation.context.processingEnv
+                                    .requireType(CommonTypeNames.STRING)
+                                    .makeNullable(),
                             element = field.element,
                             columnName = "code",
                             collate = collate,
@@ -702,7 +707,7 @@
                 ),
                 ARRAY_CONVERTER
             )
-        runProcessorTest(sources = sources) { invocation ->
+        runProcessorTestWithK1(sources = sources) { invocation ->
             val (owner, fieldElement) =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(Entity::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
index b8ed3770..9d32327 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
@@ -20,9 +20,9 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.FtsVersion
 import androidx.room.parser.SQLTypeAffinity
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CallType
 import androidx.room.vo.Field
@@ -86,7 +86,7 @@
 
     @Test
     fun missingEntityAnnotation() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     Source.java(
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
index 6051837..d63633f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/GeneratedCustomConverterTest.kt
@@ -26,7 +26,7 @@
 import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor
 import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 import com.google.devtools.ksp.processing.SymbolProcessorProvider
 import com.squareup.javapoet.ClassName
@@ -75,7 +75,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(src),
             javacProcessors = listOf(RoomProcessor(), JavacCustomConverter()),
             symbolProcessorProviders =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
index 857704e..f0ddca5 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
@@ -28,7 +28,6 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
@@ -36,6 +35,7 @@
 import androidx.room.ext.ReactiveStreamsTypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
+import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.shortcut.result.InsertOrUpsertMethodAdapter
 import androidx.room.testing.context
 import androidx.room.vo.InsertOrUpsertShortcutMethod
@@ -1139,7 +1139,7 @@
                 COMMON.RX3_SINGLE
             )
 
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -1194,7 +1194,7 @@
                 COMMON.GUAVA_ROOM
             )
 
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = commonSources + additionalSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt
index 75abd32..8de7576 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTargetMethodTest.kt
@@ -19,7 +19,7 @@
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -461,7 +461,7 @@
     }
 
     private fun singleRun(vararg sources: Source, handler: ((XTestInvocation) -> Unit)? = null) {
-        runProcessorTest(sources = sources.toList()) { invocation ->
+        runProcessorTestWithK1(sources = sources.toList()) { invocation ->
             PojoProcessor.createFor(
                     context = invocation.context,
                     element = invocation.processingEnv.requireTypeElement(MY_POJO),
@@ -506,7 +506,7 @@
         val pojoSource = Source.java(MY_POJO.canonicalName, pojoCode)
         val autoValuePojoSource = Source.java(AUTOVALUE_MY_POJO.canonicalName, autoValuePojoCode)
         val all = sources.toList() + pojoSource + autoValuePojoSource
-        return runProcessorTest(sources = all) { invocation ->
+        return runProcessorTestWithK1(sources = all) { invocation ->
             PojoProcessor.createFor(
                     context = invocation.context,
                     element = invocation.processingEnv.requireTypeElement(MY_POJO),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
index 9a2fa5d..3bebe36 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -23,7 +23,7 @@
 import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
 import androidx.room.processor.ProcessorErrors.MISSING_POJO_CONSTRUCTOR
@@ -33,6 +33,7 @@
 import androidx.room.processor.ProcessorErrors.relationCannotFindJunctionEntityField
 import androidx.room.processor.ProcessorErrors.relationCannotFindJunctionParentField
 import androidx.room.processor.ProcessorErrors.relationCannotFindParentEntityField
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CallType
 import androidx.room.vo.Constructor
@@ -84,7 +85,7 @@
                 public void setBaseField(String baseField){ }
             }
         """
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     Source.java(
@@ -1054,7 +1055,7 @@
             $FOOTER
             """
             )
-        runProcessorTest(sources = listOf(pojo)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(pojo)) { invocation ->
             val element = invocation.processingEnv.requireTypeElement(MY_POJO)
             val pojo1 =
                 PojoProcessor.createFor(
@@ -1108,7 +1109,7 @@
                     .process()
             assertThat(pojo5, sameInstance(pojo4))
 
-            val type = invocation.context.COMMON_TYPES.STRING
+            val type = invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
             val mockElement = mock(XFieldElement::class.java)
             doReturn(type).`when`(mockElement).type
             val fakeField =
@@ -1706,7 +1707,7 @@
                 "foo.bar.TestData.WithJvmOverloads"
             )
             .forEach {
-                runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
+                runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
                     PojoProcessor.createFor(
                             context = invocation.context,
                             element = invocation.processingEnv.requireTypeElement(it),
@@ -1721,7 +1722,7 @@
 
     @Test
     fun dataClass_withJvmOverloads_primaryConstructor() {
-        runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
             PojoProcessor.createFor(
                     context = invocation.context,
                     element =
@@ -1751,7 +1752,7 @@
             }
             """
             )
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val pojo =
                 PojoProcessor.createFor(
                         context = invocation.context,
@@ -1767,7 +1768,7 @@
 
     @Test
     fun ignoredColumns_noConstructor() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -1806,7 +1807,7 @@
 
     @Test
     fun ignoredColumns_noSetterGetter() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -1843,7 +1844,7 @@
 
     @Test
     fun ignoredColumns_columnInfo() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -1875,7 +1876,7 @@
 
     @Test
     fun ignoredColumns_missing() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -1909,7 +1910,7 @@
 
     @Test
     fun noSetter_scopeBindStmt() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -1938,7 +1939,7 @@
 
     @Test
     fun noSetter_scopeTwoWay() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -1970,7 +1971,7 @@
 
     @Test
     fun noSetter_scopeReadFromCursor() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -2002,7 +2003,7 @@
 
     @Test
     fun noGetter_scopeBindStmt() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -2034,7 +2035,7 @@
 
     @Test
     fun noGetter_scopeTwoWay() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -2066,7 +2067,7 @@
 
     @Test
     fun noGetter_scopeReadCursor() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.java(
                     MY_POJO.canonicalName,
@@ -2095,7 +2096,7 @@
 
     @Test
     fun setterStartsWithIs() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             listOf(
                 Source.kotlin(
                     "Book.kt",
@@ -2119,7 +2120,7 @@
                     )
                     .process()
             val fields = result.fields.associateBy { it.name }
-            val stringType = invocation.context.COMMON_TYPES.STRING
+            val stringType = invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
             assertThat(fields["isbn"]?.getter)
                 .isEqualTo(
                     FieldGetter(
@@ -2163,7 +2164,7 @@
     @Test
     fun embedded_nullability() {
         listOf("foo.bar.TestData.SomeEmbeddedVals").forEach {
-            runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
+            runProcessorTestWithK1(sources = listOf(TEST_DATA)) { invocation ->
                 val result =
                     PojoProcessor.createFor(
                             context = invocation.context,
@@ -2211,7 +2212,7 @@
     ) {
         val pojoSource = Source.java(MY_POJO.canonicalName, code)
         val all = sources.toList() + pojoSource
-        runProcessorTest(sources = all, classpath = classpath) { invocation ->
+        runProcessorTestWithK1(sources = all, classpath = classpath) { invocation ->
             handler.invoke(
                 PojoProcessor.createFor(
                         context = invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
index f4bf2e8..124fccd 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/ProjectionExpanderTest.kt
@@ -20,9 +20,9 @@
 import androidx.room.compiler.processing.isTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.SqlParser
 import androidx.room.parser.expansion.ProjectionExpander
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import createVerifierFromEntitiesAndViews
 import org.hamcrest.CoreMatchers.equalTo
@@ -521,7 +521,7 @@
 
     @Test
     fun joinAndAbandonEntity() {
-        runProcessorTest(sources = ENTITIES) { invocation ->
+        runProcessorTestWithK1(sources = ENTITIES) { invocation ->
             val entities =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Entity::class.qualifiedName!!)
@@ -613,7 +613,7 @@
         val extraSource =
             input?.let { listOf(Source.java(name, DATABASE_PREFIX + input)) } ?: emptyList()
         val all = ENTITIES + extraSource
-        return runProcessorTest(sources = all) { invocation ->
+        return runProcessorTestWithK1(sources = all) { invocation ->
             val entities =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Entity::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 1c92cc9..6ab007f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -27,7 +27,6 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.CommonTypeNames.LIST
 import androidx.room.ext.CommonTypeNames.MUTABLE_LIST
@@ -46,6 +45,7 @@
 import androidx.room.processor.ProcessorErrors.MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED
 import androidx.room.processor.ProcessorErrors.cannotFindQueryResultAdapter
 import androidx.room.processor.ProcessorErrors.mayNeedMapColumn
+import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.query.result.DataSourceFactoryQueryResultBinder
 import androidx.room.solver.query.result.ListQueryResultAdapter
 import androidx.room.solver.query.result.LiveDataQueryResultBinder
@@ -1239,7 +1239,7 @@
             )
         val allOptions =
             mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false") + options
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = additionalSources + commonSources + inputSource,
             options = allOptions
         ) { invocation ->
@@ -1309,7 +1309,7 @@
                 COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
             )
 
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = additionalSources + commonSources + inputSource,
             options = options
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
index 173a78a..d9cc959 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
@@ -35,6 +35,7 @@
 import androidx.room.ext.RxJava3TypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.RawQueryMethod
 import androidx.sqlite.db.SupportSQLiteQuery
@@ -644,7 +645,7 @@
                 COMMON.IMAGE_FORMAT,
                 COMMON.CONVERTER
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = commonSources + inputSource,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
@@ -698,7 +699,7 @@
                 COMMON.FLOW,
                 COMMON.GUAVA_ROOM
             )
-        runProcessorTest(sources = commonSources + inputSource) { invocation ->
+        runProcessorTestWithK1(sources = commonSources + inputSource) { invocation ->
             val (owner, methods) =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
index 4ffa12b..447dca2 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RemoveUnusedColumnsTest.kt
@@ -21,7 +21,7 @@
 import androidx.room.RewriteQueriesToDropUnusedColumns
 import androidx.room.compiler.processing.util.CompilationResultSubject
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -90,7 +90,7 @@
                 annotateMethod = annotateMethod
             ) + COMMON.USER
 
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = sources,
             createProcessingSteps = { listOf(DatabaseProcessingStep()) },
             options =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
index 109db0a..7d3459a 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
@@ -23,9 +23,9 @@
 import androidx.room.compiler.codegen.XTypeName.Companion.PRIMITIVE_LONG
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.CallType
 import androidx.room.vo.Field
@@ -2093,16 +2093,9 @@
             sources = listOf(COMMON.USER)
         ) { _, invocation ->
             invocation.assertCompilationResult {
-                // TODO: https://github.com/google/ksp/issues/603
-                // KSP validator does not validate annotation types so we will get another error
-                // down the line.
-                if (invocation.isKsp) {
-                    hasErrorContaining(ProcessorErrors.foreignKeyNotAnEntity("<Error>")).onLine(11)
-                } else {
-                    hasErrorContaining(
-                        "Element 'foo.bar.MyEntity' references a type that is not present"
-                    )
-                }
+                hasErrorContaining(
+                    "Element 'foo.bar.MyEntity' references a type that is not present"
+                )
             }
         }
     }
@@ -2637,7 +2630,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(src)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(src)) { invocation ->
             val parser =
                 TableEntityProcessor(
                     invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
index cc9d309..9354625 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
@@ -23,7 +23,6 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
 import androidx.room.ext.KotlinTypeNames.FLOW
 import androidx.room.ext.LifecyclesTypeNames.COMPUTABLE_LIVE_DATA
@@ -31,6 +30,7 @@
 import androidx.room.ext.ReactiveStreamsTypeNames.PUBLISHER
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.TransactionMethod
 import org.hamcrest.CoreMatchers.`is`
@@ -342,7 +342,7 @@
                 COMMON.LISTENABLE_FUTURE,
                 COMMON.FLOW
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = inputSource + otherSources,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false")
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
index 9c942db..c36fc4b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
@@ -20,10 +20,10 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.FieldProcessor
 import androidx.room.processor.PojoProcessor
 import androidx.room.processor.ProcessorErrors
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.Pojo
 import com.google.auto.value.processor.AutoValueProcessor
@@ -112,7 +112,7 @@
                 // between javac (argN) and kotlinc (pN).
                 javacArguments = listOf("-parameters")
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = emptyList(),
             classpath = libraryClasspath,
         ) { invocation: XTestInvocation ->
@@ -281,7 +281,7 @@
         val pojoSource = Source.java(MY_POJO.canonicalName, pojoCode)
         val autoValuePojoSource = Source.java(AUTOVALUE_MY_POJO.canonicalName, autoValuePojoCode)
         val all: List<Source> = sources.toList() + pojoSource + autoValuePojoSource
-        runProcessorTest(sources = all, classpath = classpathFiles) { invocation ->
+        runProcessorTestWithK1(sources = all, classpath = classpathFiles) { invocation ->
             handler.invoke(
                 PojoProcessor.createFor(
                         context = invocation.context,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
index a53c6e7..6ced559 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/BuiltInConverterFlagsTest.kt
@@ -23,10 +23,10 @@
 import androidx.room.DatabaseProcessingStep
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_CURSOR_READER
+import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -138,7 +138,7 @@
                 daoAnnotation = daoAnnotation,
                 dbAnnotation = dbAnnotation
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(source),
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
index 3528c71..3bfb880 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
@@ -31,11 +31,11 @@
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.processing.util.CompilationResultSubject
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomAnnotationTypeNames
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT
+import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -212,7 +212,7 @@
         sources: List<Source>,
         onCompilationResult: (CompilationResultSubject) -> Unit = { it.hasErrorCount(0) }
     ) {
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 sources +
                     CUSTOM_TYPE_JFO +
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
index 9842dd3..9ed50f1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
@@ -25,11 +25,12 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
 import androidx.room.compiler.processing.util.compiler.compile
-import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.Context.BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER
 import androidx.room.processor.CustomConverterProcessor
 import androidx.room.processor.DaoProcessor
+import androidx.room.runKspTestWithK1
+import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.types.CustomTypeConverterWrapper
 import androidx.room.solver.types.TypeConverter
 import androidx.room.testing.context
@@ -405,7 +406,7 @@
         """
                     .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(user, day, converters, dao),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -604,7 +605,7 @@
         """
                     .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(source),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -680,7 +681,7 @@
         """
                     .trimIndent()
             )
-        runKspTest(
+        runKspTestWithK1(
             sources = listOf(converters),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -738,7 +739,7 @@
         """
                     .trimIndent()
             )
-        runKspTest(sources = listOf(source)) { invocation ->
+        runKspTestWithK1(sources = listOf(source)) { invocation ->
             val store = invocation.createStore("TimeConverter", "AwesomenessConverter")
             val instantType = invocation.processingEnv.requireType("java.time.Instant")
             val stringType = invocation.processingEnv.requireType("java.lang.String")
@@ -752,7 +753,7 @@
     /** Collect results for conversion from String to our type */
     private fun collectStringConversionResults(vararg selectedConverters: String): String {
         val result = StringBuilder()
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(source),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
@@ -800,7 +801,7 @@
     /** Collect results for conversion from an unknown cursor type to our type */
     private fun collectCursorResults(vararg selectedConverters: String): String {
         val result = StringBuilder()
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(source),
             options = mapOf(USE_NULL_AWARE_CONVERTER.argName to "true")
         ) { invocation ->
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 0ebee23..29cbd79 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -29,6 +29,7 @@
 import androidx.room.compiler.processing.isTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
@@ -46,6 +47,7 @@
 import androidx.room.processor.DaoProcessor
 import androidx.room.processor.DaoProcessorTest
 import androidx.room.processor.ProcessorErrors
+import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
 import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider
 import androidx.room.solver.binderprovider.ListenableFuturePagingSourceQueryResultBinderProvider
@@ -137,7 +139,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(entity, converter)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(entity, converter)) { invocation ->
             val typeElement =
                 invocation.processingEnv.requireTypeElement("foo.bar.EntityWithOneWayEnum")
             val context = Context(invocation.processingEnv)
@@ -201,7 +203,7 @@
                 """
                     .trimMargin()
             )
-        runProcessorTest(sources = listOf(enumSrc)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(enumSrc)) { invocation ->
             val store =
                 TypeAdapterStore.create(
                     Context(invocation.processingEnv),
@@ -234,7 +236,7 @@
             )
         var results: Map<String, String?> = mutableMapOf()
 
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val typeAdapterStore =
                 TypeAdapterStore.create(
                     context = invocation.context,
@@ -290,7 +292,7 @@
                     .trimIndent()
             )
 
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             TypeAdapterStore.create(
                 context = invocation.context,
                 builtInConverterFlags = BuiltInConverterFlags.DEFAULT
@@ -407,7 +409,7 @@
             }
             """
             )
-        runProcessorTest(sources = listOf(point)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(point)) { invocation ->
             val context = Context(invocation.processingEnv)
             val converters =
                 CustomConverterProcessor(
@@ -532,7 +534,7 @@
             val converter =
                 store.typeConverterStore.findTypeConverter(
                     binders[0].from,
-                    invocation.context.COMMON_TYPES.STRING
+                    invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
                 )
             assertThat(converter).isNotNull()
             assertThat(store.typeConverterStore.reverse(converter!!)).isEqualTo(binders[1])
@@ -559,7 +561,7 @@
             val converter =
                 store.typeConverterStore.findTypeConverter(
                     binders[0].from,
-                    invocation.context.COMMON_TYPES.STRING
+                    invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
                 )
             assertThat(converter, notNullValue())
             assertThat(store.typeConverterStore.reverse(converter!!), nullValue())
@@ -568,7 +570,8 @@
 
     @Test
     fun testMissingRx2Room() {
-        runProcessorTest(sources = listOf(COMMON.PUBLISHER, COMMON.RX2_FLOWABLE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.PUBLISHER, COMMON.RX2_FLOWABLE)) { invocation
+            ->
             val publisherElement =
                 invocation.processingEnv.requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER)
             assertThat(publisherElement, notNullValue())
@@ -586,7 +589,8 @@
 
     @Test
     fun testMissingRx3Room() {
-        runProcessorTest(sources = listOf(COMMON.PUBLISHER, COMMON.RX3_FLOWABLE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.PUBLISHER, COMMON.RX3_FLOWABLE)) { invocation
+            ->
             val publisherElement =
                 invocation.processingEnv.requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER)
             assertThat(publisherElement, notNullValue())
@@ -625,7 +629,8 @@
 
     @Test
     fun testMissingRoomPagingGuava() {
-        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
+            invocation ->
             val listenableFuturePagingSourceElement =
                 invocation.processingEnv.requireTypeElement(
                     PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -652,7 +657,7 @@
 
     @Test
     fun testMissingRoomPagingRx2() {
-        runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
             val rx2PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -673,7 +678,7 @@
 
     @Test
     fun testMissingRoomPagingRx3() {
-        runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
             val rx3PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -696,16 +701,21 @@
     fun testFindPublisher() {
         listOf(COMMON.RX2_FLOWABLE to COMMON.RX2_ROOM, COMMON.RX3_FLOWABLE to COMMON.RX3_ROOM)
             .forEach { (rxTypeSrc, rxRoomSrc) ->
-                runProcessorTest(
-                    sources =
-                        listOf(
-                            COMMON.RX2_SINGLE,
-                            COMMON.RX3_SINGLE,
-                            COMMON.RX2_OBSERVABLE,
-                            COMMON.RX3_OBSERVABLE,
-                            COMMON.PUBLISHER,
-                            rxTypeSrc,
-                            rxRoomSrc
+                runProcessorTestWithK1(
+                    sources = listOf(rxTypeSrc, rxRoomSrc),
+                    classpath =
+                        compileFiles(
+                            listOf(
+                                COMMON.RX2_SINGLE,
+                                COMMON.RX2_MAYBE,
+                                COMMON.RX2_COMPLETABLE,
+                                COMMON.RX2_OBSERVABLE,
+                                COMMON.RX3_SINGLE,
+                                COMMON.RX3_MAYBE,
+                                COMMON.RX3_COMPLETABLE,
+                                COMMON.RX3_OBSERVABLE,
+                                COMMON.PUBLISHER,
+                            )
                         )
                 ) { invocation ->
                     val publisher =
@@ -730,16 +740,21 @@
                 Triple(COMMON.RX3_FLOWABLE, COMMON.RX3_ROOM, RxJava3TypeNames.FLOWABLE)
             )
             .forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) ->
-                runProcessorTest(
-                    sources =
-                        listOf(
-                            COMMON.RX2_SINGLE,
-                            COMMON.RX3_SINGLE,
-                            COMMON.RX2_OBSERVABLE,
-                            COMMON.RX3_OBSERVABLE,
-                            COMMON.PUBLISHER,
-                            rxTypeSrc,
-                            rxRoomSrc
+                runProcessorTestWithK1(
+                    sources = listOf(rxTypeSrc, rxRoomSrc),
+                    classpath =
+                        compileFiles(
+                            listOf(
+                                COMMON.RX2_SINGLE,
+                                COMMON.RX2_MAYBE,
+                                COMMON.RX2_COMPLETABLE,
+                                COMMON.RX2_OBSERVABLE,
+                                COMMON.RX3_SINGLE,
+                                COMMON.RX3_MAYBE,
+                                COMMON.RX3_COMPLETABLE,
+                                COMMON.RX3_OBSERVABLE,
+                                COMMON.PUBLISHER,
+                            )
                         )
                 ) { invocation ->
                     val flowable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
@@ -760,16 +775,23 @@
                 Triple(COMMON.RX3_OBSERVABLE, COMMON.RX3_ROOM, RxJava3TypeNames.OBSERVABLE)
             )
             .forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) ->
-                runProcessorTest(
-                    sources =
-                        listOf(
-                            COMMON.RX2_SINGLE,
-                            COMMON.RX3_SINGLE,
-                            COMMON.RX2_FLOWABLE,
-                            COMMON.RX3_FLOWABLE,
-                            COMMON.PUBLISHER,
-                            rxTypeSrc,
-                            rxRoomSrc
+                runProcessorTestWithK1(
+                    sources = listOf(rxTypeSrc, rxRoomSrc),
+                    classpath =
+                        compileFiles(
+                            listOf(
+                                COMMON.RX2_SINGLE,
+                                COMMON.RX2_MAYBE,
+                                COMMON.RX2_COMPLETABLE,
+                                COMMON.RX2_OBSERVABLE,
+                                COMMON.RX2_FLOWABLE,
+                                COMMON.RX3_SINGLE,
+                                COMMON.RX3_MAYBE,
+                                COMMON.RX3_COMPLETABLE,
+                                COMMON.RX3_OBSERVABLE,
+                                COMMON.RX3_FLOWABLE,
+                                COMMON.PUBLISHER,
+                            )
                         )
                 ) { invocation ->
                     val observable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
@@ -791,7 +813,7 @@
                 Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
                     val single = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(single, notNullValue())
                     assertThat(
@@ -810,7 +832,7 @@
                 Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
                     val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                         RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -828,7 +850,7 @@
                 Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
                     val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                         RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -841,7 +863,7 @@
 
     @Test
     fun testFindInsertListenableFuture() {
-        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
             val future =
                 invocation.processingEnv.requireTypeElement(
                     GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -856,7 +878,7 @@
 
     @Test
     fun testFindDeleteOrUpdateSingle() {
-        runProcessorTest(sources = listOf(COMMON.RX2_SINGLE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX2_SINGLE)) { invocation ->
             val single = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.SINGLE)
             assertThat(single, notNullValue())
             assertThat(
@@ -870,7 +892,7 @@
 
     @Test
     fun testFindDeleteOrUpdateMaybe() {
-        runProcessorTest(sources = listOf(COMMON.RX2_MAYBE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX2_MAYBE)) { invocation ->
             val maybe = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.MAYBE)
             assertThat(maybe, notNullValue())
             assertThat(
@@ -884,7 +906,7 @@
 
     @Test
     fun testFindDeleteOrUpdateCompletable() {
-        runProcessorTest(sources = listOf(COMMON.RX2_COMPLETABLE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX2_COMPLETABLE)) { invocation ->
             val completable =
                 invocation.processingEnv.requireTypeElement(RxJava2TypeNames.COMPLETABLE)
             assertThat(completable, notNullValue())
@@ -899,7 +921,7 @@
 
     @Test
     fun testFindDeleteOrUpdateListenableFuture() {
-        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
             val future =
                 invocation.processingEnv.requireTypeElement(
                     GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -920,7 +942,7 @@
                 Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
                     val single = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(single).isNotNull()
                     assertThat(
@@ -939,7 +961,7 @@
                 Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
                     val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                             RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -957,7 +979,7 @@
                 Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE)
             )
             .forEach { (rxTypeSrc, _, rxTypeClassName) ->
-                runProcessorTest(sources = listOf(rxTypeSrc)) { invocation ->
+                runProcessorTestWithK1(sources = listOf(rxTypeSrc)) { invocation ->
                     val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName)
                     assertThat(
                             RxCallableInsertOrUpsertMethodBinderProvider.getAll(invocation.context)
@@ -970,7 +992,7 @@
 
     @Test
     fun testFindUpsertListenableFuture() {
-        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE)) { invocation ->
             val future =
                 invocation.processingEnv.requireTypeElement(
                     GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
@@ -985,7 +1007,7 @@
 
     @Test
     fun testFindLiveData() {
-        runProcessorTest(sources = listOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
+        runProcessorTestWithK1(sources = listOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
             invocation ->
             val liveData =
                 invocation.processingEnv.requireTypeElement(LifecyclesTypeNames.LIVE_DATA)
@@ -999,7 +1021,7 @@
 
     @Test
     fun findPagingSourceIntKey() {
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(COMMON.LIMIT_OFFSET_PAGING_SOURCE),
         ) { invocation ->
             val pagingSourceElement =
@@ -1143,7 +1165,8 @@
 
     @Test
     fun findListenableFuturePagingSourceJavaCollectionValue() {
-        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
+            invocation ->
             val listenableFuturePagingSourceElement =
                 invocation.processingEnv.requireTypeElement(
                     PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -1171,7 +1194,8 @@
 
     @Test
     fun findListenableFutureKotlinCollectionValue() {
-        runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE)) {
+            invocation ->
             val listenableFuturePagingSourceElement =
                 invocation.processingEnv.requireTypeElement(
                     PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE
@@ -1199,7 +1223,7 @@
 
     @Test
     fun findRx2PagingSourceJavaCollectionValue() {
-        runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
             val rx2PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1225,7 +1249,7 @@
 
     @Test
     fun findRx2PagingSourceKotlinCollectionValue() {
-        runProcessorTest(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX2_PAGING_SOURCE)) { invocation ->
             val rx2PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1251,7 +1275,7 @@
 
     @Test
     fun findRx3PagingSourceJavaCollectionValue() {
-        runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
             val rx3PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1277,7 +1301,7 @@
 
     @Test
     fun findRx3PagingSourceKotlinCollectionValue() {
-        runProcessorTest(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.RX3_PAGING_SOURCE)) { invocation ->
             val rx3PagingSourceElement =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE)
             val intType = invocation.processingEnv.requireType(Integer::class)
@@ -1317,7 +1341,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     inputSource,
@@ -1381,7 +1405,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     inputSource,
@@ -1441,7 +1465,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     inputSource,
@@ -1497,7 +1521,7 @@
                     """
                         .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     inputSource,
@@ -1570,7 +1594,7 @@
 
     @Test
     fun findDataSourceFactory() {
-        runProcessorTest(sources = listOf(COMMON.DATA_SOURCE_FACTORY)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(COMMON.DATA_SOURCE_FACTORY)) { invocation ->
             val pagedListProvider =
                 invocation.processingEnv.requireTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY)
             assertThat(pagedListProvider, notNullValue())
@@ -1653,7 +1677,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val converters =
                 CustomConverterProcessor(
                         context = invocation.context,
@@ -1789,7 +1813,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     classExtendsClassWithEqualsAndHashcodeFunctions,
@@ -1832,7 +1856,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources =
                 listOf(
                     inputSource,
@@ -1864,7 +1888,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val subjectTypeElement = invocation.processingEnv.requireTypeElement("Subject")
 
             subjectTypeElement.getDeclaredFields().forEach {
@@ -1879,7 +1903,10 @@
         val listOfInts = invocation.processingEnv.getDeclaredType(listElement, intType)
         val intListConverter =
             object :
-                SingleStatementTypeConverter(listOfInts, invocation.context.COMMON_TYPES.STRING) {
+                SingleStatementTypeConverter(
+                    listOfInts,
+                    invocation.context.processingEnv.requireType(CommonTypeNames.STRING)
+                ) {
                 override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock {
                     return XCodeBlock.of(
                         scope.language,
@@ -1892,7 +1919,10 @@
 
         val stringToIntListConverter =
             object :
-                SingleStatementTypeConverter(invocation.context.COMMON_TYPES.STRING, listOfInts) {
+                SingleStatementTypeConverter(
+                    invocation.context.processingEnv.requireType(CommonTypeNames.STRING),
+                    listOfInts
+                ) {
                 override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock {
                     return XCodeBlock.of(
                         scope.language,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
index c02a5de..d67ad7d 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
@@ -20,7 +20,7 @@
 import androidx.room.compiler.processing.XVariableElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
@@ -101,6 +101,6 @@
     }
 
     private fun runTest(handler: XTestInvocation.() -> Unit) {
-        runProcessorTest(sources = listOf(TEST_OBJECT)) { it.apply { handler() } }
+        runProcessorTestWithK1(sources = listOf(TEST_OBJECT)) { it.apply { handler() } }
     }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
index 10e445c..12ca821 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
@@ -19,8 +19,8 @@
 import androidx.kruth.assertThat
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.CustomConverterProcessor
+import androidx.room.runProcessorTestWithK1
 import androidx.room.solver.types.CompositeTypeConverter
 import androidx.room.solver.types.CustomTypeConverterWrapper
 import androidx.room.solver.types.TypeConverter
@@ -65,7 +65,7 @@
             """
                     .trimIndent()
             )
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val convertersElm = invocation.processingEnv.requireTypeElement("MyConverters")
             val converters = CustomConverterProcessor(invocation.context, convertersElm).process()
             val store =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
index ba3efa3..6da36ab 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
@@ -22,10 +22,10 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames.ROOM_SQL_QUERY
 import androidx.room.ext.RoomTypeNames.STRING_UTIL
 import androidx.room.processor.QueryMethodProcessor
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.writer.QueryWriter
 import org.junit.Test
@@ -361,7 +361,7 @@
     fun singleQueryMethod(vararg input: String, handler: (Boolean, QueryWriter) -> Unit) {
         val source =
             Source.java("foo.bar.MyClass", DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX)
-        runProcessorTest(sources = listOf(source)) { invocation ->
+        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
             val (owner, methods) =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
index 521e634..cdb11ac 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
@@ -19,7 +19,7 @@
 import androidx.kruth.assertThat
 import androidx.room.compiler.processing.util.CompilationTestCapabilities
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -58,7 +58,7 @@
             }
 
         var runCount = 0
-        runProcessorTest(sources = listOf(source)) {
+        runProcessorTestWithK1(sources = listOf(source)) {
             assertThat(it.processingEnv.findTypeElement("foo.bar.MyClass")).isNotNull()
             runCount++
         }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index 7f3e54e..95c2eb0 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -31,8 +31,6 @@
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.LifecyclesTypeNames
 import androidx.room.ext.ReactiveStreamsTypeNames
-import androidx.room.ext.RoomRxJava2TypeNames
-import androidx.room.ext.RoomRxJava3TypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
 import androidx.room.processor.DatabaseViewProcessor
@@ -123,9 +121,7 @@
         )
     }
 
-    val RX2_ROOM by lazy {
-        loadJavaCode("common/input/Rx2Room.java", RoomRxJava2TypeNames.RX_ROOM.canonicalName)
-    }
+    val RX2_ROOM by lazy { loadKotlinCode("common/input/Rx2Room.kt") }
 
     val RX3_FLOWABLE by lazy {
         loadJavaCode("common/input/rxjava3/Flowable.java", RxJava3TypeNames.FLOWABLE.canonicalName)
@@ -150,9 +146,7 @@
         )
     }
 
-    val RX3_ROOM by lazy {
-        loadJavaCode("common/input/Rx3Room.java", RoomRxJava3TypeNames.RX_ROOM.canonicalName)
-    }
+    val RX3_ROOM by lazy { loadKotlinCode("common/input/Rx3Room.kt") }
 
     val DATA_SOURCE_FACTORY by lazy { loadKotlinCode("common/input/DataSource.kt") }
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
index bd1705a..0cc8204 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
@@ -24,6 +24,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.Collate
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.parser.SqlParser
@@ -318,7 +319,9 @@
                             ),
                             field(
                                 "name",
-                                invocation.context.COMMON_TYPES.STRING,
+                                invocation.context.processingEnv.requireType(
+                                    CommonTypeNames.STRING
+                                ),
                                 SQLTypeAffinity.TEXT,
                                 defaultValue = "(NO_SUCH_CONSTANT)"
                             )
@@ -354,8 +357,16 @@
                         primitive(context, XTypeName.PRIMITIVE_INT),
                         SQLTypeAffinity.INTEGER
                     ),
-                    field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
-                    field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+                    field(
+                        "name",
+                        context.processingEnv.requireType(CommonTypeNames.STRING),
+                        SQLTypeAffinity.TEXT
+                    ),
+                    field(
+                        "lastName",
+                        context.processingEnv.requireType(CommonTypeNames.STRING),
+                        SQLTypeAffinity.TEXT
+                    ),
                     field(
                         "ratio",
                         primitive(context, XTypeName.PRIMITIVE_FLOAT),
@@ -372,7 +383,11 @@
                         primitive(context, XTypeName.PRIMITIVE_INT),
                         SQLTypeAffinity.INTEGER
                     ),
-                    field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT)
+                    field(
+                        "name",
+                        context.processingEnv.requireType(CommonTypeNames.STRING),
+                        SQLTypeAffinity.TEXT
+                    )
                 )
             )
         )
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
index 34b127f..b42941f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
@@ -21,9 +21,9 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runJavaProcessorTest
-import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.migration.bundle.FieldBundle
 import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
 import androidx.room.util.SchemaDiffResult
 import androidx.room.vo.AutoMigration
 import loadTestSource
@@ -88,7 +88,7 @@
                     )
             }
 
-        runProcessorTest(listOf(specSource)) { invocation ->
+        runProcessorTestWithK1(listOf(specSource)) { invocation ->
             val autoMigrationResultWithNewAddedColumn =
                 AutoMigration(
                     from = 1,
@@ -172,7 +172,7 @@
                     )
             }
 
-        runProcessorTest(listOf(specSource)) { invocation ->
+        runProcessorTestWithK1(listOf(specSource)) { invocation ->
             val autoMigrationResultWithNewAddedColumn =
                 AutoMigration(
                     from = 1,
@@ -264,7 +264,7 @@
                     )
             }
 
-        runProcessorTest(listOf(specSource)) { invocation ->
+        runProcessorTestWithK1(listOf(specSource)) { invocation ->
             val autoMigrationResultWithNewAddedColumn =
                 AutoMigration(
                     from = 1,
@@ -320,7 +320,7 @@
         }
     }
 
-    private fun runProcessorTest(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
+    private fun runProcessorTestWithK1(sources: List<Source>, handler: (XTestInvocation) -> Unit) {
         when (codeLanguage) {
             CodeLanguage.JAVA ->
                 runJavaProcessorTest(
@@ -330,7 +330,7 @@
                     handler = handler
                 )
             CodeLanguage.KOTLIN ->
-                runKspTest(
+                runKspTestWithK1(
                     sources = sources + kotlinDatabaseSource,
                     options =
                         mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
index 8385ac1..d30d126 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/BaseDaoKotlinCodeGenTest.kt
@@ -19,11 +19,10 @@
 import androidx.room.DatabaseProcessingStep
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
 import java.io.File
 import loadTestSource
-import org.jetbrains.kotlin.config.JvmDefaultMode
 import writeTestSource
 
 abstract class BaseDaoKotlinCodeGenTest {
@@ -35,14 +34,14 @@
         sources: List<Source>,
         expectedFilePath: String,
         compiledFiles: List<File> = emptyList(),
-        jvmDefaultMode: JvmDefaultMode = JvmDefaultMode.DEFAULT,
+        jvmDefaultMode: String = "disable",
         handler: (XTestInvocation) -> Unit = {}
     ) {
-        runKspTest(
+        runKspTestWithK1(
             sources = sources,
             classpath = compiledFiles,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
-            kotlincArguments = listOf("-Xjvm-default=${jvmDefaultMode.description}")
+            kotlincArguments = listOf("-Xjvm-default=${jvmDefaultMode}")
         ) {
             val databaseFqn = "androidx.room.Database"
             DatabaseProcessingStep()
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
index 02646fd..a4afa61 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -21,7 +21,6 @@
 import androidx.room.compiler.processing.util.compileFiles
 import com.google.testing.junit.testparameterinjector.TestParameter
 import com.google.testing.junit.testparameterinjector.TestParameterInjector
-import org.jetbrains.kotlin.config.JvmDefaultMode
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -1033,8 +1032,7 @@
 
     @Test
     fun delegatingFunctions_defaultImplBridge(
-        @TestParameter("DISABLE", "ALL_COMPATIBILITY", "ALL_INCOMPATIBLE")
-        jvmDefaultMode: JvmDefaultMode
+        @TestParameter("disable", "all-compatibility", "all") jvmDefaultMode: String
     ) {
         // For parametrized tests, use method name from reflection
         val testName = object {}.javaClass.enclosingMethod!!.name
@@ -1110,8 +1108,7 @@
 
     @Test
     fun transactionMethodAdapter_interface(
-        @TestParameter("DISABLE", "ALL_COMPATIBILITY", "ALL_INCOMPATIBLE")
-        jvmDefaultMode: JvmDefaultMode
+        @TestParameter("disable", "all-compatibility", "all") jvmDefaultMode: String
     ) {
         // For parametrized tests, use method name from reflection
         val testName = object {}.javaClass.enclosingMethod!!.name
@@ -1982,18 +1979,6 @@
 
                 @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
                 fun getMaybe(vararg arg: String?): Maybe<MyEntity>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getObservableNullable(vararg arg: String?): Observable<MyEntity?>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getSingleNullable(vararg arg: String?): Single<MyEntity?>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?>
             }
 
             @Entity
@@ -2010,13 +1995,19 @@
                 listOf(
                     src,
                     databaseSrc,
-                    COMMON.RX2_ROOM,
-                    COMMON.RX2_FLOWABLE,
-                    COMMON.RX2_OBSERVABLE,
-                    COMMON.RX2_SINGLE,
-                    COMMON.RX2_MAYBE,
-                    COMMON.PUBLISHER,
-                    COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+                ),
+            compiledFiles =
+                compileFiles(
+                    listOf(
+                        COMMON.RX2_ROOM,
+                        COMMON.RX2_FLOWABLE,
+                        COMMON.RX2_OBSERVABLE,
+                        COMMON.RX2_SINGLE,
+                        COMMON.RX2_MAYBE,
+                        COMMON.RX2_COMPLETABLE,
+                        COMMON.PUBLISHER,
+                        COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+                    )
                 ),
             expectedFilePath = getTestGoldenPath(testName.methodName)
         )
@@ -2044,18 +2035,6 @@
 
                 @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
                 fun getMaybe(vararg arg: String?): Maybe<MyEntity>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getObservableNullable(vararg arg: String?): Observable<MyEntity?>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getSingleNullable(vararg arg: String?): Single<MyEntity?>
-
-                @Query("SELECT * FROM MyEntity WHERE pk IN (:arg)")
-                fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?>
             }
 
             @Entity
@@ -2072,13 +2051,19 @@
                 listOf(
                     src,
                     databaseSrc,
-                    COMMON.RX3_ROOM,
-                    COMMON.RX3_FLOWABLE,
-                    COMMON.RX3_OBSERVABLE,
-                    COMMON.RX3_SINGLE,
-                    COMMON.RX3_MAYBE,
-                    COMMON.PUBLISHER,
-                    COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+                ),
+            compiledFiles =
+                compileFiles(
+                    listOf(
+                        COMMON.RX3_ROOM,
+                        COMMON.RX3_FLOWABLE,
+                        COMMON.RX3_OBSERVABLE,
+                        COMMON.RX3_SINGLE,
+                        COMMON.RX3_MAYBE,
+                        COMMON.RX3_COMPLETABLE,
+                        COMMON.PUBLISHER,
+                        COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+                    )
                 ),
             expectedFilePath = getTestGoldenPath(testName.methodName)
         )
@@ -2119,14 +2104,19 @@
                 listOf(
                     src,
                     databaseSrc,
-                    COMMON.RX2_ROOM,
-                    COMMON.RX2_FLOWABLE,
-                    COMMON.RX2_OBSERVABLE,
-                    COMMON.RX2_SINGLE,
-                    COMMON.RX2_MAYBE,
-                    COMMON.RX2_COMPLETABLE,
-                    COMMON.PUBLISHER,
-                    COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+                ),
+            compiledFiles =
+                compileFiles(
+                    listOf(
+                        COMMON.RX2_ROOM,
+                        COMMON.RX2_FLOWABLE,
+                        COMMON.RX2_OBSERVABLE,
+                        COMMON.RX2_SINGLE,
+                        COMMON.RX2_MAYBE,
+                        COMMON.RX2_COMPLETABLE,
+                        COMMON.PUBLISHER,
+                        COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION
+                    )
                 ),
             expectedFilePath = getTestGoldenPath(testName.methodName)
         )
@@ -2167,14 +2157,19 @@
                 listOf(
                     src,
                     databaseSrc,
-                    COMMON.RX3_ROOM,
-                    COMMON.RX3_FLOWABLE,
-                    COMMON.RX3_OBSERVABLE,
-                    COMMON.RX3_SINGLE,
-                    COMMON.RX3_MAYBE,
-                    COMMON.RX3_COMPLETABLE,
-                    COMMON.PUBLISHER,
-                    COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+                ),
+            compiledFiles =
+                compileFiles(
+                    listOf(
+                        COMMON.RX3_ROOM,
+                        COMMON.RX3_FLOWABLE,
+                        COMMON.RX3_OBSERVABLE,
+                        COMMON.RX3_SINGLE,
+                        COMMON.RX3_MAYBE,
+                        COMMON.RX3_COMPLETABLE,
+                        COMMON.PUBLISHER,
+                        COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+                    )
                 ),
             expectedFilePath = getTestGoldenPath(testName.methodName)
         )
@@ -2286,13 +2281,19 @@
                     .trimIndent()
             )
         runTest(
-            sources =
-                listOf(
-                    src,
-                    databaseSrc,
-                    COMMON.RX2_SINGLE,
-                    COMMON.RX2_COMPLETABLE,
-                    COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION,
+            sources = listOf(src, databaseSrc),
+            compiledFiles =
+                compileFiles(
+                    listOf(
+                        COMMON.RX2_ROOM,
+                        COMMON.RX2_SINGLE,
+                        COMMON.RX2_MAYBE,
+                        COMMON.RX2_COMPLETABLE,
+                        COMMON.RX2_FLOWABLE,
+                        COMMON.RX2_OBSERVABLE,
+                        COMMON.RX2_EMPTY_RESULT_SET_EXCEPTION,
+                        COMMON.PUBLISHER,
+                    )
                 ),
             expectedFilePath = getTestGoldenPath(testName.methodName)
         )
@@ -2344,13 +2345,19 @@
                     .trimIndent()
             )
         runTest(
-            sources =
-                listOf(
-                    src,
-                    databaseSrc,
-                    COMMON.RX3_SINGLE,
-                    COMMON.RX3_COMPLETABLE,
-                    COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION
+            sources = listOf(src, databaseSrc),
+            compiledFiles =
+                compileFiles(
+                    listOf(
+                        COMMON.RX3_ROOM,
+                        COMMON.RX3_SINGLE,
+                        COMMON.RX3_MAYBE,
+                        COMMON.RX3_COMPLETABLE,
+                        COMMON.RX3_FLOWABLE,
+                        COMMON.RX3_OBSERVABLE,
+                        COMMON.RX3_EMPTY_RESULT_SET_EXCEPTION,
+                        COMMON.PUBLISHER,
+                    )
                 ),
             expectedFilePath = getTestGoldenPath(testName.methodName)
         )
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
index 9679f86..3f13507 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
@@ -23,9 +23,9 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.DaoProcessor
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import com.google.testing.junit.testparameterinjector.TestParameter
 import com.google.testing.junit.testparameterinjector.TestParameterInjector
@@ -148,10 +148,10 @@
                     COMMON.PUBLISHER
                 )
             )
-        runProcessorTest(sources = sources, classpath = libs) { invocation ->
+        runProcessorTestWithK1(sources = sources, classpath = libs) { invocation ->
             if (invocation.isKsp && !javaLambdaSyntaxAvailable) {
                 // Skip KSP backend without lambda syntax, it is a nonsensical combination.
-                return@runProcessorTest
+                return@runProcessorTestWithK1
             }
             val dao =
                 invocation.roundEnv
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt
index b13bd03..b3bc884 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseKotlinCodeGenTest.kt
@@ -19,8 +19,8 @@
 import androidx.room.DatabaseProcessingStep
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
 import loadTestSource
 import org.junit.Rule
 import org.junit.Test
@@ -245,7 +245,7 @@
         expectedFilePath: String,
         handler: (XTestInvocation) -> Unit = {}
     ) {
-        runKspTest(
+        runKspTestWithK1(
             sources = sources,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
index c912440..5eaf944 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
@@ -19,8 +19,8 @@
 import androidx.room.DatabaseProcessingStep
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.processor.Context
+import androidx.room.runKspTestWithK1
 import loadTestSource
 import org.junit.Rule
 import org.junit.Test
@@ -72,7 +72,7 @@
         expectedFilePath: String,
         handler: (XTestInvocation) -> Unit = {}
     ) {
-        runKspTest(
+        runKspTestWithK1(
             sources = sources,
             options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
         ) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
index eb077c0..2632fe5 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
@@ -22,8 +22,8 @@
 import androidx.room.compiler.processing.util.CompilationResultSubject
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.Context
+import androidx.room.runProcessorTestWithK1
 import androidx.testutils.generateAllEnumerations
 import loadTestSource
 import org.junit.Test
@@ -159,7 +159,7 @@
                 COMMON.LISTENABLE_FUTURE
             )
         )
-    runProcessorTest(
+    runProcessorTestWithK1(
         sources = sources,
         classpath = libs,
         options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
index 68ca39f..b237147 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DefaultsInDaoTest.kt
@@ -21,13 +21,12 @@
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.processor.DaoProcessor
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import com.google.common.truth.StringSubject
 import createVerifierFromEntitiesAndViews
-import org.jetbrains.kotlin.config.JvmDefaultMode
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -42,11 +41,11 @@
  * For Java default method tests, we have DefaultDaoMethodsTest in TestApp.
  */
 @RunWith(Parameterized::class)
-class DefaultsInDaoTest(private val jvmDefaultMode: JvmDefaultMode) {
+class DefaultsInDaoTest(private val jvmDefaultMode: String) {
     @Test
     fun abstractDao() {
         val defaultWithCompatibilityAnnotation =
-            if (jvmDefaultMode == JvmDefaultMode.ALL_COMPATIBILITY) {
+            if (jvmDefaultMode == "all-compatibility") {
                 "@JvmDefaultWithoutCompatibility"
             } else {
                 ""
@@ -101,7 +100,7 @@
             )
         compileInEachDefaultsMode(source) { generated ->
             generated.contains("public void upsert(final User obj)")
-            if (jvmDefaultMode == JvmDefaultMode.DISABLE) {
+            if (jvmDefaultMode == "disable") {
                 generated.contains("SubjectDao.DefaultImpls.upsert(SubjectDao_Impl.this")
             } else {
                 generated.contains("SubjectDao.super.upsert(")
@@ -137,7 +136,7 @@
                 "public Object upsert(final User obj, " +
                     "final Continuation<? super Unit> \$completion)"
             )
-            if (jvmDefaultMode == JvmDefaultMode.DISABLE) {
+            if (jvmDefaultMode == "disable") {
                 generated.contains("SubjectDao.DefaultImpls.upsert(SubjectDao_Impl.this")
             } else {
                 generated.contains("SubjectDao.super.upsert(")
@@ -179,11 +178,10 @@
         jvmTarget: String = "1.8",
         handler: (StringSubject) -> Unit
     ) {
-        runProcessorTest(
+        runProcessorTestWithK1(
             sources = listOf(source, COMMON.COROUTINES_ROOM, COMMON.ROOM_DATABASE_KTX),
             javacArguments = listOf("-source", jvmTarget),
-            kotlincArguments =
-                listOf("-jvm-target=$jvmTarget", "-Xjvm-default=${jvmDefaultMode.description}")
+            kotlincArguments = listOf("-jvm-target=$jvmTarget", "-Xjvm-default=${jvmDefaultMode}")
         ) { invocation ->
             invocation.roundEnv
                 .getElementsAnnotatedWith(androidx.room.Dao::class.qualifiedName!!)
@@ -223,9 +221,9 @@
         @Parameters(name = "jvmDefaultMode={0}")
         fun modes() =
             listOf(
-                JvmDefaultMode.ALL_COMPATIBILITY,
-                JvmDefaultMode.ALL_INCOMPATIBLE,
-                JvmDefaultMode.DISABLE,
+                "all-compatibility",
+                "all",
+                "disable",
             )
     }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
index 5e9aa20..26f7d0b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/OpenDelegateWriterTest.kt
@@ -19,8 +19,8 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
-import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.DatabaseProcessor
+import androidx.room.runProcessorTestWithK1
 import androidx.room.testing.context
 import androidx.room.vo.Database
 import org.hamcrest.CoreMatchers.`is`
@@ -219,7 +219,7 @@
             }
             """
             )
-        runProcessorTest(sources = sources + databaseCode) { invocation ->
+        runProcessorTestWithK1(sources = sources + databaseCode) { invocation ->
             val db =
                 invocation.roundEnv
                     .getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx2Room.java b/room/room-compiler/src/test/test-data/common/input/Rx2Room.java
deleted file mode 100644
index 9e06920..0000000
--- a/room/room-compiler/src/test/test-data/common/input/Rx2Room.java
+++ /dev/null
@@ -1,39 +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.
- */
-
-// mock rx2 helper
-package androidx.room;
-
-import androidx.room.RoomDatabase;
-import java.util.concurrent.Callable;
-import io.reactivex.Flowable;
-import io.reactivex.Observable;
-import io.reactivex.Single;
-public class RxRoom {
-    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
-            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
-        return null;
-    }
-
-    public static <T> Observable<T> createObservable(final RoomDatabase database,
-            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
-        return null;
-    }
-
-    public static <T> Single<T> createSingle(final Callable<? extends T> callable) {
-        return null;
-    }
-}
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx2Room.kt b/room/room-compiler/src/test/test-data/common/input/Rx2Room.kt
new file mode 100644
index 0000000..771a7c1
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/common/input/Rx2Room.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 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.room
+
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+import java.util.concurrent.Callable
+
+// mock rx2 helper
+ class RxRoom {
+
+    companion object {
+
+        @JvmField
+        val NOTHING: Any = Any()
+
+        @JvmStatic
+        fun <T : Any> createFlowable(
+            db: RoomDatabase,
+            inTransaction: Boolean,
+            tableNames: Array<String>,
+            block: (SQLiteConnection) -> T?
+        ): Flowable<T> {
+            TODO()
+        }
+
+        @JvmStatic
+        fun <T : Any> createObservable(
+            db: RoomDatabase,
+            inTransaction: Boolean,
+            tableNames: Array<String>,
+            block: (SQLiteConnection) -> T?
+        ): Observable<T> {
+            TODO()
+        }
+
+        @JvmStatic
+        fun <T : Any> createMaybe(
+            db: RoomDatabase,
+            isReadOnly: Boolean,
+            inTransaction: Boolean,
+            block: (SQLiteConnection) -> T?
+        ): Maybe<T> {
+            TODO()
+        }
+
+        @JvmStatic
+        fun createCompletable(
+            db: RoomDatabase,
+            isReadOnly: Boolean,
+            inTransaction: Boolean,
+            block: (SQLiteConnection) -> Unit
+        ): Completable {
+            TODO()
+        }
+
+        @JvmStatic
+        fun <T : Any> createSingle(
+            db: RoomDatabase,
+            isReadOnly: Boolean,
+            inTransaction: Boolean,
+            block: (SQLiteConnection) -> T?
+        ): Single<T> {
+            TODO()
+        }
+
+        @JvmStatic
+        fun createFlowable(database: RoomDatabase, vararg tableNames: String): Flowable<Any> {
+            TODO()
+        }
+
+        @JvmStatic
+        fun createObservable(database: RoomDatabase, vararg tableNames: String): Observable<Any> {
+            TODO()
+        }
+    }
+}
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx3Room.java b/room/room-compiler/src/test/test-data/common/input/Rx3Room.java
deleted file mode 100644
index 518609d..0000000
--- a/room/room-compiler/src/test/test-data/common/input/Rx3Room.java
+++ /dev/null
@@ -1,40 +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.
- */
-
-// mock rx3 helper
-package androidx.room.rxjava3;
-
-import androidx.room.RoomDatabase;
-import java.util.concurrent.Callable;
-import io.reactivex.rxjava3.core.Flowable;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-
-public class RxRoom {
-    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
-            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
-        return null;
-    }
-
-    public static <T> Observable<T> createObservable(final RoomDatabase database,
-            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
-        return null;
-    }
-
-    public static <T> Single<T> createSingle(final Callable<? extends T> callable) {
-        return null;
-    }
-}
diff --git a/room/room-compiler/src/test/test-data/common/input/Rx3Room.kt b/room/room-compiler/src/test/test-data/common/input/Rx3Room.kt
new file mode 100644
index 0000000..663e479
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/common/input/Rx3Room.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// mock rx2 helper
+@file:JvmName("RxRoom")
+
+package androidx.room.rxjava3
+
+import androidx.room.RoomDatabase
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Flowable
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import java.util.concurrent.Callable
+
+class Rx3RoomArtifactMarker private constructor()
+
+@JvmField
+val NOTHING: Any = Any()
+
+fun <T : Any> createFlowable(
+    db: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    block: (SQLiteConnection) -> T?
+): Flowable<T> {
+    TODO()
+}
+
+fun <T : Any> createObservable(
+    db: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    block: (SQLiteConnection) -> T?
+): Observable<T> {
+    TODO()
+}
+
+fun <T : Any> createMaybe(
+    db: RoomDatabase,
+    isReadOnly: Boolean,
+    inTransaction: Boolean,
+    block: (SQLiteConnection) -> T?
+): Maybe<T> {
+    TODO()
+}
+
+fun createCompletable(
+    db: RoomDatabase,
+    isReadOnly: Boolean,
+    inTransaction: Boolean,
+    block: (SQLiteConnection) -> Unit
+): Completable {
+    TODO()
+}
+
+fun <T : Any> createSingle(
+    db: RoomDatabase,
+    isReadOnly: Boolean,
+    inTransaction: Boolean,
+    block: (SQLiteConnection) -> T?
+): Single<T> {
+    TODO()
+}
+
+fun createFlowable(
+    database: RoomDatabase,
+    vararg tableNames: String
+): Flowable<Any> {
+    TODO()
+}
+
+fun <T : Any> createFlowable(
+    database: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    callable: Callable<out T>
+): Flowable<T> {
+    TODO()
+}
+
+fun createObservable(
+    database: RoomDatabase,
+    vararg tableNames: String
+): Observable<Any> {
+    TODO()
+}
+
+fun <T : Any> createObservable(
+    database: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    callable: Callable<out T>
+): Observable<T> {
+    TODO()
+}
+
+fun <T : Any> createSingle(callable: Callable<out T>): Single<T> {
+    TODO()
+}
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
index 0a79ba3..0fa55d4 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/ComplexDao.java
@@ -2,10 +2,8 @@
 
 import android.database.Cursor;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.lifecycle.LiveData;
 import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
 import androidx.room.guava.GuavaRoom;
 import androidx.room.util.CursorUtil;
 import androidx.room.util.DBUtil;
@@ -15,7 +13,6 @@
 import androidx.sqlite.db.SupportSQLiteQuery;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
@@ -24,7 +21,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
 
 @Generated("androidx.room.RoomProcessor")
@@ -387,48 +383,38 @@
   @Override
   public LiveData<User> getByIdLive(final int id) {
     final String _sql = "SELECT * FROM user where uid = ?";
-    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-    int _argIndex = 1;
-    _statement.bindLong(_argIndex, id);
-    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
-      @Override
-      @Nullable
-      public User call() throws Exception {
-        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-        try {
-          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-          final User _result;
-          if (_cursor.moveToFirst()) {
-            _result = new User();
-            _result.uid = _cursor.getInt(_cursorIndexOfUid);
-            if (_cursor.isNull(_cursorIndexOfName)) {
-              _result.name = null;
-            } else {
-              _result.name = _cursor.getString(_cursorIndexOfName);
-            }
-            final String _tmpLastName;
-            if (_cursor.isNull(_cursorIndexOfLastName)) {
-              _tmpLastName = null;
-            } else {
-              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-            }
-            _result.setLastName(_tmpLastName);
-            _result.age = _cursor.getInt(_cursorIndexOfAge);
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, id);
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final User _result;
+        if (_stmt.step()) {
+          _result = new User();
+          _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _result.name = null;
           } else {
-            _result = null;
+            _result.name = _stmt.getText(_cursorIndexOfName);
           }
-          return _result;
-        } finally {
-          _cursor.close();
+          final String _tmpLastName;
+          if (_stmt.isNull(_cursorIndexOfLastName)) {
+            _tmpLastName = null;
+          } else {
+            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          }
+          _result.setLastName(_tmpLastName);
+          _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+        } else {
+          _result = null;
         }
-      }
-
-      @Override
-      protected void finalize() {
-        _statement.release();
+        return _result;
+      } finally {
+        _stmt.close();
       }
     });
   }
@@ -441,56 +427,45 @@
     StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
     _stringBuilder.append(")");
     final String _sql = _stringBuilder.toString();
-    final int _argCount = 0 + _inputSize;
-    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-    int _argIndex = 1;
-    if (ids == null) {
-      _statement.bindNull(_argIndex);
-    } else {
-      for (int _item : ids) {
-        _statement.bindLong(_argIndex, _item);
-        _argIndex++;
-      }
-    }
-    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
-      @Override
-      @Nullable
-      public List<User> call() throws Exception {
-        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-        try {
-          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-          final List<User> _result = new ArrayList<User>();
-          while (_cursor.moveToNext()) {
-            final User _item_1;
-            _item_1 = new User();
-            _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
-            if (_cursor.isNull(_cursorIndexOfName)) {
-              _item_1.name = null;
-            } else {
-              _item_1.name = _cursor.getString(_cursorIndexOfName);
-            }
-            final String _tmpLastName;
-            if (_cursor.isNull(_cursorIndexOfLastName)) {
-              _tmpLastName = null;
-            } else {
-              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-            }
-            _item_1.setLastName(_tmpLastName);
-            _item_1.age = _cursor.getInt(_cursorIndexOfAge);
-            _result.add(_item_1);
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item : ids) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
           }
-          return _result;
-        } finally {
-          _cursor.close();
         }
-      }
-
-      @Override
-      protected void finalize() {
-        _statement.release();
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final List<User> _result = new ArrayList<User>();
+        while (_stmt.step()) {
+          final User _item_1;
+          _item_1 = new User();
+          _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _item_1.name = null;
+          } else {
+            _item_1.name = _stmt.getText(_cursorIndexOfName);
+          }
+          final String _tmpLastName;
+          if (_stmt.isNull(_cursorIndexOfLastName)) {
+            _tmpLastName = null;
+          } else {
+            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          }
+          _item_1.setLastName(_tmpLastName);
+          _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+          _result.add(_item_1);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
       }
     });
   }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
index 996eaf5..12f1fc8 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/DeletionDao.java
@@ -1,30 +1,26 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
 import androidx.room.util.DBUtil;
 import androidx.room.util.SQLiteConnectionUtil;
 import androidx.room.util.StringUtil;
 import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.StringBuilder;
 import java.lang.SuppressWarnings;
-import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
+import kotlin.Unit;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -33,8 +29,6 @@
 
   private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
 
-  private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
-
   private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
 
   private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
@@ -53,18 +47,6 @@
         statement.bindLong(1, entity.uid);
       }
     };
-    this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      @NonNull
-      protected String createQuery() {
-        return "DELETE FROM `User` WHERE `uid` = ?";
-      }
-
-      @Override
-      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
-        statement.bindLong(1, entity.uid);
-      }
-    };
     this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
       @Override
       @NonNull
@@ -164,57 +146,27 @@
 
   @Override
   public Completable deleteUserCompletable(final User user) {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        __db.beginTransaction();
-        try {
-          __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user);
+      return Unit.INSTANCE;
     });
   }
 
   @Override
   public Single<Integer> deleteUserSingle(final User user) {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
   @Override
   public Maybe<Integer> deleteUserMaybe(final User user) {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
@@ -254,66 +206,48 @@
 
   @Override
   public Completable deleteByUidCompletable(final int uid) {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
         _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
-        try {
-          _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
+        _stmt.step();
+        return Unit.INSTANCE;
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Single<Integer> deleteByUidSingle(final int uid) {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
         _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Maybe<Integer> deleteByUidMaybe(final int uid) {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
         _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
index 6214715..4f809d6 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withLambda/UpdateDao.java
@@ -1,27 +1,24 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
 import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
 import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
-import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
+import kotlin.Unit;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -32,8 +29,6 @@
 
   private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
 
-  private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
-
   private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
 
   private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
@@ -88,30 +83,6 @@
         statement.bindLong(5, entity.uid);
       }
     };
-    this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      @NonNull
-      protected String createQuery() {
-        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-      }
-
-      @Override
-      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
-        statement.bindLong(1, entity.uid);
-        if (entity.name == null) {
-          statement.bindNull(2);
-        } else {
-          statement.bindString(2, entity.name);
-        }
-        if (entity.getLastName() == null) {
-          statement.bindNull(3);
-        } else {
-          statement.bindString(3, entity.getLastName());
-        }
-        statement.bindLong(4, entity.age);
-        statement.bindLong(5, entity.uid);
-      }
-    };
     this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
       @Override
       @NonNull
@@ -232,57 +203,27 @@
 
   @Override
   public Completable updateUserAndReturnCountCompletable(final User user) {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        __db.beginTransaction();
-        try {
-          __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user);
+      return Unit.INSTANCE;
     });
   }
 
   @Override
   public Single<Integer> updateUserAndReturnCountSingle(final User user) {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
   @Override
   public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
@@ -340,60 +281,42 @@
 
   @Override
   public Completable ageUserAllCompletable() {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
-        try {
-          _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return Unit.INSTANCE;
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Single<Integer> ageUserAllSingle() {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Maybe<Integer> ageUserAllMaybe() {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
index 27a0bc1..8074e12 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/ComplexDao.java
@@ -5,7 +5,6 @@
 import androidx.annotation.Nullable;
 import androidx.lifecycle.LiveData;
 import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
 import androidx.room.guava.GuavaRoom;
 import androidx.room.util.CursorUtil;
 import androidx.room.util.DBUtil;
@@ -17,7 +16,6 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import java.lang.Boolean;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
@@ -26,7 +24,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
 import kotlin.jvm.functions.Function1;
 
@@ -426,49 +423,43 @@
   @Override
   public LiveData<User> getByIdLive(final int id) {
     final String _sql = "SELECT * FROM user where uid = ?";
-    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-    int _argIndex = 1;
-    _statement.bindLong(_argIndex, id);
-    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Function1<SQLiteConnection, User>() {
       @Override
       @Nullable
-      public User call() throws Exception {
-        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+      public User invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, id);
+          final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+          final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+          final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+          final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
           final User _result;
-          if (_cursor.moveToFirst()) {
+          if (_stmt.step()) {
             _result = new User();
-            _result.uid = _cursor.getInt(_cursorIndexOfUid);
-            if (_cursor.isNull(_cursorIndexOfName)) {
+            _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+            if (_stmt.isNull(_cursorIndexOfName)) {
               _result.name = null;
             } else {
-              _result.name = _cursor.getString(_cursorIndexOfName);
+              _result.name = _stmt.getText(_cursorIndexOfName);
             }
             final String _tmpLastName;
-            if (_cursor.isNull(_cursorIndexOfLastName)) {
+            if (_stmt.isNull(_cursorIndexOfLastName)) {
               _tmpLastName = null;
             } else {
-              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+              _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
             }
             _result.setLastName(_tmpLastName);
-            _result.age = _cursor.getInt(_cursorIndexOfAge);
+            _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
           } else {
             _result = null;
           }
           return _result;
         } finally {
-          _cursor.close();
+          _stmt.close();
         }
       }
-
-      @Override
-      protected void finalize() {
-        _statement.release();
-      }
     });
   }
 
@@ -480,57 +471,50 @@
     StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
     _stringBuilder.append(")");
     final String _sql = _stringBuilder.toString();
-    final int _argCount = 0 + _inputSize;
-    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-    int _argIndex = 1;
-    if (ids == null) {
-      _statement.bindNull(_argIndex);
-    } else {
-      for (int _item : ids) {
-        _statement.bindLong(_argIndex, _item);
-        _argIndex++;
-      }
-    }
-    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Function1<SQLiteConnection, List<User>>() {
       @Override
       @Nullable
-      public List<User> call() throws Exception {
-        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
+      public List<User> invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
+          int _argIndex = 1;
+          if (ids == null) {
+            _stmt.bindNull(_argIndex);
+          } else {
+            for (int _item : ids) {
+              _stmt.bindLong(_argIndex, _item);
+              _argIndex++;
+            }
+          }
+          final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+          final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+          final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+          final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
           final List<User> _result = new ArrayList<User>();
-          while (_cursor.moveToNext()) {
+          while (_stmt.step()) {
             final User _item_1;
             _item_1 = new User();
-            _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
-            if (_cursor.isNull(_cursorIndexOfName)) {
+            _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+            if (_stmt.isNull(_cursorIndexOfName)) {
               _item_1.name = null;
             } else {
-              _item_1.name = _cursor.getString(_cursorIndexOfName);
+              _item_1.name = _stmt.getText(_cursorIndexOfName);
             }
             final String _tmpLastName;
-            if (_cursor.isNull(_cursorIndexOfLastName)) {
+            if (_stmt.isNull(_cursorIndexOfLastName)) {
               _tmpLastName = null;
             } else {
-              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+              _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
             }
             _item_1.setLastName(_tmpLastName);
-            _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+            _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
             _result.add(_item_1);
           }
           return _result;
         } finally {
-          _cursor.close();
+          _stmt.close();
         }
       }
-
-      @Override
-      protected void finalize() {
-        _statement.release();
-      }
     });
   }
 
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
index e287d17..bbbac5e 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/DeletionDao.java
@@ -3,19 +3,17 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
 import androidx.room.util.DBUtil;
 import androidx.room.util.SQLiteConnectionUtil;
 import androidx.room.util.StringUtil;
 import androidx.sqlite.SQLiteConnection;
 import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
@@ -24,8 +22,8 @@
 import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
+import kotlin.Unit;
 import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
@@ -35,8 +33,6 @@
 
   private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
 
-  private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
-
   private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
 
   private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
@@ -55,18 +51,6 @@
         statement.bindLong(1, entity.uid);
       }
     };
-    this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      @NonNull
-      protected String createQuery() {
-        return "DELETE FROM `User` WHERE `uid` = ?";
-      }
-
-      @Override
-      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
-        statement.bindLong(1, entity.uid);
-      }
-    };
     this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
       @Override
       @NonNull
@@ -194,56 +178,38 @@
 
   @Override
   public Completable deleteUserCompletable(final User user) {
-    return Completable.fromCallable(new Callable<Void>() {
+    return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
       @Override
-      @Nullable
-      public Void call() throws Exception {
-        __db.beginTransaction();
-        try {
-          __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
+      @NonNull
+      public Unit invoke(@NonNull final SQLiteConnection _connection) {
+        __deleteAdapterOfUser.handle(_connection, user);
+        return Unit.INSTANCE;
       }
     });
   }
 
   @Override
   public Single<Integer> deleteUserSingle(final User user) {
-    return Single.fromCallable(new Callable<Integer>() {
+    return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __deleteAdapterOfUser.handle(_connection, user);
+        return _result;
       }
     });
   }
 
   @Override
   public Maybe<Integer> deleteUserMaybe(final User user) {
-    return Maybe.fromCallable(new Callable<Integer>() {
+    return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __deleteAdapterOfUser.handle(_connection, user);
+        return _result;
       }
     });
   }
@@ -296,21 +262,19 @@
 
   @Override
   public Completable deleteByUidCompletable(final int uid) {
-    return Completable.fromCallable(new Callable<Void>() {
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
       @Override
-      @Nullable
-      public Void call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        int _argIndex = 1;
-        _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
+      @NonNull
+      public Unit invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return null;
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, uid);
+          _stmt.step();
+          return Unit.INSTANCE;
         } finally {
-          __db.endTransaction();
+          _stmt.close();
         }
       }
     });
@@ -318,21 +282,19 @@
 
   @Override
   public Single<Integer> deleteByUidSingle(final int uid) {
-    return Single.fromCallable(new Callable<Integer>() {
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        int _argIndex = 1;
-        _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, uid);
+          _stmt.step();
+          return SQLiteConnectionUtil.getTotalChangedRows(_connection);
         } finally {
-          __db.endTransaction();
+          _stmt.close();
         }
       }
     });
@@ -340,21 +302,19 @@
 
   @Override
   public Maybe<Integer> deleteByUidMaybe(final int uid) {
-    return Maybe.fromCallable(new Callable<Integer>() {
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        int _argIndex = 1;
-        _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
+          int _argIndex = 1;
+          _stmt.bindLong(_argIndex, uid);
+          _stmt.step();
+          return SQLiteConnectionUtil.getTotalChangedRows(_connection);
         } finally {
-          __db.endTransaction();
+          _stmt.close();
         }
       }
     });
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
index f3ca3df..8bbaf88 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/withoutLambda/UpdateDao.java
@@ -3,17 +3,16 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
 import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
 import androidx.sqlite.SQLiteConnection;
 import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
@@ -21,8 +20,8 @@
 import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
+import kotlin.Unit;
 import kotlin.jvm.functions.Function1;
 
 @Generated("androidx.room.RoomProcessor")
@@ -34,8 +33,6 @@
 
   private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
 
-  private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
-
   private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
 
   private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
@@ -90,30 +87,6 @@
         statement.bindLong(5, entity.uid);
       }
     };
-    this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      @NonNull
-      protected String createQuery() {
-        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-      }
-
-      @Override
-      protected void bind(@NonNull final SupportSQLiteStatement statement, final User entity) {
-        statement.bindLong(1, entity.uid);
-        if (entity.name == null) {
-          statement.bindNull(2);
-        } else {
-          statement.bindString(2, entity.name);
-        }
-        if (entity.getLastName() == null) {
-          statement.bindNull(3);
-        } else {
-          statement.bindString(3, entity.getLastName());
-        }
-        statement.bindLong(4, entity.age);
-        statement.bindLong(5, entity.uid);
-      }
-    };
     this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
       @Override
       @NonNull
@@ -266,56 +239,38 @@
 
   @Override
   public Completable updateUserAndReturnCountCompletable(final User user) {
-    return Completable.fromCallable(new Callable<Void>() {
+    return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
       @Override
-      @Nullable
-      public Void call() throws Exception {
-        __db.beginTransaction();
-        try {
-          __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
+      @NonNull
+      public Unit invoke(@NonNull final SQLiteConnection _connection) {
+        __updateAdapterOfUser.handle(_connection, user);
+        return Unit.INSTANCE;
       }
     });
   }
 
   @Override
   public Single<Integer> updateUserAndReturnCountSingle(final User user) {
-    return Single.fromCallable(new Callable<Integer>() {
+    return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __updateAdapterOfUser.handle(_connection, user);
+        return _result;
       }
     });
   }
 
   @Override
   public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
-    return Maybe.fromCallable(new Callable<Integer>() {
+    return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        int _result = 0;
+        _result += __updateAdapterOfUser.handle(_connection, user);
+        return _result;
       }
     });
   }
@@ -390,19 +345,17 @@
 
   @Override
   public Completable ageUserAllCompletable() {
-    return Completable.fromCallable(new Callable<Void>() {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createCompletable(__db, false, true, new Function1<SQLiteConnection, Unit>() {
       @Override
-      @Nullable
-      public Void call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
+      @NonNull
+      public Unit invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return null;
+          _stmt.step();
+          return Unit.INSTANCE;
         } finally {
-          __db.endTransaction();
+          _stmt.close();
         }
       }
     });
@@ -410,19 +363,17 @@
 
   @Override
   public Single<Integer> ageUserAllSingle() {
-    return Single.fromCallable(new Callable<Integer>() {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createSingle(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
+          _stmt.step();
+          return SQLiteConnectionUtil.getTotalChangedRows(_connection);
         } finally {
-          __db.endTransaction();
+          _stmt.close();
         }
       }
     });
@@ -430,19 +381,17 @@
 
   @Override
   public Maybe<Integer> ageUserAllMaybe() {
-    return Maybe.fromCallable(new Callable<Integer>() {
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createMaybe(__db, false, true, new Function1<SQLiteConnection, Integer>() {
       @Override
       @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
+      public Integer invoke(@NonNull final SQLiteConnection _connection) {
+        final SQLiteStatement _stmt = _connection.prepare(_sql);
         try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
+          _stmt.step();
+          return SQLiteConnectionUtil.getTotalChangedRows(_connection);
         } finally {
-          __db.endTransaction();
+          _stmt.close();
         }
       }
     });
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
index a876756..54daec0 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
@@ -2,10 +2,8 @@
 
 import android.database.Cursor;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.lifecycle.LiveData;
 import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
 import androidx.room.guava.GuavaRoom;
 import androidx.room.util.CursorUtil;
 import androidx.room.util.DBUtil;
@@ -15,7 +13,6 @@
 import androidx.sqlite.db.SupportSQLiteQuery;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
@@ -24,7 +21,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
 
 @Generated("androidx.room.RoomProcessor")
@@ -383,48 +379,38 @@
   @Override
   public LiveData<User> getByIdLive(final int id) {
     final String _sql = "SELECT * FROM user where uid = ?";
-    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-    int _argIndex = 1;
-    _statement.bindLong(_argIndex, id);
-    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<User>() {
-      @Override
-      @Nullable
-      public User call() throws Exception {
-        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-        try {
-          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-          final User _result;
-          if (_cursor.moveToFirst()) {
-            _result = new User();
-            _result.uid = _cursor.getInt(_cursorIndexOfUid);
-            if (_cursor.isNull(_cursorIndexOfName)) {
-              _result.name = null;
-            } else {
-              _result.name = _cursor.getString(_cursorIndexOfName);
-            }
-            final String _tmpLastName;
-            if (_cursor.isNull(_cursorIndexOfLastName)) {
-              _tmpLastName = null;
-            } else {
-              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-            }
-            _result.setLastName(_tmpLastName);
-            _result.age = _cursor.getInt(_cursorIndexOfAge);
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        _stmt.bindLong(_argIndex, id);
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final User _result;
+        if (_stmt.step()) {
+          _result = new User();
+          _result.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _result.name = null;
           } else {
-            _result = null;
+            _result.name = _stmt.getText(_cursorIndexOfName);
           }
-          return _result;
-        } finally {
-          _cursor.close();
+          final String _tmpLastName;
+          if (_stmt.isNull(_cursorIndexOfLastName)) {
+            _tmpLastName = null;
+          } else {
+            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          }
+          _result.setLastName(_tmpLastName);
+          _result.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+        } else {
+          _result = null;
         }
-      }
-
-      @Override
-      protected void finalize() {
-        _statement.release();
+        return _result;
+      } finally {
+        _stmt.close();
       }
     });
   }
@@ -437,56 +423,45 @@
     StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
     _stringBuilder.append(")");
     final String _sql = _stringBuilder.toString();
-    final int _argCount = 0 + _inputSize;
-    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-    int _argIndex = 1;
-    if (ids == null) {
-      _statement.bindNull(_argIndex);
-    } else {
-      for (int _item : ids) {
-        _statement.bindLong(_argIndex, _item);
-        _argIndex++;
-      }
-    }
-    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, new Callable<List<User>>() {
-      @Override
-      @Nullable
-      public List<User> call() throws Exception {
-        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
-        try {
-          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
-          final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
-          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
-          final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "ageColumn");
-          final List<User> _result = new ArrayList<User>();
-          while (_cursor.moveToNext()) {
-            final User _item_1;
-            _item_1 = new User();
-            _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
-            if (_cursor.isNull(_cursorIndexOfName)) {
-              _item_1.name = null;
-            } else {
-              _item_1.name = _cursor.getString(_cursorIndexOfName);
-            }
-            final String _tmpLastName;
-            if (_cursor.isNull(_cursorIndexOfLastName)) {
-              _tmpLastName = null;
-            } else {
-              _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-            }
-            _item_1.setLastName(_tmpLastName);
-            _item_1.age = _cursor.getInt(_cursorIndexOfAge);
-            _result.add(_item_1);
+    return __db.getInvalidationTracker().createLiveData(new String[] {"user"}, false, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        int _argIndex = 1;
+        if (ids == null) {
+          _stmt.bindNull(_argIndex);
+        } else {
+          for (int _item : ids) {
+            _stmt.bindLong(_argIndex, _item);
+            _argIndex++;
           }
-          return _result;
-        } finally {
-          _cursor.close();
         }
-      }
-
-      @Override
-      protected void finalize() {
-        _statement.release();
+        final int _cursorIndexOfUid = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "uid");
+        final int _cursorIndexOfName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "name");
+        final int _cursorIndexOfLastName = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "lastName");
+        final int _cursorIndexOfAge = SQLiteStatementUtil.getColumnIndexOrThrow(_stmt, "ageColumn");
+        final List<User> _result = new ArrayList<User>();
+        while (_stmt.step()) {
+          final User _item_1;
+          _item_1 = new User();
+          _item_1.uid = (int) (_stmt.getLong(_cursorIndexOfUid));
+          if (_stmt.isNull(_cursorIndexOfName)) {
+            _item_1.name = null;
+          } else {
+            _item_1.name = _stmt.getText(_cursorIndexOfName);
+          }
+          final String _tmpLastName;
+          if (_stmt.isNull(_cursorIndexOfLastName)) {
+            _tmpLastName = null;
+          } else {
+            _tmpLastName = _stmt.getText(_cursorIndexOfLastName);
+          }
+          _item_1.setLastName(_tmpLastName);
+          _item_1.age = (int) (_stmt.getLong(_cursorIndexOfAge));
+          _result.add(_item_1);
+        }
+        return _result;
+      } finally {
+        _stmt.close();
       }
     });
   }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
index 4d0654d..d81f936 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/DeletionDao.java
@@ -1,30 +1,26 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
 import androidx.room.util.DBUtil;
 import androidx.room.util.SQLiteConnectionUtil;
 import androidx.room.util.StringUtil;
 import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.StringBuilder;
 import java.lang.SuppressWarnings;
-import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
+import kotlin.Unit;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -33,8 +29,6 @@
 
   private final EntityDeleteOrUpdateAdapter<User> __deleteAdapterOfUser;
 
-  private final EntityDeletionOrUpdateAdapter<User> __deleteCompatAdapterOfUser;
-
   private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __deleteAdapterOfMultiPKeyEntity;
 
   private final EntityDeleteOrUpdateAdapter<Book> __deleteAdapterOfBook;
@@ -53,19 +47,6 @@
         statement.bindLong(1, entity.uid);
       }
     };
-    this.__deleteCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      @NonNull
-      protected String createQuery() {
-        return "DELETE FROM `User` WHERE `uid` = ?";
-      }
-
-      @Override
-      protected void bind(@NonNull final SupportSQLiteStatement statement,
-          @NonNull final User entity) {
-        statement.bindLong(1, entity.uid);
-      }
-    };
     this.__deleteAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
       @Override
       @NonNull
@@ -158,57 +139,27 @@
 
   @Override
   public Completable deleteUserCompletable(final User user) {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        __db.beginTransaction();
-        try {
-          __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      __deleteAdapterOfUser.handle(_connection, user);
+      return Unit.INSTANCE;
     });
   }
 
   @Override
   public Single<Integer> deleteUserSingle(final User user) {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
   @Override
   public Maybe<Integer> deleteUserMaybe(final User user) {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __deleteCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __deleteAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
@@ -248,66 +199,48 @@
 
   @Override
   public Completable deleteByUidCompletable(final int uid) {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
         _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
-        try {
-          _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
+        _stmt.step();
+        return Unit.INSTANCE;
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Single<Integer> deleteByUidSingle(final int uid) {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
         _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Maybe<Integer> deleteByUidMaybe(final int uid) {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "DELETE FROM user where uid = ?";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+    final String _sql = "DELETE FROM user where uid = ?";
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
         int _argIndex = 1;
         _stmt.bindLong(_argIndex, uid);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
index a94f1d5..41ba21c 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/UpdateDao.java
@@ -1,27 +1,24 @@
 package foo.bar;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.room.EntityDeleteOrUpdateAdapter;
-import androidx.room.EntityDeletionOrUpdateAdapter;
 import androidx.room.RoomDatabase;
+import androidx.room.RxRoom;
 import androidx.room.util.DBUtil;
+import androidx.room.util.SQLiteConnectionUtil;
 import androidx.sqlite.SQLiteStatement;
-import androidx.sqlite.db.SupportSQLiteStatement;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import java.lang.Class;
-import java.lang.Exception;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
-import java.lang.Void;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Callable;
 import javax.annotation.processing.Generated;
+import kotlin.Unit;
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation", "removal"})
@@ -32,8 +29,6 @@
 
   private final EntityDeleteOrUpdateAdapter<User> __updateAdapterOfUser_1;
 
-  private final EntityDeletionOrUpdateAdapter<User> __updateCompatAdapterOfUser;
-
   private final EntityDeleteOrUpdateAdapter<MultiPKeyEntity> __updateAdapterOfMultiPKeyEntity;
 
   private final EntityDeleteOrUpdateAdapter<Book> __updateAdapterOfBook;
@@ -88,31 +83,6 @@
         statement.bindLong(5, entity.uid);
       }
     };
-    this.__updateCompatAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      @NonNull
-      protected String createQuery() {
-        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-      }
-
-      @Override
-      protected void bind(@NonNull final SupportSQLiteStatement statement,
-          @NonNull final User entity) {
-        statement.bindLong(1, entity.uid);
-        if (entity.name == null) {
-          statement.bindNull(2);
-        } else {
-          statement.bindString(2, entity.name);
-        }
-        if (entity.getLastName() == null) {
-          statement.bindNull(3);
-        } else {
-          statement.bindString(3, entity.getLastName());
-        }
-        statement.bindLong(4, entity.age);
-        statement.bindLong(5, entity.uid);
-      }
-    };
     this.__updateAdapterOfMultiPKeyEntity = new EntityDeleteOrUpdateAdapter<MultiPKeyEntity>() {
       @Override
       @NonNull
@@ -218,57 +188,27 @@
 
   @Override
   public Completable updateUserAndReturnCountCompletable(final User user) {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        __db.beginTransaction();
-        try {
-          __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      __updateAdapterOfUser.handle(_connection, user);
+      return Unit.INSTANCE;
     });
   }
 
   @Override
   public Single<Integer> updateUserAndReturnCountSingle(final User user) {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
   @Override
   public Maybe<Integer> updateUserAndReturnCountMaybe(final User user) {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        int _total = 0;
-        __db.beginTransaction();
-        try {
-          _total += __updateCompatAdapterOfUser.handle(user);
-          __db.setTransactionSuccessful();
-          return _total;
-        } finally {
-          __db.endTransaction();
-        }
-      }
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      int _result = 0;
+      _result += __updateAdapterOfUser.handle(_connection, user);
+      return _result;
     });
   }
 
@@ -326,60 +266,42 @@
 
   @Override
   public Completable ageUserAllCompletable() {
-    return Completable.fromCallable(new Callable<Void>() {
-      @Override
-      @Nullable
-      public Void call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
-        try {
-          _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return null;
-        } finally {
-          __db.endTransaction();
-        }
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createCompletable(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return Unit.INSTANCE;
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Single<Integer> ageUserAllSingle() {
-    return Single.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createSingle(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
 
   @Override
   public Maybe<Integer> ageUserAllMaybe() {
-    return Maybe.fromCallable(new Callable<Integer>() {
-      @Override
-      @Nullable
-      public Integer call() throws Exception {
-        final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
-        final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-        __db.beginTransaction();
-        try {
-          final Integer _result = _stmt.executeUpdateDelete();
-          __db.setTransactionSuccessful();
-          return _result;
-        } finally {
-          __db.endTransaction();
-        }
+    final String _sql = "UPDATE User SET ageColumn = ageColumn + 1";
+    return RxRoom.createMaybe(__db, false, true, (_connection) -> {
+      final SQLiteStatement _stmt = _connection.prepare(_sql);
+      try {
+        _stmt.step();
+        return SQLiteConnectionUtil.getTotalChangedRows(_connection);
+      } finally {
+        _stmt.close();
       }
     });
   }
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
index 3ac3f8a..18e3a2b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx2.kt
@@ -1,17 +1,15 @@
-import android.database.Cursor
-import androidx.room.EmptyResultSetException
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
-import androidx.room.RxRoom
+import androidx.room.RxRoom.Companion.createFlowable
+import androidx.room.RxRoom.Companion.createMaybe
+import androidx.room.RxRoom.Companion.createObservable
+import androidx.room.RxRoom.Companion.createSingle
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
+import androidx.sqlite.SQLiteStatement
 import io.reactivex.Flowable
 import io.reactivex.Maybe
 import io.reactivex.Observable
 import io.reactivex.Single
-import java.util.concurrent.Callable
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
@@ -37,43 +35,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
-      public override fun call(): MyEntity {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createFlowable(__db, false, arrayOf("MyEntity")) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getObservable(vararg arg: String?): Observable<MyEntity> {
@@ -83,43 +73,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
-      public override fun call(): MyEntity {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createObservable(__db, false, arrayOf("MyEntity")) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getSingle(vararg arg: String?): Single<MyEntity> {
@@ -129,46 +111,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createSingle(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createSingle(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            _result = null
+            _stmt.bindText(_argIndex, _item)
           }
-          if (_result == null) {
-            throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
-          }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity?
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          _result = null
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getMaybe(vararg arg: String?): Maybe<MyEntity> {
@@ -178,230 +149,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return Maybe.fromCallable(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createMaybe(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            _result = null
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity?
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          _result = null
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
     }
-    return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getObservableNullable(vararg arg: String?): Observable<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getSingleNullable(vararg arg: String?): Single<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createSingle(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          if (_result == null) {
-            throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return Maybe.fromCallable(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
   }
 
   public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
index 491de60..463fa8b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/callableQuery_rx3.kt
@@ -1,17 +1,15 @@
-import android.database.Cursor
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
-import androidx.room.rxjava3.EmptyResultSetException
-import androidx.room.rxjava3.RxRoom
+import androidx.room.rxjava3.createFlowable
+import androidx.room.rxjava3.createMaybe
+import androidx.room.rxjava3.createObservable
+import androidx.room.rxjava3.createSingle
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
+import androidx.sqlite.SQLiteStatement
 import io.reactivex.rxjava3.core.Flowable
 import io.reactivex.rxjava3.core.Maybe
 import io.reactivex.rxjava3.core.Observable
 import io.reactivex.rxjava3.core.Single
-import java.util.concurrent.Callable
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
@@ -37,43 +35,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
-      public override fun call(): MyEntity {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createFlowable(__db, false, arrayOf("MyEntity")) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getObservable(vararg arg: String?): Observable<MyEntity> {
@@ -83,43 +73,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
-      public override fun call(): MyEntity {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createObservable(__db, false, arrayOf("MyEntity")) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getSingle(vararg arg: String?): Single<MyEntity> {
@@ -129,46 +111,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createSingle(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createSingle(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            _result = null
+            _stmt.bindText(_argIndex, _item)
           }
-          if (_result == null) {
-            throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
-          }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity?
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          _result = null
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getMaybe(vararg arg: String?): Maybe<MyEntity> {
@@ -178,230 +149,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return Maybe.fromCallable(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createMaybe(__db, true, false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            _result = null
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity?
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          _result = null
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getFlowableNullable(vararg arg: String?): Flowable<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
     }
-    return RxRoom.createFlowable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getObservableNullable(vararg arg: String?): Observable<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createObservable(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getSingleNullable(vararg arg: String?): Single<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return RxRoom.createSingle(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          if (_result == null) {
-            throw EmptyResultSetException("Query returned empty result set: " + _statement.sql)
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
-  }
-
-  public override fun getMaybeNullable(vararg arg: String?): Maybe<MyEntity?> {
-    val _stringBuilder: StringBuilder = StringBuilder()
-    _stringBuilder.append("SELECT * FROM MyEntity WHERE pk IN (")
-    val _inputSize: Int = arg.size
-    appendPlaceholders(_stringBuilder, _inputSize)
-    _stringBuilder.append(")")
-    val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return Maybe.fromCallable(object : Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
-          } else {
-            _result = null
-          }
-          return _result
-        } finally {
-          _cursor.close()
-        }
-      }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
   }
 
   public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
index 8a831c0..be0e2f6 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/liveDataCallable.kt
@@ -1,12 +1,8 @@
-import android.database.Cursor
 import androidx.lifecycle.LiveData
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndexOrThrow
-import androidx.room.util.query
-import java.util.concurrent.Callable
+import androidx.sqlite.SQLiteStatement
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.String
@@ -32,44 +28,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false, object :
-        Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            _result = null
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity?
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          _result = null
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getLiveDataNullable(vararg arg: String?): LiveData<MyEntity?> {
@@ -79,44 +66,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false, object :
-        Callable<MyEntity?> {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return __db.invalidationTracker.createLiveData(arrayOf("MyEntity"), false) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            _result = null
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity?
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          _result = null
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public companion object {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
index e3f48cc..fbc5b8f2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx2.kt
@@ -1,10 +1,12 @@
 import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.RxRoom.Companion.createCompletable
+import androidx.room.RxRoom.Companion.createMaybe
+import androidx.room.RxRoom.Companion.createSingle
+import androidx.room.util.getLastInsertedRowId
+import androidx.sqlite.SQLiteStatement
 import io.reactivex.Completable
 import io.reactivex.Maybe
 import io.reactivex.Single
-import java.lang.Void
-import java.util.concurrent.Callable
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
@@ -23,65 +25,55 @@
     this.__db = __db
   }
 
-  public override fun insertPublisherSingle(id: String, name: String): Single<Long> =
-      Single.fromCallable(object : Callable<Long?> {
-    public override fun call(): Long? {
-      val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
-      val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
-      var _argIndex: Int = 1
-      _stmt.bindString(_argIndex, id)
-      _argIndex = 2
-      _stmt.bindString(_argIndex, name)
-      __db.beginTransaction()
+  public override fun insertPublisherSingle(id: String, name: String): Single<Long> {
+    val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+    return createSingle(__db, false, true) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _result: Long? = _stmt.executeInsert()
-        __db.setTransactionSuccessful()
-        return _result
+        var _argIndex: Int = 1
+        _stmt.bindText(_argIndex, id)
+        _argIndex = 2
+        _stmt.bindText(_argIndex, name)
+        _stmt.step()
+        getLastInsertedRowId(_connection)
       } finally {
-        __db.endTransaction()
+        _stmt.close()
       }
     }
-  })
+  }
 
-  public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> =
-      Maybe.fromCallable(object : Callable<Long?> {
-    public override fun call(): Long? {
-      val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
-      val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
-      var _argIndex: Int = 1
-      _stmt.bindString(_argIndex, id)
-      _argIndex = 2
-      _stmt.bindString(_argIndex, name)
-      __db.beginTransaction()
+  public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> {
+    val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+    return createMaybe(__db, false, true) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _result: Long? = _stmt.executeInsert()
-        __db.setTransactionSuccessful()
-        return _result
+        var _argIndex: Int = 1
+        _stmt.bindText(_argIndex, id)
+        _argIndex = 2
+        _stmt.bindText(_argIndex, name)
+        _stmt.step()
+        getLastInsertedRowId(_connection)
       } finally {
-        __db.endTransaction()
+        _stmt.close()
       }
     }
-  })
+  }
 
-  public override fun insertPublisherCompletable(id: String, name: String): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
-      val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
-      var _argIndex: Int = 1
-      _stmt.bindString(_argIndex, id)
-      _argIndex = 2
-      _stmt.bindString(_argIndex, name)
-      __db.beginTransaction()
+  public override fun insertPublisherCompletable(id: String, name: String): Completable {
+    val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+    return createCompletable(__db, false, true) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        _stmt.executeInsert()
-        __db.setTransactionSuccessful()
-        return null
+        var _argIndex: Int = 1
+        _stmt.bindText(_argIndex, id)
+        _argIndex = 2
+        _stmt.bindText(_argIndex, name)
+        _stmt.step()
       } finally {
-        __db.endTransaction()
+        _stmt.close()
       }
     }
-  })
+  }
 
   public companion object {
     public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
index 85d36b7..91a3312 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedCallableQuery_rx3.kt
@@ -1,10 +1,12 @@
 import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.rxjava3.createCompletable
+import androidx.room.rxjava3.createMaybe
+import androidx.room.rxjava3.createSingle
+import androidx.room.util.getLastInsertedRowId
+import androidx.sqlite.SQLiteStatement
 import io.reactivex.rxjava3.core.Completable
 import io.reactivex.rxjava3.core.Maybe
 import io.reactivex.rxjava3.core.Single
-import java.lang.Void
-import java.util.concurrent.Callable
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
@@ -23,65 +25,55 @@
     this.__db = __db
   }
 
-  public override fun insertPublisherSingle(id: String, name: String): Single<Long> =
-      Single.fromCallable(object : Callable<Long?> {
-    public override fun call(): Long? {
-      val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
-      val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
-      var _argIndex: Int = 1
-      _stmt.bindString(_argIndex, id)
-      _argIndex = 2
-      _stmt.bindString(_argIndex, name)
-      __db.beginTransaction()
+  public override fun insertPublisherSingle(id: String, name: String): Single<Long> {
+    val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+    return createSingle(__db, false, true) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _result: Long? = _stmt.executeInsert()
-        __db.setTransactionSuccessful()
-        return _result
+        var _argIndex: Int = 1
+        _stmt.bindText(_argIndex, id)
+        _argIndex = 2
+        _stmt.bindText(_argIndex, name)
+        _stmt.step()
+        getLastInsertedRowId(_connection)
       } finally {
-        __db.endTransaction()
+        _stmt.close()
       }
     }
-  })
+  }
 
-  public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> =
-      Maybe.fromCallable(object : Callable<Long?> {
-    public override fun call(): Long? {
-      val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
-      val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
-      var _argIndex: Int = 1
-      _stmt.bindString(_argIndex, id)
-      _argIndex = 2
-      _stmt.bindString(_argIndex, name)
-      __db.beginTransaction()
+  public override fun insertPublisherMaybe(id: String, name: String): Maybe<Long> {
+    val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+    return createMaybe(__db, false, true) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _result: Long? = _stmt.executeInsert()
-        __db.setTransactionSuccessful()
-        return _result
+        var _argIndex: Int = 1
+        _stmt.bindText(_argIndex, id)
+        _argIndex = 2
+        _stmt.bindText(_argIndex, name)
+        _stmt.step()
+        getLastInsertedRowId(_connection)
       } finally {
-        __db.endTransaction()
+        _stmt.close()
       }
     }
-  })
+  }
 
-  public override fun insertPublisherCompletable(id: String, name: String): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
-      val _stmt: SupportSQLiteStatement = __db.compileStatement(_sql)
-      var _argIndex: Int = 1
-      _stmt.bindString(_argIndex, id)
-      _argIndex = 2
-      _stmt.bindString(_argIndex, name)
-      __db.beginTransaction()
+  public override fun insertPublisherCompletable(id: String, name: String): Completable {
+    val _sql: String = "INSERT INTO MyEntity (pk, other) VALUES (?, ?)"
+    return createCompletable(__db, false, true) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        _stmt.executeInsert()
-        __db.setTransactionSuccessful()
-        return null
+        var _argIndex: Int = 1
+        _stmt.bindText(_argIndex, id)
+        _argIndex = 2
+        _stmt.bindText(_argIndex, name)
+        _stmt.step()
       } finally {
-        __db.endTransaction()
+        _stmt.close()
       }
     }
-  })
+  }
 
   public companion object {
     public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
index 57b5fce..34749e2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx2.kt
@@ -1,12 +1,12 @@
-import androidx.room.EntityDeletionOrUpdateAdapter
-import androidx.room.EntityInsertionAdapter
-import androidx.room.EntityUpsertionAdapter
+import androidx.room.EntityDeleteOrUpdateAdapter
+import androidx.room.EntityInsertAdapter
+import androidx.room.EntityUpsertAdapter
 import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.RxRoom.Companion.createCompletable
+import androidx.room.RxRoom.Companion.createSingle
+import androidx.sqlite.SQLiteStatement
 import io.reactivex.Completable
 import io.reactivex.Single
-import java.lang.Void
-import java.util.concurrent.Callable
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
@@ -22,175 +22,109 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
 
-  private val __deleteCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __deleteAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
 
-  private val __updateCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __updateAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
 
-  private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+  private val __upsertAdapterOfMyEntity: EntityUpsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
       }
     }
-    this.__deleteCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__deleteAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
       }
     }
-    this.__updateCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__updateAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
         statement.bindLong(3, entity.pk.toLong())
       }
     }
-    this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
-        EntityInsertionAdapter<MyEntity>(__db) {
+    this.__upsertAdapterOfMyEntity = EntityUpsertAdapter<MyEntity>(object :
+        EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
       }
-    }, object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    }, object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
         statement.bindLong(3, entity.pk.toLong())
       }
     })
   }
 
   public override fun insertSingle(vararg entities: MyEntity): Single<List<Long>> =
-      Single.fromCallable(object : Callable<List<Long>?> {
-    public override fun call(): List<Long>? {
-      __db.beginTransaction()
-      try {
-        val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
-        __db.setTransactionSuccessful()
-        return _result
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createSingle(__db, false, true) { _connection ->
+    val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(_connection,
+        entities)
+    _result
+  }
 
   public override fun insertCompletable(vararg entities: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __insertionAdapterOfMyEntity.insert(entities)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createCompletable(__db, false, true) { _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, entities)
+  }
 
-  public override fun deleteSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
-      Callable<Int?> {
-    public override fun call(): Int? {
-      var _total: Int = 0
-      __db.beginTransaction()
-      try {
-        _total += __deleteCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return _total
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun deleteSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+      { _connection ->
+    var _result: Int = 0
+    _result += __deleteAdapterOfMyEntity.handle(_connection, entity)
+    _result
+  }
 
-  public override fun deleteCompletable(entity: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __deleteCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun deleteCompletable(entity: MyEntity): Completable = createCompletable(__db,
+      false, true) { _connection ->
+    __deleteAdapterOfMyEntity.handle(_connection, entity)
+  }
 
-  public override fun updateSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
-      Callable<Int?> {
-    public override fun call(): Int? {
-      var _total: Int = 0
-      __db.beginTransaction()
-      try {
-        _total += __updateCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return _total
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun updateSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+      { _connection ->
+    var _result: Int = 0
+    _result += __updateAdapterOfMyEntity.handle(_connection, entity)
+    _result
+  }
 
-  public override fun updateCompletable(entity: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __updateCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun updateCompletable(entity: MyEntity): Completable = createCompletable(__db,
+      false, true) { _connection ->
+    __updateAdapterOfMyEntity.handle(_connection, entity)
+  }
 
   public override fun upsertSingle(vararg entities: MyEntity): Single<List<Long>> =
-      Single.fromCallable(object : Callable<List<Long>?> {
-    public override fun call(): List<Long>? {
-      __db.beginTransaction()
-      try {
-        val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
-        __db.setTransactionSuccessful()
-        return _result
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createSingle(__db, false, true) { _connection ->
+    val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(_connection,
+        entities)
+    _result
+  }
 
   public override fun upsertCompletable(vararg entities: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __upsertionAdapterOfMyEntity.upsert(entities)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createCompletable(__db, false, true) { _connection ->
+    __upsertAdapterOfMyEntity.upsert(_connection, entities)
+  }
 
   public companion object {
     public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
index e64428f..6867046 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/shortcutMethods_rx3.kt
@@ -1,12 +1,12 @@
-import androidx.room.EntityDeletionOrUpdateAdapter
-import androidx.room.EntityInsertionAdapter
-import androidx.room.EntityUpsertionAdapter
+import androidx.room.EntityDeleteOrUpdateAdapter
+import androidx.room.EntityInsertAdapter
+import androidx.room.EntityUpsertAdapter
 import androidx.room.RoomDatabase
-import androidx.sqlite.db.SupportSQLiteStatement
+import androidx.room.rxjava3.createCompletable
+import androidx.room.rxjava3.createSingle
+import androidx.sqlite.SQLiteStatement
 import io.reactivex.rxjava3.core.Completable
 import io.reactivex.rxjava3.core.Single
-import java.lang.Void
-import java.util.concurrent.Callable
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
@@ -22,175 +22,109 @@
 ) : MyDao {
   private val __db: RoomDatabase
 
-  private val __insertionAdapterOfMyEntity: EntityInsertionAdapter<MyEntity>
+  private val __insertAdapterOfMyEntity: EntityInsertAdapter<MyEntity>
 
-  private val __deleteCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __deleteAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
 
-  private val __updateCompatAdapterOfMyEntity: EntityDeletionOrUpdateAdapter<MyEntity>
+  private val __updateAdapterOfMyEntity: EntityDeleteOrUpdateAdapter<MyEntity>
 
-  private val __upsertionAdapterOfMyEntity: EntityUpsertionAdapter<MyEntity>
+  private val __upsertAdapterOfMyEntity: EntityUpsertAdapter<MyEntity>
   init {
     this.__db = __db
-    this.__insertionAdapterOfMyEntity = object : EntityInsertionAdapter<MyEntity>(__db) {
+    this.__insertAdapterOfMyEntity = object : EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT OR ABORT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
       }
     }
-    this.__deleteCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__deleteAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String = "DELETE FROM `MyEntity` WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
       }
     }
-    this.__updateCompatAdapterOfMyEntity = object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    this.__updateAdapterOfMyEntity = object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE OR ABORT `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
         statement.bindLong(3, entity.pk.toLong())
       }
     }
-    this.__upsertionAdapterOfMyEntity = EntityUpsertionAdapter<MyEntity>(object :
-        EntityInsertionAdapter<MyEntity>(__db) {
+    this.__upsertAdapterOfMyEntity = EntityUpsertAdapter<MyEntity>(object :
+        EntityInsertAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "INSERT INTO `MyEntity` (`pk`,`other`) VALUES (?,?)"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
       }
-    }, object : EntityDeletionOrUpdateAdapter<MyEntity>(__db) {
+    }, object : EntityDeleteOrUpdateAdapter<MyEntity>() {
       protected override fun createQuery(): String =
           "UPDATE `MyEntity` SET `pk` = ?,`other` = ? WHERE `pk` = ?"
 
-      protected override fun bind(statement: SupportSQLiteStatement, entity: MyEntity) {
+      protected override fun bind(statement: SQLiteStatement, entity: MyEntity) {
         statement.bindLong(1, entity.pk.toLong())
-        statement.bindString(2, entity.other)
+        statement.bindText(2, entity.other)
         statement.bindLong(3, entity.pk.toLong())
       }
     })
   }
 
   public override fun insertSingle(vararg entities: MyEntity): Single<List<Long>> =
-      Single.fromCallable(object : Callable<List<Long>?> {
-    public override fun call(): List<Long>? {
-      __db.beginTransaction()
-      try {
-        val _result: List<Long>? = __insertionAdapterOfMyEntity.insertAndReturnIdsList(entities)
-        __db.setTransactionSuccessful()
-        return _result
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createSingle(__db, false, true) { _connection ->
+    val _result: List<Long>? = __insertAdapterOfMyEntity.insertAndReturnIdsList(_connection,
+        entities)
+    _result
+  }
 
   public override fun insertCompletable(vararg entities: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __insertionAdapterOfMyEntity.insert(entities)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createCompletable(__db, false, true) { _connection ->
+    __insertAdapterOfMyEntity.insert(_connection, entities)
+  }
 
-  public override fun deleteSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
-      Callable<Int?> {
-    public override fun call(): Int? {
-      var _total: Int = 0
-      __db.beginTransaction()
-      try {
-        _total += __deleteCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return _total
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun deleteSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+      { _connection ->
+    var _result: Int = 0
+    _result += __deleteAdapterOfMyEntity.handle(_connection, entity)
+    _result
+  }
 
-  public override fun deleteCompletable(entity: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __deleteCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun deleteCompletable(entity: MyEntity): Completable = createCompletable(__db,
+      false, true) { _connection ->
+    __deleteAdapterOfMyEntity.handle(_connection, entity)
+  }
 
-  public override fun updateSingle(entity: MyEntity): Single<Int> = Single.fromCallable(object :
-      Callable<Int?> {
-    public override fun call(): Int? {
-      var _total: Int = 0
-      __db.beginTransaction()
-      try {
-        _total += __updateCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return _total
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun updateSingle(entity: MyEntity): Single<Int> = createSingle(__db, false, true)
+      { _connection ->
+    var _result: Int = 0
+    _result += __updateAdapterOfMyEntity.handle(_connection, entity)
+    _result
+  }
 
-  public override fun updateCompletable(entity: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __updateCompatAdapterOfMyEntity.handle(entity)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+  public override fun updateCompletable(entity: MyEntity): Completable = createCompletable(__db,
+      false, true) { _connection ->
+    __updateAdapterOfMyEntity.handle(_connection, entity)
+  }
 
   public override fun upsertSingle(vararg entities: MyEntity): Single<List<Long>> =
-      Single.fromCallable(object : Callable<List<Long>?> {
-    public override fun call(): List<Long>? {
-      __db.beginTransaction()
-      try {
-        val _result: List<Long>? = __upsertionAdapterOfMyEntity.upsertAndReturnIdsList(entities)
-        __db.setTransactionSuccessful()
-        return _result
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createSingle(__db, false, true) { _connection ->
+    val _result: List<Long>? = __upsertAdapterOfMyEntity.upsertAndReturnIdsList(_connection,
+        entities)
+    _result
+  }
 
   public override fun upsertCompletable(vararg entities: MyEntity): Completable =
-      Completable.fromCallable(object : Callable<Void?> {
-    public override fun call(): Void? {
-      __db.beginTransaction()
-      try {
-        __upsertionAdapterOfMyEntity.upsert(entities)
-        __db.setTransactionSuccessful()
-        return null
-      } finally {
-        __db.endTransaction()
-      }
-    }
-  })
+      createCompletable(__db, false, true) { _connection ->
+    __upsertAdapterOfMyEntity.upsert(_connection, entities)
+  }
 
   public companion object {
     public fun getRequiredConverters(): List<KClass<*>> = emptyList()
diff --git a/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt b/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt
index 3690e65..c31091d 100644
--- a/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt
+++ b/room/room-gradle-plugin/src/test/test-data/multiplatform-project/src/jvmMain/kotlin/room/testapp/MyDatabase.kt
@@ -19,13 +19,10 @@
 import androidx.room.*
 
 @Database(entities = [JvmEntity::class], version = 1)
-@ConstructedBy(MyDatabaseCtor::class)
 abstract class MyDatabase : RoomDatabase() {
     abstract fun getMyDao(): MyDao
 }
 
-expect object MyDatabaseCtor : RoomDatabaseConstructor<MyDatabase>
-
 @Entity
 data class JvmEntity(
     @PrimaryKey val id: Long
diff --git a/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt b/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt
index db0f5ae..7bd8f30 100644
--- a/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt
+++ b/room/room-paging-rxjava2/src/main/java/androidx/room/paging/rxjava2/LimitOffsetRxPagingSource.kt
@@ -24,7 +24,7 @@
 import androidx.paging.rxjava2.RxPagingSource
 import androidx.room.RoomDatabase
 import androidx.room.RoomSQLiteQuery
-import androidx.room.RxRoom.createSingle
+import androidx.room.RxRoom
 import androidx.room.paging.util.INITIAL_ITEM_COUNT
 import androidx.room.paging.util.INVALID
 import androidx.room.paging.util.ThreadSafeInvalidationObserver
@@ -56,7 +56,7 @@
 
     override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Value>> {
         val scheduler = Schedulers.from(db.queryExecutor)
-        return createSingle {
+        return RxRoom.createSingle {
                 observer.registerIfNecessary(db)
                 val tempCount = itemCount.get()
                 if (tempCount == INITIAL_ITEM_COUNT) {
diff --git a/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt b/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt
index 6067c44..a2792a0 100644
--- a/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt
+++ b/room/room-paging-rxjava3/src/main/java/androidx/room/paging/rxjava3/LimitOffsetRxPagingSource.kt
@@ -30,7 +30,7 @@
 import androidx.room.paging.util.getClippedRefreshKey
 import androidx.room.paging.util.queryDatabase
 import androidx.room.paging.util.queryItemCount
-import androidx.room.rxjava3.RxRoom.createSingle
+import androidx.room.rxjava3.createSingle
 import androidx.sqlite.db.SupportSQLiteQuery
 import io.reactivex.rxjava3.core.Single
 import io.reactivex.rxjava3.schedulers.Schedulers
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index 5a6a8e5..2799d77 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -140,6 +140,7 @@
     method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer observer);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void addWeakObserver(androidx.room.InvalidationTracker.Observer observer);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, boolean inTransaction, java.util.concurrent.Callable<T?> computeFunction);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> computeFunction);
     method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, java.util.concurrent.Callable<T?> computeFunction);
     method public final void refreshAsync();
     method public void refreshVersionsAsync();
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
index b9f5b2a..1673b96 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationLiveDataContainer.android.kt
@@ -17,6 +17,7 @@
 package androidx.room
 
 import androidx.lifecycle.LiveData
+import androidx.sqlite.SQLiteConnection
 import java.util.Collections
 import java.util.IdentityHashMap
 import java.util.concurrent.Callable
@@ -33,9 +34,31 @@
     fun <T> create(
         tableNames: Array<out String>,
         inTransaction: Boolean,
-        computeFunction: Callable<T?>
+        callableFunction: Callable<T?>
     ): LiveData<T> {
-        return RoomTrackingLiveData(database, this, inTransaction, computeFunction, tableNames)
+        return RoomTrackingLiveData(
+            database = database,
+            container = this,
+            inTransaction = inTransaction,
+            callableFunction = callableFunction,
+            lambdaFunction = null,
+            tableNames = tableNames
+        )
+    }
+
+    fun <T> create(
+        tableNames: Array<out String>,
+        inTransaction: Boolean,
+        lambdaFunction: (SQLiteConnection) -> T?
+    ): LiveData<T> {
+        return RoomTrackingLiveData(
+            database = database,
+            container = this,
+            inTransaction = inTransaction,
+            callableFunction = null,
+            lambdaFunction = lambdaFunction,
+            tableNames = tableNames
+        )
     }
 
     fun onActive(liveData: LiveData<*>) {
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
index f4b7057..b030dab 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
@@ -342,6 +342,33 @@
         return invalidationLiveDataContainer.create(tableNames, inTransaction, computeFunction)
     }
 
+    /**
+     * Creates a LiveData that computes the given function once and for every other invalidation of
+     * the database.
+     *
+     * Holds a strong reference to the created LiveData as long as it is active.
+     *
+     * @param tableNames The list of tables to observe
+     * @param inTransaction True if the computeFunction will be done in a transaction, false
+     *   otherwise.
+     * @param computeFunction The function that calculates the value
+     * @param T The return type
+     * @return A new LiveData that computes the given function when the given list of tables
+     *   invalidates.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    fun <T> createLiveData(
+        tableNames: Array<out String>,
+        inTransaction: Boolean,
+        computeFunction: (SQLiteConnection) -> T?
+    ): LiveData<T> {
+        // Validate names early to fail fast as actual observer subscription is done once LiveData
+        // is observed.
+        implementation.validateTableNames(tableNames)
+        // TODO(329315924): Could we use createFlow(...).asLiveData() ?
+        return invalidationLiveDataContainer.create(tableNames, inTransaction, computeFunction)
+    }
+
     internal fun initMultiInstanceInvalidation(
         context: Context,
         name: String,
@@ -399,7 +426,7 @@
      *
      * This class will automatically unsubscribe when the wrapped observer goes out of memory.
      */
-    private class WeakObserver(
+    internal class WeakObserver(
         val tracker: InvalidationTracker,
         val coroutineScope: CoroutineScope,
         delegate: Observer
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
index 8fa3649..9450a2b 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomTrackingLiveData.android.kt
@@ -15,13 +15,13 @@
  */
 package androidx.room
 
-import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.lifecycle.LiveData
-import java.lang.Exception
-import java.lang.RuntimeException
+import androidx.room.util.performSuspending
+import androidx.sqlite.SQLiteConnection
 import java.util.concurrent.Callable
-import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * A LiveData implementation that closely works with [InvalidationTracker] to implement database
@@ -36,24 +36,32 @@
  * [InvalidationTracker] as long as it is active.
  */
 internal class RoomTrackingLiveData<T>(
-    val database: RoomDatabase,
+    private val database: RoomDatabase,
     private val container: InvalidationLiveDataContainer,
-    val inTransaction: Boolean,
-    val computeFunction: Callable<T?>,
+    private val inTransaction: Boolean,
+    private val callableFunction: Callable<T?>?,
+    private val lambdaFunction: ((SQLiteConnection) -> T?)?,
     tableNames: Array<out String>
 ) : LiveData<T>() {
-    val observer: InvalidationTracker.Observer =
+    private val observer: InvalidationTracker.Observer =
         object : InvalidationTracker.Observer(tableNames) {
             override fun onInvalidated(tables: Set<String>) {
-                ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable)
+                database.getCoroutineScope().launch { invalidated() }
             }
         }
-    val invalid = AtomicBoolean(true)
-    val computing = AtomicBoolean(false)
-    val registeredObserver = AtomicBoolean(false)
-    val refreshRunnable = Runnable {
+    private val invalid = AtomicBoolean(true)
+    private val computing = AtomicBoolean(false)
+    private val registeredObserver = AtomicBoolean(false)
+
+    private suspend fun refresh() {
         if (registeredObserver.compareAndSet(false, true)) {
-            database.invalidationTracker.addWeakObserver(observer)
+            database.invalidationTracker.subscribe(
+                InvalidationTracker.WeakObserver(
+                    database.invalidationTracker,
+                    database.getCoroutineScope(),
+                    observer
+                )
+            )
         }
         var computed: Boolean
         do {
@@ -66,7 +74,22 @@
                     while (invalid.compareAndSet(true, false)) {
                         computed = true
                         try {
-                            value = computeFunction.call()
+                            value =
+                                if (callableFunction != null) {
+                                    withContext(
+                                        if (inTransaction) {
+                                            database.getTransactionContext()
+                                        } else {
+                                            database.getQueryContext()
+                                        }
+                                    ) {
+                                        callableFunction.call()
+                                    }
+                                } else if (lambdaFunction != null) {
+                                    performSuspending(database, true, inTransaction, lambdaFunction)
+                                } else {
+                                    error("Both callable and lambda functions are null")
+                                }
                         } catch (e: Exception) {
                             throw RuntimeException(
                                 "Exception while computing database live data.",
@@ -92,33 +115,23 @@
         } while (computed && invalid.get())
     }
 
-    val invalidationRunnable = Runnable {
+    private suspend fun invalidated() {
         val isActive = hasActiveObservers()
         if (invalid.compareAndSet(false, true)) {
             if (isActive) {
-                queryExecutor.execute(refreshRunnable)
+                refresh()
             }
         }
     }
 
-    @Suppress("UNCHECKED_CAST")
     override fun onActive() {
         super.onActive()
-        container.onActive(this as LiveData<Any>)
-        queryExecutor.execute(refreshRunnable)
+        container.onActive(this)
+        database.getCoroutineScope().launch { refresh() }
     }
 
-    @Suppress("UNCHECKED_CAST")
     override fun onInactive() {
         super.onInactive()
-        container.onInactive(this as LiveData<Any>)
+        container.onInactive(this)
     }
-
-    val queryExecutor: Executor
-        get() =
-            if (inTransaction) {
-                database.transactionExecutor
-            } else {
-                database.queryExecutor
-            }
 }
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
index c6eb76d..58dce9f 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/QueryInterceptorDatabase.android.kt
@@ -51,7 +51,7 @@
 
     override fun beginTransactionNonExclusive() {
         queryCallbackScope.launch {
-            queryCallback.onQuery("BEGIN DEFERRED TRANSACTION", emptyList())
+            queryCallback.onQuery("BEGIN IMMEDIATE TRANSACTION", emptyList())
         }
         delegate.beginTransactionNonExclusive()
     }
@@ -67,7 +67,7 @@
         transactionListener: SQLiteTransactionListener
     ) {
         queryCallbackScope.launch {
-            queryCallback.onQuery("BEGIN DEFERRED TRANSACTION", emptyList())
+            queryCallback.onQuery("BEGIN IMMEDIATE TRANSACTION", emptyList())
         }
         delegate.beginTransactionWithListenerNonExclusive(transactionListener)
     }
diff --git a/room/room-rxjava2/api/current.txt b/room/room-rxjava2/api/current.txt
index 73d416e..4df9b3d 100644
--- a/room/room-rxjava2/api/current.txt
+++ b/room/room-rxjava2/api/current.txt
@@ -2,14 +2,20 @@
 package androidx.room {
 
   public class EmptyResultSetException extends java.lang.RuntimeException {
-    ctor public EmptyResultSetException(String!);
+    ctor public EmptyResultSetException(String message);
   }
 
   public class RxRoom {
     ctor @Deprecated public RxRoom();
-    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...!);
-    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...!);
-    field public static final Object! NOTHING;
+    method public static final io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method public static final io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    field public static final androidx.room.RxRoom.Companion Companion;
+    field public static final Object NOTHING;
+  }
+
+  public static final class RxRoom.Companion {
+    method public io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method public io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
   }
 
 }
diff --git a/room/room-rxjava2/api/restricted_current.txt b/room/room-rxjava2/api/restricted_current.txt
index a3ecfa2..9a2d59c 100644
--- a/room/room-rxjava2/api/restricted_current.txt
+++ b/room/room-rxjava2/api/restricted_current.txt
@@ -2,19 +2,40 @@
 package androidx.room {
 
   public class EmptyResultSetException extends java.lang.RuntimeException {
-    ctor public EmptyResultSetException(String!);
+    ctor public EmptyResultSetException(String message);
   }
 
   public class RxRoom {
     ctor @Deprecated public RxRoom();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
-    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...!);
-    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
-    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...!);
-    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Single<T!>! createSingle(java.util.concurrent.Callable<? extends T!>!);
-    field public static final Object! NOTHING;
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final io.reactivex.Completable createCompletable(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,kotlin.Unit> block);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method public static final io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Maybe<T> createMaybe(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method public static final io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Single<T> createSingle(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final <T> io.reactivex.Single<T> createSingle(java.util.concurrent.Callable<? extends T> callable);
+    field public static final androidx.room.RxRoom.Companion Companion;
+    field public static final Object NOTHING;
+  }
+
+  public static final class RxRoom.Companion {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public io.reactivex.Completable createCompletable(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,kotlin.Unit> block);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method public io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Flowable<T> createFlowable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Maybe<T> createMaybe(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method public io.reactivex.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Observable<T> createObservable(androidx.room.RoomDatabase database, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Single<T> createSingle(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> io.reactivex.Single<T> createSingle(java.util.concurrent.Callable<? extends T> callable);
   }
 
 }
diff --git a/room/room-rxjava2/build.gradle b/room/room-rxjava2/build.gradle
index c49ef63..2b23d3f 100644
--- a/room/room-rxjava2/build.gradle
+++ b/room/room-rxjava2/build.gradle
@@ -36,6 +36,7 @@
 
     implementation("androidx.arch.core:core-runtime:2.2.0")
     implementation(libs.kotlinStdlib)
+    implementation(libs.kotlinCoroutinesRx2)
 
     testImplementation(project(":kruth:kruth"))
     testImplementation(libs.kotlinTest)
diff --git a/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.java b/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.java
deleted file mode 100644
index a36d8a9..0000000
--- a/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-/**
- * Thrown by Room when the query in a Single&lt;T&gt; DAO method needs to return a result but the
- * returned result from the database is empty.
- * <p>
- * Since a Single&lt;T&gt; must either emit a single non-null value or an error, this exception is
- * thrown instead of emitting a null value when the query resulted empty. If the Single&lt;T&gt;
- * contains a type argument of a collection (e.g. Single&lt;List&lt;Song&gt&gt;) then this
- * exception is not thrown an an empty collection is emitted instead.
- */
-public class EmptyResultSetException extends RuntimeException {
-    /**
-     * Constructs a new EmptyResultSetException with the exception.
-     * @param message The SQL query which didn't return any results.
-     */
-    public EmptyResultSetException(String message) {
-        super(message);
-    }
-}
diff --git a/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.kt b/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.kt
new file mode 100644
index 0000000..e2674f6
--- /dev/null
+++ b/room/room-rxjava2/src/main/java/androidx/room/EmptyResultSetException.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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
+
+/**
+ * Thrown by Room when the query in a [io.reactivex.Single] DAO method needs to return a result but
+ * the returned result from the database is empty.
+ *
+ * Since a [io.reactivex.Single] must either emit a single non-null value or an error, this
+ * exception is thrown instead of emitting a null value when the query resulted empty. If the
+ * [io.reactivex.Single] contains a type argument of a collection (e.g. `Single<List<Song>>`) the
+ * this exception is not thrown an an empty collection is emitted instead.
+ */
+open class EmptyResultSetException(message: String) : RuntimeException(message)
diff --git a/room/room-rxjava2/src/main/java/androidx/room/RxRoom.java b/room/room-rxjava2/src/main/java/androidx/room/RxRoom.java
deleted file mode 100644
index 42f3f79..0000000
--- a/room/room-rxjava2/src/main/java/androidx/room/RxRoom.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room;
-
-import android.annotation.SuppressLint;
-
-import androidx.annotation.RestrictTo;
-
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-
-import io.reactivex.BackpressureStrategy;
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-import io.reactivex.FlowableOnSubscribe;
-import io.reactivex.Maybe;
-import io.reactivex.MaybeSource;
-import io.reactivex.Observable;
-import io.reactivex.ObservableEmitter;
-import io.reactivex.ObservableOnSubscribe;
-import io.reactivex.Scheduler;
-import io.reactivex.Single;
-import io.reactivex.SingleEmitter;
-import io.reactivex.SingleOnSubscribe;
-import io.reactivex.disposables.Disposables;
-import io.reactivex.functions.Action;
-import io.reactivex.functions.Function;
-import io.reactivex.schedulers.Schedulers;
-
-/**
- * Helper class to add RxJava2 support to Room.
- */
-@SuppressLint("PrivateConstructorForUtilityClass")
-@SuppressWarnings("WeakerAccess")
-public class RxRoom {
-    /**
-     * Data dispatched by the publisher created by {@link #createFlowable(RoomDatabase, String...)}.
-     */
-    public static final Object NOTHING = new Object();
-
-    /**
-     * Creates a {@link Flowable} that emits at least once and also re-emits whenever one of the
-     * observed tables is updated.
-     * <p>
-     * You can easily chain a database operation to downstream of this {@link Flowable} to ensure
-     * that it re-runs when database is modified.
-     * <p>
-     * Since database invalidation is batched, multiple changes in the database may results in just
-     * 1 emission.
-     *
-     * @param database   The database instance
-     * @param tableNames The list of table names that should be observed
-     * @return A {@link Flowable} which emits {@link #NOTHING} when one of the observed tables
-     * is modified (also once when the invalidation tracker connection is established).
-     */
-    public static Flowable<Object> createFlowable(final RoomDatabase database,
-            final String... tableNames) {
-        return Flowable.create(new FlowableOnSubscribe<Object>() {
-            @Override
-            public void subscribe(final FlowableEmitter<Object> emitter) throws Exception {
-                final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
-                        tableNames) {
-                    @Override
-                    public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
-                        if (!emitter.isCancelled()) {
-                            emitter.onNext(NOTHING);
-                        }
-                    }
-                };
-                if (!emitter.isCancelled()) {
-                    database.getInvalidationTracker().addObserver(observer);
-                    emitter.setDisposable(Disposables.fromAction(new Action() {
-                        @Override
-                        public void run() throws Exception {
-                            database.getInvalidationTracker().removeObserver(observer);
-                        }
-                    }));
-                }
-
-                // emit once to avoid missing any data and also easy chaining
-                if (!emitter.isCancelled()) {
-                    emitter.onNext(NOTHING);
-                }
-            }
-        }, BackpressureStrategy.LATEST);
-    }
-
-    /**
-     * Helper method used by generated code to bind a Callable such that it will be run in
-     * our disk io thread and will automatically block null values since RxJava2 does not like null.
-     *
-     * @deprecated Use {@link #createFlowable(RoomDatabase, boolean, String[], Callable)}
-     *
-     */
-    @Deprecated
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
-            final String[] tableNames, final Callable<T> callable) {
-        return createFlowable(database, false, tableNames, callable);
-    }
-
-    /**
-     * Helper method used by generated code to bind a Callable such that it will be run in
-     * our disk io thread and will automatically block null values since RxJava2 does not like null.
-     *
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
-            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
-        Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
-        final Maybe<T> maybe = Maybe.fromCallable(callable);
-        return createFlowable(database, tableNames)
-                .subscribeOn(scheduler)
-                .unsubscribeOn(scheduler)
-                .observeOn(scheduler)
-                .flatMapMaybe(new Function<Object, MaybeSource<T>>() {
-                    @Override
-                    public MaybeSource<T> apply(Object o) throws Exception {
-                        return maybe;
-                    }
-                });
-    }
-
-    /**
-     * Creates a {@link Observable} that emits at least once and also re-emits whenever one of the
-     * observed tables is updated.
-     * <p>
-     * You can easily chain a database operation to downstream of this {@link Observable} to ensure
-     * that it re-runs when database is modified.
-     * <p>
-     * Since database invalidation is batched, multiple changes in the database may results in just
-     * 1 emission.
-     *
-     * @param database   The database instance
-     * @param tableNames The list of table names that should be observed
-     * @return A {@link Observable} which emits {@link #NOTHING} when one of the observed tables
-     * is modified (also once when the invalidation tracker connection is established).
-     */
-    public static Observable<Object> createObservable(final RoomDatabase database,
-            final String... tableNames) {
-        return Observable.create(new ObservableOnSubscribe<Object>() {
-            @Override
-            public void subscribe(final ObservableEmitter<Object> emitter) throws Exception {
-                final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
-                        tableNames) {
-                    @Override
-                    public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
-                        emitter.onNext(NOTHING);
-                    }
-                };
-                database.getInvalidationTracker().addObserver(observer);
-                emitter.setDisposable(Disposables.fromAction(new Action() {
-                    @Override
-                    public void run() throws Exception {
-                        database.getInvalidationTracker().removeObserver(observer);
-                    }
-                }));
-
-                // emit once to avoid missing any data and also easy chaining
-                emitter.onNext(NOTHING);
-            }
-        });
-    }
-
-    /**
-     * Helper method used by generated code to bind a Callable such that it will be run in
-     * our disk io thread and will automatically block null values since RxJava2 does not like null.
-     *
-     * @deprecated Use {@link #createObservable(RoomDatabase, boolean, String[], Callable)}
-     *
-     */
-    @Deprecated
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Observable<T> createObservable(final RoomDatabase database,
-            final String[] tableNames, final Callable<T> callable) {
-        return createObservable(database, false, tableNames, callable);
-    }
-
-    /**
-     * Helper method used by generated code to bind a Callable such that it will be run in
-     * our disk io thread and will automatically block null values since RxJava2 does not like null.
-     *
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Observable<T> createObservable(final RoomDatabase database,
-            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
-        Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
-        final Maybe<T> maybe = Maybe.fromCallable(callable);
-        return createObservable(database, tableNames)
-                .subscribeOn(scheduler)
-                .unsubscribeOn(scheduler)
-                .observeOn(scheduler)
-                .flatMapMaybe(new Function<Object, MaybeSource<T>>() {
-                    @Override
-                    public MaybeSource<T> apply(Object o) throws Exception {
-                        return maybe;
-                    }
-                });
-    }
-
-    /**
-     * Helper method used by generated code to create a Single from a Callable that will ignore
-     * the EmptyResultSetException if the stream is already disposed.
-     *
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Single<T> createSingle(final Callable<? extends T> callable) {
-        return Single.create(new SingleOnSubscribe<T>() {
-            @Override
-            public void subscribe(SingleEmitter<T> emitter) throws Exception {
-                try {
-                    emitter.onSuccess(callable.call());
-                } catch (EmptyResultSetException e) {
-                    emitter.tryOnError(e);
-                }
-            }
-        });
-    }
-
-    private static Executor getExecutor(RoomDatabase database, boolean inTransaction) {
-        if (inTransaction) {
-            return database.getTransactionExecutor();
-        } else {
-            return database.getQueryExecutor();
-        }
-    }
-
-    /** @deprecated This type should not be instantiated as it contains only static methods. */
-    @Deprecated
-    @SuppressWarnings("PrivateConstructorForUtilityClass")
-    public RxRoom() {
-    }
-}
diff --git a/room/room-rxjava2/src/main/java/androidx/room/RxRoom.kt b/room/room-rxjava2/src/main/java/androidx/room/RxRoom.kt
new file mode 100644
index 0000000..2ae5937
--- /dev/null
+++ b/room/room-rxjava2/src/main/java/androidx/room/RxRoom.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room
+
+import androidx.annotation.RestrictTo
+import androidx.room.coroutines.createFlow
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.BackpressureStrategy
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.disposables.Disposables
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.Callable
+import java.util.concurrent.Executor
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.rx2.asObservable
+
+open class RxRoom
+@Deprecated("This type should not be instantiated as it contains only utility functions.")
+constructor() {
+
+    companion object {
+
+        /** Data dispatched by the publisher created by [createFlowable]. */
+        @JvmField val NOTHING: Any = Any()
+
+        /** Helper function used by generated code to create a [Flowable] */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        fun <T : Any> createFlowable(
+            db: RoomDatabase,
+            inTransaction: Boolean,
+            tableNames: Array<String>,
+            block: (SQLiteConnection) -> T?
+        ): Flowable<T> =
+            createObservable(db, inTransaction, tableNames, block)
+                .toFlowable(BackpressureStrategy.LATEST)
+
+        /** Helper function used by generated code to create a [Observable] */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        fun <T : Any> createObservable(
+            db: RoomDatabase,
+            inTransaction: Boolean,
+            tableNames: Array<String>,
+            block: (SQLiteConnection) -> T?
+        ): Observable<T> =
+            createFlow(db, inTransaction, tableNames, block)
+                .filterNotNull()
+                .asObservable(db.getQueryContext())
+
+        /** Helper function used by generated code to create a [Maybe] */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        fun <T : Any> createMaybe(
+            db: RoomDatabase,
+            isReadOnly: Boolean,
+            inTransaction: Boolean,
+            block: (SQLiteConnection) -> T?
+        ): Maybe<T> = Maybe.fromCallable { performBlocking(db, isReadOnly, inTransaction, block) }
+
+        /** Helper function used by generated code to create a [Completable] */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        fun createCompletable(
+            db: RoomDatabase,
+            isReadOnly: Boolean,
+            inTransaction: Boolean,
+            block: (SQLiteConnection) -> Unit
+        ): Completable =
+            Completable.fromAction { performBlocking(db, isReadOnly, inTransaction, block) }
+
+        /** Helper function used by generated code to create a [Single] */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        fun <T : Any> createSingle(
+            db: RoomDatabase,
+            isReadOnly: Boolean,
+            inTransaction: Boolean,
+            block: (SQLiteConnection) -> T?
+        ): Single<T> =
+            Single.create { emitter ->
+                if (emitter.isDisposed) return@create
+                try {
+                    val result = performBlocking(db, isReadOnly, inTransaction, block)
+                    if (result != null) {
+                        emitter.onSuccess(result)
+                    } else {
+                        throw EmptyResultSetException("Query returned empty result set.")
+                    }
+                } catch (e: EmptyResultSetException) {
+                    emitter.tryOnError(e)
+                }
+            }
+
+        /**
+         * Creates a [Flowable] that emits at least once and also re-emits whenever one of the
+         * observed tables is updated.
+         *
+         * You can easily chain a database operation to downstream of this [Flowable] to ensure that
+         * it re-runs when database is modified.
+         *
+         * Since database invalidation is batched, multiple changes in the database may results in
+         * just 1 emission.
+         *
+         * @param database The database instance
+         * @param tableNames The list of table names that should be observed
+         * @return A [Flowable] which emits [NOTHING] when one of the observed tables is modified
+         *   (also once when the invalidation tracker connection is established).
+         */
+        @JvmStatic
+        fun createFlowable(database: RoomDatabase, vararg tableNames: String): Flowable<Any> {
+            return Flowable.create(
+                { emitter ->
+                    val observer =
+                        object : InvalidationTracker.Observer(tableNames) {
+                            override fun onInvalidated(tables: Set<String>) {
+                                if (!emitter.isCancelled) {
+                                    emitter.onNext(NOTHING)
+                                }
+                            }
+                        }
+                    if (!emitter.isCancelled) {
+                        database.invalidationTracker.addObserver(observer)
+                        emitter.setDisposable(
+                            Disposables.fromAction {
+                                database.invalidationTracker.removeObserver(observer)
+                            }
+                        )
+                    }
+
+                    // emit once to avoid missing any data and also easy chaining
+                    if (!emitter.isCancelled) {
+                        emitter.onNext(NOTHING)
+                    }
+                },
+                BackpressureStrategy.LATEST
+            )
+        }
+
+        /**
+         * Helper method used by generated code to bind a [Callable] such that it will be run in our
+         * disk io thread and will automatically block null values since RxJava2 does not like null.
+         */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        @Deprecated("No longer used by generated code.")
+        fun <T : Any> createFlowable(
+            database: RoomDatabase,
+            tableNames: Array<String>,
+            callable: Callable<out T>
+        ): Flowable<T> {
+            @Suppress("DEPRECATION") return createFlowable(database, false, tableNames, callable)
+        }
+
+        /**
+         * Helper method used by generated code to bind a [Callable] such that it will be run in our
+         * disk io thread and will automatically block null values since RxJava2 does not like null.
+         */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        @Deprecated("No longer used by generated code.")
+        fun <T : Any> createFlowable(
+            database: RoomDatabase,
+            inTransaction: Boolean,
+            tableNames: Array<String>,
+            callable: Callable<out T>
+        ): Flowable<T> {
+            val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+            val maybe = Maybe.fromCallable(callable)
+            return createFlowable(database, *tableNames)
+                .subscribeOn(scheduler)
+                .unsubscribeOn(scheduler)
+                .observeOn(scheduler)
+                .flatMapMaybe { maybe }
+        }
+
+        /**
+         * Creates a [Observable] that emits at least once and also re-emits whenever one of the
+         * observed tables is updated.
+         *
+         * You can easily chain a database operation to downstream of this [Observable] to ensure
+         * that it re-runs when database is modified.
+         *
+         * Since database invalidation is batched, multiple changes in the database may results in
+         * just 1 emission.
+         *
+         * @param database The database instance
+         * @param tableNames The list of table names that should be observed
+         * @return A [Observable] which emits [.NOTHING] when one of the observed tables is modified
+         *   (also once when the invalidation tracker connection is established).
+         */
+        @JvmStatic
+        fun createObservable(database: RoomDatabase, vararg tableNames: String): Observable<Any> {
+            return Observable.create { emitter ->
+                val observer =
+                    object : InvalidationTracker.Observer(tableNames) {
+                        override fun onInvalidated(tables: Set<String>) {
+                            emitter.onNext(NOTHING)
+                        }
+                    }
+                database.invalidationTracker.addObserver(observer)
+                emitter.setDisposable(
+                    Disposables.fromAction { database.invalidationTracker.removeObserver(observer) }
+                )
+
+                // emit once to avoid missing any data and also easy chaining
+                emitter.onNext(NOTHING)
+            }
+        }
+
+        /**
+         * Helper method used by generated code to bind a [Callable] such that it will be run in our
+         * disk io thread and will automatically block null values since RxJava2 does not like null.
+         */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        @Deprecated("No longer used by generated code.")
+        fun <T : Any> createObservable(
+            database: RoomDatabase,
+            tableNames: Array<String>,
+            callable: Callable<out T>
+        ): Observable<T> {
+            @Suppress("DEPRECATION") return createObservable(database, false, tableNames, callable)
+        }
+
+        /**
+         * Helper method used by generated code to bind a [Callable] such that it will be run in our
+         * disk io thread and will automatically block null values since RxJava2 does not like null.
+         */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        @Deprecated("No longer used by generated code.")
+        fun <T : Any> createObservable(
+            database: RoomDatabase,
+            inTransaction: Boolean,
+            tableNames: Array<String>,
+            callable: Callable<out T>
+        ): Observable<T> {
+            val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+            val maybe = Maybe.fromCallable(callable)
+            return createObservable(database, *tableNames)
+                .subscribeOn(scheduler)
+                .unsubscribeOn(scheduler)
+                .observeOn(scheduler)
+                .flatMapMaybe { maybe }
+        }
+
+        /**
+         * Helper method used by generated code to create a [Single] from a [Callable] that will
+         * ignore the [EmptyResultSetException] if the stream is already disposed.
+         */
+        @JvmStatic
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        fun <T : Any> createSingle(callable: Callable<out T>): Single<T> {
+            return Single.create { emitter ->
+                try {
+                    val result = callable.call()
+                    if (result != null) {
+                        emitter.onSuccess(result)
+                    } else {
+                        throw EmptyResultSetException("Query returned empty result set.")
+                    }
+                } catch (e: EmptyResultSetException) {
+                    emitter.tryOnError(e)
+                }
+            }
+        }
+
+        private fun getExecutor(database: RoomDatabase, inTransaction: Boolean): Executor {
+            return if (inTransaction) {
+                database.transactionExecutor
+            } else {
+                database.queryExecutor
+            }
+        }
+    }
+}
diff --git a/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt b/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
index 32b23a8..2dcc9d5 100644
--- a/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
+++ b/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
@@ -21,6 +21,7 @@
 import io.reactivex.functions.Consumer
 import io.reactivex.observers.TestObserver
 import io.reactivex.subscribers.TestSubscriber
+import java.util.concurrent.Callable
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicReference
 import org.junit.Before
@@ -138,12 +139,12 @@
     }
 
     @Test
-    @Throws(Exception::class)
+    @Suppress("DEPRECATION")
     fun internalCallable_Flowable() {
         val value = AtomicReference<Any>(null)
         val tables = arrayOf("a", "b")
         val tableSet: Set<String> = HashSet(listOf(*tables))
-        val flowable = RxRoom.createFlowable(mDatabase, false, tables) { value.get() }
+        val flowable = RxRoom.createFlowable(mDatabase, false, tables, Callable { value.get() })
         val consumer = CountingConsumer()
         flowable.subscribe(consumer)
         drain()
@@ -167,12 +168,12 @@
     }
 
     @Test
-    @Throws(Exception::class)
+    @Suppress("DEPRECATION")
     fun internalCallable_Observable() {
         val value = AtomicReference<Any>(null)
         val tables = arrayOf("a", "b")
         val tableSet: Set<String> = HashSet(listOf(*tables))
-        val flowable = RxRoom.createObservable(mDatabase, false, tables) { value.get() }
+        val flowable = RxRoom.createObservable(mDatabase, false, tables, Callable { value.get() })
         val consumer = CountingConsumer()
         flowable.subscribe(consumer)
         drain()
@@ -196,11 +197,15 @@
     }
 
     @Test
+    @Suppress("DEPRECATION")
     fun exception_Flowable() {
         val flowable =
-            RxRoom.createFlowable<String>(mDatabase, false, arrayOf("a")) {
-                throw Exception("i want exception")
-            }
+            RxRoom.createFlowable<String>(
+                mDatabase,
+                false,
+                arrayOf("a"),
+                Callable { throw Exception("i want exception") }
+            )
         val subscriber = TestSubscriber<String>()
         flowable.subscribe(subscriber)
         drain()
@@ -209,11 +214,15 @@
     }
 
     @Test
+    @Suppress("DEPRECATION")
     fun exception_Observable() {
         val flowable =
-            RxRoom.createObservable<String>(mDatabase, false, arrayOf("a")) {
-                throw Exception("i want exception")
-            }
+            RxRoom.createObservable<String>(
+                mDatabase,
+                false,
+                arrayOf("a"),
+                Callable { throw Exception("i want exception") }
+            )
         val observer = TestObserver<String>()
         flowable.subscribe(observer)
         drain()
diff --git a/room/room-rxjava3/api/current.txt b/room/room-rxjava3/api/current.txt
index 6b78281..bbddba6 100644
--- a/room/room-rxjava3/api/current.txt
+++ b/room/room-rxjava3/api/current.txt
@@ -2,12 +2,12 @@
 package androidx.room.rxjava3 {
 
   public final class EmptyResultSetException extends java.lang.RuntimeException {
-    ctor public EmptyResultSetException(String);
+    ctor public EmptyResultSetException(String message);
   }
 
   public final class RxRoom {
-    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
-    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
     field public static final Object NOTHING;
   }
 
diff --git a/room/room-rxjava3/api/restricted_current.txt b/room/room-rxjava3/api/restricted_current.txt
index 4fb4dbd..9b71011 100644
--- a/room/room-rxjava3/api/restricted_current.txt
+++ b/room/room-rxjava3/api/restricted_current.txt
@@ -2,15 +2,20 @@
 package androidx.room.rxjava3 {
 
   public final class EmptyResultSetException extends java.lang.RuntimeException {
-    ctor public EmptyResultSetException(String);
+    ctor public EmptyResultSetException(String message);
   }
 
   public final class RxRoom {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T!> createFlowable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
-    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T!> createObservable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
-    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T!> createSingle(java.util.concurrent.Callable<? extends T!>);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static io.reactivex.rxjava3.core.Completable createCompletable(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,kotlin.Unit> block);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T> createFlowable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T> createFlowable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Maybe<T> createMaybe(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T> createObservable(androidx.room.RoomDatabase database, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<? extends T> callable);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T> createObservable(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object> createObservable(androidx.room.RoomDatabase database, java.lang.String... tableNames);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T> createSingle(androidx.room.RoomDatabase db, boolean isReadOnly, boolean inTransaction, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends T?> block);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T> createSingle(java.util.concurrent.Callable<? extends T> callable);
     field public static final Object NOTHING;
   }
 
diff --git a/room/room-rxjava3/build.gradle b/room/room-rxjava3/build.gradle
index dd068ad..96c86a1 100644
--- a/room/room-rxjava3/build.gradle
+++ b/room/room-rxjava3/build.gradle
@@ -27,7 +27,6 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("kotlin-android")
-    id("com.google.devtools.ksp")
 }
 
 dependencies {
@@ -37,6 +36,7 @@
 
     implementation("androidx.arch.core:core-runtime:2.2.0")
     implementation(libs.kotlinStdlib)
+    implementation(libs.kotlinCoroutinesRx3)
 
     testImplementation(project(":kruth:kruth"))
     testImplementation(libs.kotlinTest)
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.java b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.java
deleted file mode 100644
index 3b2058d..0000000
--- a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.rxjava3;
-
-import androidx.annotation.NonNull;
-
-/**
- * Thrown by Room when the query in a Single&lt;T&gt; DAO method needs to return a result but the
- * returned result from the database is empty.
- * <p>
- * Since a Single&lt;T&gt; must either emit a single non-null value or an error, this exception is
- * thrown instead of emitting a null value when the query resulted empty. If the Single&lt;T&gt;
- * contains a type argument of a collection (e.g. Single&lt;List&lt;Song&gt&gt;) then this
- * exception is not thrown an an empty collection is emitted instead.
- */
-@SuppressWarnings("serial")
-public final class EmptyResultSetException extends RuntimeException {
-    /**
-     * Constructs a new EmptyResultSetException with the exception.
-     * @param message The SQL query which didn't return any results.
-     */
-    public EmptyResultSetException(@NonNull String message) {
-        super(message);
-    }
-}
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.kt b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.kt
new file mode 100644
index 0000000..b009f6e
--- /dev/null
+++ b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/EmptyResultSetException.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.rxjava3
+
+/**
+ * Thrown by Room when the query in a [io.reactivex.rxjava3.core.Single] DAO method needs to return
+ * a result but the returned result from the database is empty.
+ *
+ * Since a [io.reactivex.rxjava3.core.Single] must either emit a single non-null value or an error,
+ * this exception is thrown instead of emitting a null value when the query resulted empty. If the
+ * [io.reactivex.rxjava3.core.Single] contains a type argument of a collection (e.g.
+ * `Single<List<Song>>`) the this exception is not thrown an an empty collection is emitted instead.
+ */
+class EmptyResultSetException(message: String) : RuntimeException(message)
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.java b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.java
deleted file mode 100644
index e520efd..0000000
--- a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.rxjava3;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.room.InvalidationTracker;
-import androidx.room.RoomDatabase;
-
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-
-import io.reactivex.rxjava3.core.BackpressureStrategy;
-import io.reactivex.rxjava3.core.Flowable;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.MaybeSource;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.functions.Function;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-/**
- * Helper class to add RxJava3 support to Room.
- */
-public final class RxRoom {
-    /**
-     * Data dispatched by the publisher created by {@link #createFlowable(RoomDatabase, String...)}.
-     */
-    @NonNull
-    public static final Object NOTHING = new Object();
-
-    /**
-     * Creates a {@link Flowable} that emits at least once and also re-emits whenever one of the
-     * observed tables is updated.
-     * <p>
-     * You can easily chain a database operation to downstream of this {@link Flowable} to ensure
-     * that it re-runs when database is modified.
-     * <p>
-     * Since database invalidation is batched, multiple changes in the database may results in just
-     * 1 emission.
-     *
-     * @param database   The database instance
-     * @param tableNames The list of table names that should be observed
-     * @return A {@link Flowable} which emits {@link #NOTHING} when one of the observed tables
-     * is modified (also once when the invalidation tracker connection is established).
-     */
-    @NonNull
-    public static Flowable<Object> createFlowable(@NonNull final RoomDatabase database,
-            @NonNull final String... tableNames) {
-        return Flowable.create(emitter -> {
-            final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
-                    tableNames) {
-                @Override
-                public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
-                    if (!emitter.isCancelled()) {
-                        emitter.onNext(NOTHING);
-                    }
-                }
-            };
-            if (!emitter.isCancelled()) {
-                database.getInvalidationTracker().addObserver(observer);
-                emitter.setDisposable(Disposable.fromAction(
-                        () -> database.getInvalidationTracker().removeObserver(observer)));
-            }
-
-            // emit once to avoid missing any data and also easy chaining
-            if (!emitter.isCancelled()) {
-                emitter.onNext(NOTHING);
-            }
-        }, BackpressureStrategy.LATEST);
-    }
-
-    /**
-     * Helper method used by generated code to bind a Callable such that it will be run in
-     * our disk io thread and will automatically block null values since RxJava3 does not like null.
-     *
-     */
-    @NonNull
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Flowable<T> createFlowable(@NonNull final RoomDatabase database,
-            final boolean inTransaction, @NonNull final String[] tableNames,
-            @NonNull final Callable<T> callable) {
-        Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
-        final Maybe<T> maybe = Maybe.fromCallable(callable);
-        return createFlowable(database, tableNames)
-                .subscribeOn(scheduler)
-                .unsubscribeOn(scheduler)
-                .observeOn(scheduler)
-                .flatMapMaybe((Function<Object, MaybeSource<T>>) o -> maybe);
-    }
-
-    /**
-     * Creates a {@link Observable} that emits at least once and also re-emits whenever one of the
-     * observed tables is updated.
-     * <p>
-     * You can easily chain a database operation to downstream of this {@link Observable} to ensure
-     * that it re-runs when database is modified.
-     * <p>
-     * Since database invalidation is batched, multiple changes in the database may results in just
-     * 1 emission.
-     *
-     * @param database   The database instance
-     * @param tableNames The list of table names that should be observed
-     * @return A {@link Observable} which emits {@link #NOTHING} when one of the observed tables
-     * is modified (also once when the invalidation tracker connection is established).
-     */
-    @NonNull
-    public static Observable<Object> createObservable(@NonNull final RoomDatabase database,
-            @NonNull final String... tableNames) {
-        return Observable.create(emitter -> {
-            final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
-                    tableNames) {
-                @Override
-                public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
-                    emitter.onNext(NOTHING);
-                }
-            };
-            database.getInvalidationTracker().addObserver(observer);
-            emitter.setDisposable(Disposable.fromAction(
-                    () -> database.getInvalidationTracker().removeObserver(observer)));
-
-            // emit once to avoid missing any data and also easy chaining
-            emitter.onNext(NOTHING);
-        });
-    }
-
-    /**
-     * Helper method used by generated code to bind a Callable such that it will be run in
-     * our disk io thread and will automatically block null values since RxJava3 does not like null.
-     *
-     */
-    @NonNull
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Observable<T> createObservable(@NonNull final RoomDatabase database,
-            final boolean inTransaction, @NonNull final String[] tableNames,
-            @NonNull final Callable<T> callable) {
-        Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
-        final Maybe<T> maybe = Maybe.fromCallable(callable);
-        return createObservable(database, tableNames)
-                .subscribeOn(scheduler)
-                .unsubscribeOn(scheduler)
-                .observeOn(scheduler)
-                .flatMapMaybe(o -> maybe);
-    }
-
-    /**
-     * Helper method used by generated code to create a Single from a Callable that will ignore
-     * the EmptyResultSetException if the stream is already disposed.
-     *
-     */
-    @NonNull
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    public static <T> Single<T> createSingle(@NonNull final Callable<? extends T> callable) {
-        return Single.create(emitter -> {
-            try {
-                emitter.onSuccess(callable.call());
-            } catch (EmptyResultSetException e) {
-                emitter.tryOnError(e);
-            }
-        });
-    }
-
-    private static Executor getExecutor(@NonNull RoomDatabase database, boolean inTransaction) {
-        if (inTransaction) {
-            return database.getTransactionExecutor();
-        } else {
-            return database.getQueryExecutor();
-        }
-    }
-
-    private RxRoom() {
-    }
-}
diff --git a/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.kt b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.kt
new file mode 100644
index 0000000..82ee4b5
--- /dev/null
+++ b/room/room-rxjava3/src/main/java/androidx/room/rxjava3/RxRoom.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("RxRoom")
+
+package androidx.room.rxjava3
+
+import androidx.annotation.RestrictTo
+import androidx.room.InvalidationTracker
+import androidx.room.RoomDatabase
+import androidx.room.coroutines.createFlow
+import androidx.room.util.performBlocking
+import androidx.sqlite.SQLiteConnection
+import io.reactivex.rxjava3.core.BackpressureStrategy
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Flowable
+import io.reactivex.rxjava3.core.FlowableEmitter
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.ObservableEmitter
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import java.util.concurrent.Callable
+import java.util.concurrent.Executor
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.rx3.asObservable
+
+/** Marker class used by annotation processor to identify dependency is in the classpath. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class Rx3RoomArtifactMarker private constructor()
+
+/** Data dispatched by the publisher created by [createFlowable]. */
+@JvmField val NOTHING: Any = Any()
+
+/** Helper function used by generated code to create a [Flowable] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createFlowable(
+    db: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    block: (SQLiteConnection) -> T?
+): Flowable<T> =
+    createObservable(db, inTransaction, tableNames, block).toFlowable(BackpressureStrategy.LATEST)
+
+/** Helper function used by generated code to create a [Observable] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createObservable(
+    db: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    block: (SQLiteConnection) -> T?
+): Observable<T> =
+    createFlow(db, inTransaction, tableNames, block)
+        .filterNotNull()
+        .asObservable(db.getQueryContext())
+
+/** Helper function used by generated code to create a [Maybe] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createMaybe(
+    db: RoomDatabase,
+    isReadOnly: Boolean,
+    inTransaction: Boolean,
+    block: (SQLiteConnection) -> T?
+): Maybe<T> =
+    Maybe.fromCallable(Callable<T> { performBlocking(db, isReadOnly, inTransaction, block) })
+
+/** Helper function used by generated code to create a [Completable] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun createCompletable(
+    db: RoomDatabase,
+    isReadOnly: Boolean,
+    inTransaction: Boolean,
+    block: (SQLiteConnection) -> Unit
+): Completable = Completable.fromCallable { performBlocking(db, isReadOnly, inTransaction, block) }
+
+/** Helper function used by generated code to create a [Single] */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createSingle(
+    db: RoomDatabase,
+    isReadOnly: Boolean,
+    inTransaction: Boolean,
+    block: (SQLiteConnection) -> T?
+): Single<T> =
+    Single.create { emitter ->
+        if (emitter.isDisposed) return@create
+        try {
+            val result = performBlocking(db, isReadOnly, inTransaction, block)
+            if (result != null) {
+                emitter.onSuccess(result)
+            } else {
+                throw EmptyResultSetException("Query returned empty result set.")
+            }
+        } catch (e: EmptyResultSetException) {
+            emitter.tryOnError(e)
+        }
+    }
+
+/**
+ * Creates a [Flowable] that emits at least once and also re-emits whenever one of the observed
+ * tables is updated.
+ *
+ * You can easily chain a database operation to downstream of this [Flowable] to ensure that it
+ * re-runs when database is modified.
+ *
+ * Since database invalidation is batched, multiple changes in the database may results in just 1
+ * emission.
+ *
+ * @param database The database instance
+ * @param tableNames The list of table names that should be observed
+ * @return A [Flowable] which emits [NOTHING] when one of the observed tables is modified (also once
+ *   when the invalidation tracker connection is established).
+ */
+fun createFlowable(database: RoomDatabase, vararg tableNames: String): Flowable<Any> {
+    return Flowable.create(
+        { emitter: FlowableEmitter<Any> ->
+            val observer =
+                object : InvalidationTracker.Observer(tableNames) {
+                    override fun onInvalidated(tables: Set<String>) {
+                        if (!emitter.isCancelled) {
+                            emitter.onNext(NOTHING)
+                        }
+                    }
+                }
+            if (!emitter.isCancelled) {
+                database.invalidationTracker.addObserver(observer)
+                emitter.setDisposable(
+                    Disposable.fromAction { database.invalidationTracker.removeObserver(observer) }
+                )
+            }
+
+            // emit once to avoid missing any data and also easy chaining
+            if (!emitter.isCancelled) {
+                emitter.onNext(NOTHING)
+            }
+        },
+        BackpressureStrategy.LATEST
+    )
+}
+
+/**
+ * Helper method used by generated code to bind a Callable such that it will be run in our disk io
+ * thread and will automatically block null values since RxJava3 does not like null.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+@Deprecated("No longer used by generated code.")
+fun <T : Any> createFlowable(
+    database: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    callable: Callable<out T>
+): Flowable<T> {
+    val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+    val maybe = Maybe.fromCallable(callable)
+    return createFlowable(database, *tableNames)
+        .subscribeOn(scheduler)
+        .unsubscribeOn(scheduler)
+        .observeOn(scheduler)
+        .flatMapMaybe { maybe }
+}
+
+/**
+ * Creates a [Observable] that emits at least once and also re-emits whenever one of the observed
+ * tables is updated.
+ *
+ * You can easily chain a database operation to downstream of this [Observable] to ensure that it
+ * re-runs when database is modified.
+ *
+ * Since database invalidation is batched, multiple changes in the database may results in just 1
+ * emission.
+ *
+ * @param database The database instance
+ * @param tableNames The list of table names that should be observed
+ * @return A [Observable] which emits [NOTHING] when one of the observed tables is modified (also
+ *   once when the invalidation tracker connection is established).
+ */
+fun createObservable(database: RoomDatabase, vararg tableNames: String): Observable<Any> {
+    return Observable.create { emitter: ObservableEmitter<Any> ->
+        val observer =
+            object : InvalidationTracker.Observer(tableNames) {
+                override fun onInvalidated(tables: Set<String>) {
+                    emitter.onNext(NOTHING)
+                }
+            }
+        database.invalidationTracker.addObserver(observer)
+        emitter.setDisposable(
+            Disposable.fromAction { database.invalidationTracker.removeObserver(observer) }
+        )
+
+        // emit once to avoid missing any data and also easy chaining
+        emitter.onNext(NOTHING)
+    }
+}
+
+/**
+ * Helper method used by generated code to bind a Callable such that it will be run in our disk io
+ * thread and will automatically block null values since RxJava3 does not like null.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+@Deprecated("No longer used by generated code.")
+fun <T : Any> createObservable(
+    database: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    callable: Callable<out T>
+): Observable<T> {
+    val scheduler = Schedulers.from(getExecutor(database, inTransaction))
+    val maybe = Maybe.fromCallable(callable)
+    return createObservable(database, *tableNames)
+        .subscribeOn(scheduler)
+        .unsubscribeOn(scheduler)
+        .observeOn(scheduler)
+        .flatMapMaybe { maybe }
+}
+
+/**
+ * Helper method used by generated code to create a Single from a Callable that will ignore the
+ * EmptyResultSetException if the stream is already disposed.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <T : Any> createSingle(callable: Callable<out T>): Single<T> {
+    return Single.create { emitter ->
+        try {
+            val result = callable.call()
+            if (result != null) {
+                emitter.onSuccess(result)
+            } else {
+                throw EmptyResultSetException("Query returned empty result set.")
+            }
+        } catch (e: EmptyResultSetException) {
+            emitter.tryOnError(e)
+        }
+    }
+}
+
+private fun getExecutor(database: RoomDatabase, inTransaction: Boolean): Executor {
+    return if (inTransaction) {
+        database.transactionExecutor
+    } else {
+        database.queryExecutor
+    }
+}
diff --git a/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt b/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
index 256e5c2..27f5606 100644
--- a/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
+++ b/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
@@ -23,6 +23,7 @@
 import io.reactivex.rxjava3.functions.Consumer
 import io.reactivex.rxjava3.observers.TestObserver
 import io.reactivex.rxjava3.subscribers.TestSubscriber
+import java.util.concurrent.Callable
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicReference
 import org.junit.Before
@@ -62,7 +63,7 @@
 
     @Test
     fun basicAddRemove_Flowable() {
-        val flowable = RxRoom.createFlowable(mDatabase, "a", "b")
+        val flowable = createFlowable(mDatabase, "a", "b")
         verify(mInvalidationTracker, never()).addObserver(any())
         var disposable = flowable.subscribe()
         verify(mInvalidationTracker).addObserver(any())
@@ -82,7 +83,7 @@
 
     @Test
     fun basicAddRemove_Observable() {
-        val observable = RxRoom.createObservable(mDatabase, "a", "b")
+        val observable = createObservable(mDatabase, "a", "b")
         verify(mInvalidationTracker, never()).addObserver(any())
         var disposable = observable.subscribe()
         verify(mInvalidationTracker).addObserver(any())
@@ -104,7 +105,7 @@
     fun basicNotify_Flowable() {
         val tables = arrayOf("a", "b")
         val tableSet: Set<String> = HashSet(listOf(*tables))
-        val flowable = RxRoom.createFlowable(mDatabase, *tables)
+        val flowable = createFlowable(mDatabase, *tables)
         val consumer = CountingConsumer()
         val disposable = flowable.subscribe(consumer)
         assertThat(mAddedObservers.size).isEqualTo(1)
@@ -123,7 +124,7 @@
     fun basicNotify_Observable() {
         val tables = arrayOf("a", "b")
         val tableSet: Set<String> = HashSet(listOf(*tables))
-        val observable = RxRoom.createObservable(mDatabase, *tables)
+        val observable = createObservable(mDatabase, *tables)
         val consumer = CountingConsumer()
         val disposable = observable.subscribe(consumer)
         assertThat(mAddedObservers.size).isEqualTo(1)
@@ -139,12 +140,12 @@
     }
 
     @Test
-    @Throws(Exception::class)
+    @Suppress("DEPRECATION")
     fun internalCallable_Flowable() {
         val value = AtomicReference<Any>(null)
         val tables = arrayOf("a", "b")
         val tableSet: Set<String> = HashSet(listOf(*tables))
-        val flowable = RxRoom.createFlowable(mDatabase, false, tables) { value.get() }
+        val flowable = createFlowable(mDatabase, false, tables, Callable { value.get() })
         val consumer = CountingConsumer()
         val disposable = flowable.subscribe(consumer)
         drain()
@@ -169,12 +170,12 @@
     }
 
     @Test
-    @Throws(Exception::class)
+    @Suppress("DEPRECATION")
     fun internalCallable_Observable() {
         val value = AtomicReference<Any>(null)
         val tables = arrayOf("a", "b")
         val tableSet: Set<String> = HashSet(listOf(*tables))
-        val flowable = RxRoom.createObservable(mDatabase, false, tables) { value.get() }
+        val flowable = createObservable(mDatabase, false, tables, Callable { value.get() })
         val consumer = CountingConsumer()
         val disposable = flowable.subscribe(consumer)
         drain()
@@ -199,12 +200,15 @@
     }
 
     @Test
-    @Throws(Exception::class)
+    @Suppress("DEPRECATION")
     fun exception_Flowable() {
         val flowable =
-            RxRoom.createFlowable<String>(mDatabase, false, arrayOf("a")) {
-                throw Exception("i want exception")
-            }
+            createFlowable<String>(
+                mDatabase,
+                false,
+                arrayOf("a"),
+                Callable { throw Exception("i want exception") }
+            )
         val subscriber = TestSubscriber<String>()
         flowable.subscribe(subscriber)
         drain()
@@ -212,12 +216,15 @@
     }
 
     @Test
-    @Throws(Exception::class)
+    @Suppress("DEPRECATION")
     fun exception_Observable() {
         val flowable =
-            RxRoom.createObservable<String>(mDatabase, false, arrayOf("a")) {
-                throw Exception("i want exception")
-            }
+            createObservable<String>(
+                mDatabase,
+                false,
+                arrayOf("a"),
+                Callable { throw Exception("i want exception") }
+            )
         val observer = TestObserver<String>()
         flowable.subscribe(observer)
         drain()
diff --git a/sqlite/sqlite-bundled/build.gradle b/sqlite/sqlite-bundled/build.gradle
index 6c4caeb..3bc7daf 100644
--- a/sqlite/sqlite-bundled/build.gradle
+++ b/sqlite/sqlite-bundled/build.gradle
@@ -249,5 +249,4 @@
     inceptionYear = "2023"
     description = "The implementation of SQLite library using the bundled SQLite."
     metalavaK2UastEnabled = false
-    legacyDisableKotlinStrictApiMode = true
 }
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt
index 6adf9ca..30bd8d1 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.kt
@@ -23,39 +23,39 @@
 import kotlin.jvm.JvmName
 
 /** Opens the database in read-only mode. */
-const val SQLITE_OPEN_READONLY = 0x00000001
+public const val SQLITE_OPEN_READONLY: Int = 0x00000001
 
 /** Opens the database for reading and writing. */
-const val SQLITE_OPEN_READWRITE = 0x00000002
+public const val SQLITE_OPEN_READWRITE: Int = 0x00000002
 
 /** Create the database if it does not already exist. */
-const val SQLITE_OPEN_CREATE = 0x00000004
+public const val SQLITE_OPEN_CREATE: Int = 0x00000004
 
 /** Interpret the filename as a URI. */
-const val SQLITE_OPEN_URI = 0x00000040
+public const val SQLITE_OPEN_URI: Int = 0x00000040
 
 /** Opens the database as a in-memory database. */
-const val SQLITE_OPEN_MEMORY = 0x00000080
+public const val SQLITE_OPEN_MEMORY: Int = 0x00000080
 
 /**
  * The database connection will use the "multi-thread" threading mode.
  *
  * See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
  */
-const val SQLITE_OPEN_NOMUTEX = 0x00008000
+public const val SQLITE_OPEN_NOMUTEX: Int = 0x00008000
 
 /**
  * The database connection will use the "serialized" threading mode.
  *
  * See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
  */
-const val SQLITE_OPEN_FULLMUTEX = 0x00010000
+public const val SQLITE_OPEN_FULLMUTEX: Int = 0x00010000
 
 /** The filename is not allowed to contain a symbolic link. */
-const val SQLITE_OPEN_NOFOLLOW = 0x01000000
+public const val SQLITE_OPEN_NOFOLLOW: Int = 0x01000000
 
 /** The database connection will use extended result codes. */
-const val SQLITE_OPEN_EXRESCODE = 0x02000000
+public const val SQLITE_OPEN_EXRESCODE: Int = 0x02000000
 
 /** The flags constant that can be used with [BundledSQLiteDriver.open]. */
 @IntDef(
@@ -75,4 +75,4 @@
 )
 @Retention(AnnotationRetention.SOURCE)
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-expect annotation class OpenFlag()
+public expect annotation class OpenFlag()
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt
index 49b5731..0eec41a 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.kt
@@ -20,4 +20,5 @@
 import androidx.sqlite.SQLiteConnection
 
 // Restricted instead of internal due to KT-37316
-@RestrictTo(RestrictTo.Scope.LIBRARY) expect class BundledSQLiteConnection : SQLiteConnection
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public expect class BundledSQLiteConnection : SQLiteConnection
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
index b55c476..66ee02b 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
@@ -24,14 +24,14 @@
  * A [SQLiteDriver] that uses a bundled version of SQLite included as a native component of this
  * library.
  */
-expect class BundledSQLiteDriver() : SQLiteDriver {
+public expect class BundledSQLiteDriver() : SQLiteDriver {
 
     /**
      * The thread safe mode SQLite was compiled with.
      *
      * See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) val threadingMode: Int
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public val threadingMode: Int
 
     /**
      * Opens a new database connection.
@@ -42,5 +42,5 @@
      * @param flags Connection open flags.
      * @return the database connection.
      */
-    fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection
+    public fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection
 }
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt
index a48d56e..7b82a28 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.kt
@@ -20,4 +20,4 @@
 import androidx.sqlite.SQLiteStatement
 
 // Restricted instead of internal due to KT-37316
-@RestrictTo(RestrictTo.Scope.LIBRARY) expect class BundledSQLiteStatement : SQLiteStatement
+@RestrictTo(RestrictTo.Scope.LIBRARY) public expect class BundledSQLiteStatement : SQLiteStatement
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt
index 1717770..d283118 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.jvmAndroid.kt
@@ -36,7 +36,7 @@
 )
 @Retention(AnnotationRetention.SOURCE)
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-actual annotation class OpenFlag
+public actual annotation class OpenFlag
 
 internal object ResultCode {
     const val SQLITE_MISUSE = 21
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt
index 65be6da..3d34667 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.jvmAndroid.kt
@@ -24,7 +24,8 @@
 import androidx.sqlite.throwSQLiteException
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-actual class BundledSQLiteConnection(private val connectionPointer: Long) : SQLiteConnection {
+public actual class BundledSQLiteConnection(private val connectionPointer: Long) :
+    SQLiteConnection {
 
     @OptIn(ExperimentalStdlibApi::class) @Volatile private var isClosed = false
 
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt
index de1e6eb..a38a069 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.jvmAndroid.kt
@@ -27,7 +27,7 @@
  */
 // TODO(b/313895287): Explore usability of @FastNative and @CriticalNative for the external
 // functions.
-actual class BundledSQLiteDriver : SQLiteDriver {
+public actual class BundledSQLiteDriver : SQLiteDriver {
 
     /**
      * The thread safe mode SQLite was compiled with.
@@ -35,7 +35,7 @@
      * See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
      */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    actual val threadingMode: Int
+    public actual val threadingMode: Int
         get() = nativeThreadSafeMode()
 
     override fun open(fileName: String): SQLiteConnection {
@@ -51,7 +51,7 @@
      * @param flags Connection open flags.
      * @return the database connection.
      */
-    actual fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection {
+    public actual fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection {
         val address = nativeOpen(fileName, flags)
         return BundledSQLiteConnection(address)
     }
diff --git a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt
index f33c4ab..f2577c5 100644
--- a/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt
+++ b/sqlite/sqlite-bundled/src/jvmAndroidMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.jvmAndroid.kt
@@ -23,7 +23,7 @@
 import androidx.sqlite.throwSQLiteException
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-actual class BundledSQLiteStatement(
+public actual class BundledSQLiteStatement(
     private val connectionPointer: Long,
     private val statementPointer: Long
 ) : SQLiteStatement {
@@ -118,7 +118,7 @@
         }
     }
 
-    companion object {
+    private companion object {
         private const val COLUMN_TYPE_INTEGER = 1
         private const val COLUMN_TYPE_FLOAT = 2
         private const val COLUMN_TYPE_TEXT = 3
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt
index d3d1d5d..2a65818 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLite.nativeCommon.kt
@@ -16,4 +16,4 @@
 
 package androidx.sqlite.driver.bundled
 
-actual typealias OpenFlag = androidx.sqlite.driver.OpenFlag
+public actual typealias OpenFlag = androidx.sqlite.driver.OpenFlag
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt
index d466e1a..a368590 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteConnection.native.kt
@@ -20,4 +20,4 @@
 
 import androidx.annotation.RestrictTo
 
-actual typealias BundledSQLiteConnection = androidx.sqlite.driver.NativeSQLiteConnection
+public actual typealias BundledSQLiteConnection = androidx.sqlite.driver.NativeSQLiteConnection
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt
index c39d148..4d2be0d 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.native.kt
@@ -22,4 +22,4 @@
  * A [SQLiteDriver] that uses a bundled version of SQLite included as a native component of this
  * library.
  */
-actual typealias BundledSQLiteDriver = androidx.sqlite.driver.NativeSQLiteDriver
+public actual typealias BundledSQLiteDriver = androidx.sqlite.driver.NativeSQLiteDriver
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt
index 92e09eb..7afcd68 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.native.kt
@@ -20,4 +20,4 @@
 
 import androidx.annotation.RestrictTo
 
-actual typealias BundledSQLiteStatement = androidx.sqlite.driver.NativeSQLiteStatement
+public actual typealias BundledSQLiteStatement = androidx.sqlite.driver.NativeSQLiteStatement
diff --git a/sqlite/sqlite-framework/build.gradle b/sqlite/sqlite-framework/build.gradle
index 7ad2380..411b332f 100644
--- a/sqlite/sqlite-framework/build.gradle
+++ b/sqlite/sqlite-framework/build.gradle
@@ -146,7 +146,6 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "The implementation of SQLite library using the framework code."
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt
index f4a55ee..fc1617e 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.android.kt
@@ -20,7 +20,7 @@
 /**
  * Implements [SupportSQLiteOpenHelper.Factory] using the SQLite implementation in the framework.
  */
-class FrameworkSQLiteOpenHelperFactory : SupportSQLiteOpenHelper.Factory {
+public class FrameworkSQLiteOpenHelperFactory : SupportSQLiteOpenHelper.Factory {
     override fun create(
         configuration: SupportSQLiteOpenHelper.Configuration
     ): SupportSQLiteOpenHelper {
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt
index 375a279..75ae53e 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteConnection.android.kt
@@ -24,7 +24,7 @@
 import androidx.sqlite.throwSQLiteException
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-class AndroidSQLiteConnection(val db: SQLiteDatabase) : SQLiteConnection {
+public class AndroidSQLiteConnection(public val db: SQLiteDatabase) : SQLiteConnection {
     override fun prepare(sql: String): SQLiteStatement {
         if (db.isOpen) {
             return AndroidSQLiteStatement.create(db, sql)
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
index 245be46..9db9d96 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
@@ -23,7 +23,7 @@
 /**
  * A [SQLiteDriver] implemented by [android.database] and that uses the Android's SDK SQLite APIs.
  */
-class AndroidSQLiteDriver : SQLiteDriver {
+public class AndroidSQLiteDriver : SQLiteDriver {
     override fun open(fileName: String): SQLiteConnection {
         val database = SQLiteDatabase.openOrCreateDatabase(fileName, null)
         return AndroidSQLiteConnection(database)
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt
index ebb9046..706903c 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/util/ProcessLock.android.kt
@@ -47,7 +47,7 @@
  *   can be overridden via the [lock] method.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ProcessLock(name: String, lockDir: File?, private val processLock: Boolean) {
+public class ProcessLock(name: String, lockDir: File?, private val processLock: Boolean) {
     private val lockFile: File? = lockDir?.let { File(it, "$name.lck") }
     private val threadLock: Lock = getThreadLock(name)
     private var lockChannel: FileChannel? = null
@@ -57,7 +57,7 @@
      *
      * @param [processLock] whether to use file for process level locking or not.
      */
-    fun lock(processLock: Boolean = this.processLock) {
+    public fun lock(processLock: Boolean = this.processLock) {
         threadLock.lock()
         if (processLock) {
             try {
@@ -76,14 +76,14 @@
     }
 
     /** Releases the lock. */
-    fun unlock() {
+    public fun unlock() {
         try {
             lockChannel?.close()
         } catch (ignored: IOException) {}
         threadLock.unlock()
     }
 
-    companion object {
+    private companion object {
         private const val TAG = "SupportSQLiteLock"
         // in-process lock map
         private val threadLocksMap: MutableMap<String, Lock> = HashMap()
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt
index 0e060fa..2534593 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLite.kt
@@ -52,7 +52,7 @@
 )
 @Retention(AnnotationRetention.SOURCE)
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-annotation class OpenFlag
+public annotation class OpenFlag
 
 internal fun CPointer<sqlite3>.getErrorMsg(): String? {
     return sqlite3_errmsg16(this)?.reinterpret<UShortVar>()?.toKStringFromUtf16()
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt
index c3fce96..6ff1dbb 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteConnection.kt
@@ -35,7 +35,7 @@
 import sqlite3.sqlite3_prepare16_v2
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For actual typealias in unbundled
-class NativeSQLiteConnection(private val dbPointer: CPointer<sqlite3>) : SQLiteConnection {
+public class NativeSQLiteConnection(private val dbPointer: CPointer<sqlite3>) : SQLiteConnection {
 
     @OptIn(ExperimentalStdlibApi::class) @Volatile private var isClosed = false
 
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
index 5040a1e..ebfe6d8 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
@@ -36,7 +36,7 @@
  *
  * Usage of this driver expects that `libsqlite` can be found in the shared library path.
  */
-class NativeSQLiteDriver : SQLiteDriver {
+public class NativeSQLiteDriver : SQLiteDriver {
 
     /**
      * The thread safe mode SQLite was compiled with.
@@ -44,7 +44,7 @@
      * See also [SQLite In Multi-Threaded Applications](https://www.sqlite.org/threadsafe.html)
      */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-    val threadingMode: Int
+    public val threadingMode: Int
         get() = sqlite3_threadsafe()
 
     override fun open(fileName: String): SQLiteConnection {
@@ -60,7 +60,7 @@
      * @param flags Connection open flags.
      * @return the database connection.
      */
-    fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection = memScoped {
+    public fun open(fileName: String, @OpenFlag flags: Int): SQLiteConnection = memScoped {
         val dbPointer = allocPointerTo<sqlite3>()
         val resultCode =
             sqlite3_open_v2(filename = fileName, ppDb = dbPointer.ptr, flags = flags, zVfs = null)
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
index 1995de0..dfd2829 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
@@ -57,7 +57,7 @@
 import sqlite3.sqlite3_step
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For actual typealias in unbundled
-class NativeSQLiteStatement(
+public class NativeSQLiteStatement(
     private val dbPointer: CPointer<sqlite3>,
     private val stmtPointer: CPointer<sqlite3_stmt>
 ) : SQLiteStatement {
diff --git a/sqlite/sqlite-ktx/build.gradle b/sqlite/sqlite-ktx/build.gradle
index b03f306..90d69c5 100644
--- a/sqlite/sqlite-ktx/build.gradle
+++ b/sqlite/sqlite-ktx/build.gradle
@@ -42,7 +42,6 @@
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2018"
     description = "Kotlin extensions for DB"
-    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt b/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt
index e637a991..51d244f 100644
--- a/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt
+++ b/sqlite/sqlite-ktx/src/main/java/androidx/sqlite/db/SupportSQLiteDatabaseExt.kt
@@ -23,7 +23,7 @@
  * @param exclusive Run in `EXCLUSIVE` mode when true, `IMMEDIATE` mode otherwise.
  * @param body Lambda to be run in the transaction.
  */
-inline fun <T> SupportSQLiteDatabase.transaction(
+public inline fun <T> SupportSQLiteDatabase.transaction(
     exclusive: Boolean = true,
     body: SupportSQLiteDatabase.() -> T
 ): T {
diff --git a/sqlite/sqlite/build.gradle b/sqlite/sqlite/build.gradle
index 59c4baf..3c4a438 100644
--- a/sqlite/sqlite/build.gradle
+++ b/sqlite/sqlite/build.gradle
@@ -90,5 +90,4 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2017"
     description = "SQLite API"
-    legacyDisableKotlinStrictApiMode = true
 }
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt
index febb4af..6401e1f 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/SQLiteException.android.kt
@@ -15,4 +15,4 @@
  */
 package androidx.sqlite
 
-actual typealias SQLiteException = android.database.SQLException
+public actual typealias SQLiteException = android.database.SQLException
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt
index bbe86d9..1939a45 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SimpleSQLiteQuery.android.kt
@@ -24,7 +24,7 @@
  * @constructor Creates an SQL query with the sql string and the bind arguments.
  */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-class SimpleSQLiteQuery(
+public class SimpleSQLiteQuery(
     private val query: String,
     @Suppress("ArrayReturn") // Due to legacy API
     private val bindArgs: Array<out Any?>?
@@ -35,7 +35,7 @@
      *
      * @param query The SQL query to execute. Cannot include bind parameters.
      */
-    constructor(query: String) : this(query, null)
+    public constructor(query: String) : this(query, null)
 
     override val sql: String
         get() = this.query
@@ -53,7 +53,7 @@
     override val argCount: Int
         get() = bindArgs?.size ?: 0
 
-    companion object {
+    public companion object {
         /**
          * Binds the given arguments into the given sqlite statement.
          *
@@ -61,7 +61,7 @@
          * @param [bindArgs] The list of bind arguments
          */
         @JvmStatic
-        fun bind(
+        public fun bind(
             @Suppress("AcronymName") // SQL is a known term and should remain capitalized
             statement: SupportSQLiteProgram,
             @Suppress("ArrayReturn") // Due to legacy API
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt
index 856d8ba..8e56dc1 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteCompat.android.kt
@@ -26,11 +26,11 @@
 
 /** Helper for accessing features in [SupportSQLiteOpenHelper]. */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class SupportSQLiteCompat private constructor() {
+public class SupportSQLiteCompat private constructor() {
     /** Helper for accessing functions that require SDK version 21 and higher. */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @RequiresApi(21)
-    object Api21Impl {
+    public object Api21Impl {
         /**
          * Returns the absolute path to the directory on the filesystem.
          *
@@ -39,7 +39,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
-        fun getNoBackupFilesDir(context: Context): File {
+        public fun getNoBackupFilesDir(context: Context): File {
             return context.noBackupFilesDir
         }
     }
@@ -47,7 +47,7 @@
     /** Helper for accessing functions that require SDK version 23 and higher. */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @RequiresApi(23)
-    object Api23Impl {
+    public object Api23Impl {
         /**
          * Sets a [Bundle] that will be returned by [Cursor.getExtras].
          *
@@ -55,7 +55,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
-        fun setExtras(cursor: Cursor, extras: Bundle) {
+        public fun setExtras(cursor: Cursor, extras: Bundle) {
             cursor.extras = extras
         }
     }
@@ -63,7 +63,7 @@
     /** Helper for accessing functions that require SDK version 29 and higher. */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @RequiresApi(29)
-    object Api29Impl {
+    public object Api29Impl {
         /**
          * Similar to [Cursor.setNotificationUri], except this version allows to watch multiple
          * content URIs for changes.
@@ -74,7 +74,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
-        fun setNotificationUris(cursor: Cursor, cr: ContentResolver, uris: List<Uri?>) {
+        public fun setNotificationUris(cursor: Cursor, cr: ContentResolver, uris: List<Uri?>) {
             cursor.setNotificationUris(cr, uris)
         }
 
@@ -88,7 +88,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @JvmStatic
-        fun getNotificationUris(cursor: Cursor): List<Uri> {
+        public fun getNotificationUris(cursor: Cursor): List<Uri> {
             return cursor.notificationUris!!
         }
     }
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt
index 4f4e98c..5c6d9dd 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteDatabase.android.kt
@@ -30,14 +30,14 @@
  * versions. It mimics the behavior of [android.database.sqlite.SQLiteDatabase]
  */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteDatabase : Closeable {
+public interface SupportSQLiteDatabase : Closeable {
     /**
      * Compiles the given SQL statement.
      *
      * @param sql The sql query.
      * @return Compiled statement.
      */
-    fun compileStatement(sql: String): SupportSQLiteStatement
+    public fun compileStatement(sql: String): SupportSQLiteStatement
 
     /**
      * Begins a transaction in EXCLUSIVE mode.
@@ -58,7 +58,7 @@
      *  }
      * ```
      */
-    fun beginTransaction()
+    public fun beginTransaction()
 
     /**
      * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When the outer
@@ -78,7 +78,7 @@
      *  }
      *  ```
      */
-    fun beginTransactionNonExclusive()
+    public fun beginTransactionNonExclusive()
 
     /**
      * Begins a transaction in DEFERRED mode, with the android-specific constraint that the
@@ -107,7 +107,7 @@
      * If the implementation does not support read-only transactions then the default implementation
      * delegates to [beginTransaction].
      */
-    fun beginTransactionReadOnly() {
+    public fun beginTransactionReadOnly() {
         beginTransaction()
     }
 
@@ -133,7 +133,7 @@
      * @param transactionListener listener that should be notified when the transaction begins,
      *   commits, or is rolled back, either explicitly or by a call to [yieldIfContendedSafely].
      */
-    fun beginTransactionWithListener(transactionListener: SQLiteTransactionListener)
+    public fun beginTransactionWithListener(transactionListener: SQLiteTransactionListener)
 
     /**
      * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When the outer
@@ -156,7 +156,9 @@
      * @param transactionListener listener that should be notified when the transaction begins,
      *   commits, or is rolled back, either explicitly or by a call to [yieldIfContendedSafely].
      */
-    fun beginTransactionWithListenerNonExclusive(transactionListener: SQLiteTransactionListener)
+    public fun beginTransactionWithListenerNonExclusive(
+        transactionListener: SQLiteTransactionListener
+    )
 
     /**
      * Begins a transaction in read-only mode with a {@link SQLiteTransactionListener} listener. The
@@ -182,7 +184,9 @@
      * delegates to [beginTransactionWithListener].
      */
     @Suppress("ExecutorRegistration")
-    fun beginTransactionWithListenerReadOnly(transactionListener: SQLiteTransactionListener) {
+    public fun beginTransactionWithListenerReadOnly(
+        transactionListener: SQLiteTransactionListener
+    ) {
         beginTransactionWithListener(transactionListener)
     }
 
@@ -190,7 +194,7 @@
      * End a transaction. See beginTransaction for notes about how to use this and when transactions
      * are committed and rolled back.
      */
-    fun endTransaction()
+    public fun endTransaction()
 
     /**
      * Marks the current transaction as successful. Do not do any more database work between calling
@@ -201,14 +205,14 @@
      * @throws IllegalStateException if the current thread is not in a transaction or the
      *   transaction is already marked as successful.
      */
-    fun setTransactionSuccessful()
+    public fun setTransactionSuccessful()
 
     /**
      * Returns true if the current thread has a transaction pending.
      *
      * @return True if the current thread is in a transaction.
      */
-    fun inTransaction(): Boolean
+    public fun inTransaction(): Boolean
 
     /**
      * True if the current thread is holding an active connection to the database.
@@ -218,7 +222,7 @@
      * longer a true "database lock" although threads may block if they cannot acquire a database
      * connection to perform a particular operation.
      */
-    val isDbLockedByCurrentThread: Boolean
+    public val isDbLockedByCurrentThread: Boolean
 
     /**
      * Temporarily end the transaction to let other threads run. The transaction is assumed to be
@@ -229,7 +233,7 @@
      *
      * @return true if the transaction was yielded
      */
-    fun yieldIfContendedSafely(): Boolean
+    public fun yieldIfContendedSafely(): Boolean
 
     /**
      * Temporarily end the transaction to let other threads run. The transaction is assumed to be
@@ -243,11 +247,11 @@
      *   more progress than they would if we started the transaction immediately.
      * @return true if the transaction was yielded
      */
-    fun yieldIfContendedSafely(sleepAfterYieldDelayMillis: Long): Boolean
+    public fun yieldIfContendedSafely(sleepAfterYieldDelayMillis: Long): Boolean
 
     /** Is true if [execPerConnectionSQL] is supported by the implementation. */
     @get:Suppress("AcronymName") // To keep consistency with framework method name.
-    val isExecPerConnectionSQLSupported: Boolean
+    public val isExecPerConnectionSQLSupported: Boolean
         get() = false
 
     /**
@@ -272,15 +276,18 @@
      *   supported use [isExecPerConnectionSQLSupported]
      */
     @Suppress("AcronymName") // To keep consistency with framework method name.
-    fun execPerConnectionSQL(sql: String, @SuppressLint("ArrayReturn") bindArgs: Array<out Any?>?) {
+    public fun execPerConnectionSQL(
+        sql: String,
+        @SuppressLint("ArrayReturn") bindArgs: Array<out Any?>?
+    ) {
         throw UnsupportedOperationException()
     }
 
     /** The database version. */
-    var version: Int
+    public var version: Int
 
     /** The maximum size the database may grow to. */
-    val maximumSize: Long
+    public val maximumSize: Long
 
     /**
      * Sets the maximum size the database will grow to. The maximum size cannot be set below the
@@ -289,7 +296,7 @@
      * @param numBytes the maximum database size, in bytes
      * @return the new maximum database size
      */
-    fun setMaximumSize(numBytes: Long): Long
+    public fun setMaximumSize(numBytes: Long): Long
 
     /**
      * The current database page size, in bytes.
@@ -297,7 +304,7 @@
      * The page size must be a power of two. This method does not work if any data has been written
      * to the database file, and must be called right after the database has been created.
      */
-    var pageSize: Long
+    public var pageSize: Long
 
     /**
      * Runs the given query on the database. If you would like to have typed bind arguments, use
@@ -308,7 +315,7 @@
      * @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
      *   are not synchronized, see the documentation for more details.
      */
-    fun query(query: String): Cursor
+    public fun query(query: String): Cursor
 
     /**
      * Runs the given query on the database. If you would like to have bind arguments, use [query].
@@ -319,7 +326,7 @@
      * @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
      *   are not synchronized, see the documentation for more details.
      */
-    fun query(query: String, bindArgs: Array<out Any?>): Cursor
+    public fun query(query: String, bindArgs: Array<out Any?>): Cursor
 
     /**
      * Runs the given query on the database.
@@ -331,7 +338,7 @@
      * @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
      *   are not synchronized, see the documentation for more details.
      */
-    fun query(query: SupportSQLiteQuery): Cursor
+    public fun query(query: SupportSQLiteQuery): Cursor
 
     /**
      * Runs the given query on the database.
@@ -346,7 +353,7 @@
      * @return A [Cursor] object, which is positioned before the first entry. Note that [Cursor]s
      *   are not synchronized, see the documentation for more details.
      */
-    fun query(query: SupportSQLiteQuery, cancellationSignal: CancellationSignal?): Cursor
+    public fun query(query: SupportSQLiteQuery, cancellationSignal: CancellationSignal?): Cursor
 
     /**
      * Convenience method for inserting a row into the database.
@@ -365,7 +372,7 @@
      * @throws SQLException If the insert fails
      */
     @Throws(SQLException::class)
-    fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long
+    public fun insert(table: String, conflictAlgorithm: Int, values: ContentValues): Long
 
     /**
      * Convenience method for deleting rows in the database.
@@ -378,7 +385,7 @@
      * @return the number of rows affected if a whereClause is passed in, 0 otherwise. To remove all
      *   rows and get a count pass "1" as the whereClause.
      */
-    fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int
+    public fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int
 
     /**
      * Convenience method for updating rows in the database.
@@ -399,7 +406,7 @@
      *   from whereArgs. The values will be bound as Strings.
      * @return the number of rows affected
      */
-    fun update(
+    public fun update(
         table: String,
         conflictAlgorithm: Int,
         values: ContentValues,
@@ -420,7 +427,7 @@
      */
     @Suppress("AcronymName") // SQL is a known term and should remain capitalized
     @Throws(SQLException::class)
-    fun execSQL(sql: String)
+    public fun execSQL(sql: String)
 
     /**
      * Execute a single SQL statement that does not return any data.
@@ -436,13 +443,13 @@
      */
     @Suppress("AcronymName") // SQL is a known term and should remain capitalized
     @Throws(SQLException::class)
-    fun execSQL(sql: String, bindArgs: Array<out Any?>)
+    public fun execSQL(sql: String, bindArgs: Array<out Any?>)
 
     /** Is true if the database is opened as read only. */
-    val isReadOnly: Boolean
+    public val isReadOnly: Boolean
 
     /** Is true if the database is currently open. */
-    val isOpen: Boolean
+    public val isOpen: Boolean
 
     /**
      * Returns true if the new version code is greater than the current database version.
@@ -450,10 +457,10 @@
      * @param newVersion The new version code.
      * @return True if the new version code is greater than the current database version.
      */
-    fun needUpgrade(newVersion: Int): Boolean
+    public fun needUpgrade(newVersion: Int): Boolean
 
     /** The path to the database file. */
-    val path: String?
+    public val path: String?
 
     /**
      * Sets the locale for this database. Does nothing if this database has the
@@ -465,7 +472,7 @@
      *   there is no collator available for the locale you requested. In this case the database
      *   remains unchanged.
      */
-    fun setLocale(locale: Locale)
+    public fun setLocale(locale: Locale)
 
     /**
      * Sets the maximum size of the prepared-statement cache for this database. (size of the cache =
@@ -482,7 +489,7 @@
      * @throws IllegalStateException if input cacheSize is over the max.
      *   [android.database.sqlite.SQLiteDatabase.MAX_SQL_CACHE_SIZE].
      */
-    fun setMaxSqlCacheSize(cacheSize: Int)
+    public fun setMaxSqlCacheSize(cacheSize: Int)
 
     /**
      * Sets whether foreign key constraints are enabled for the database.
@@ -509,7 +516,7 @@
      * @throws IllegalStateException if the are transactions is in progress when this method is
      *   called.
      */
-    fun setForeignKeyConstraintsEnabled(enabled: Boolean)
+    public fun setForeignKeyConstraintsEnabled(enabled: Boolean)
 
     /**
      * This method enables parallel execution of queries from multiple threads on the same database.
@@ -573,7 +580,7 @@
      * @throws IllegalStateException if there are transactions in progress at the time this method
      *   is called. WAL mode can only be changed when there are no transactions in progress.
      */
-    fun enableWriteAheadLogging(): Boolean
+    public fun enableWriteAheadLogging(): Boolean
 
     /**
      * This method disables the features enabled by [enableWriteAheadLogging].
@@ -581,20 +588,20 @@
      * @throws IllegalStateException if there are transactions in progress at the time this method
      *   is called. WAL mode can only be changed when there are no transactions in progress.
      */
-    fun disableWriteAheadLogging()
+    public fun disableWriteAheadLogging()
 
     /** Is true if write-ahead logging has been enabled for this database. */
-    val isWriteAheadLoggingEnabled: Boolean
+    public val isWriteAheadLoggingEnabled: Boolean
 
     /**
      * The list of full path names of all attached databases including the main database by
      * executing 'pragma database_list' on the database.
      */
-    @get:Suppress("NullableCollection") val attachedDbs: List<Pair<String, String>>?
+    @get:Suppress("NullableCollection") public val attachedDbs: List<Pair<String, String>>?
 
     /**
      * Is true if the given database (and all its attached databases) pass integrity_check, false
      * otherwise.
      */
-    val isDatabaseIntegrityOk: Boolean
+    public val isDatabaseIntegrityOk: Boolean
 }
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt
index 90aee80..2be3a94 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.android.kt
@@ -32,12 +32,12 @@
  * create this and [Callback] to implement the methods that should be overridden.
  */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteOpenHelper : Closeable {
+public interface SupportSQLiteOpenHelper : Closeable {
     /**
      * Return the name of the SQLite database being opened, as given to the constructor. `null`
      * indicates an in-memory database.
      */
-    val databaseName: String?
+    public val databaseName: String?
 
     /**
      * Enables or disables the use of write-ahead logging for the database.
@@ -49,7 +49,7 @@
      *
      * @param enabled True if write-ahead logging should be enabled, false if it should be disabled.
      */
-    fun setWriteAheadLoggingEnabled(enabled: Boolean)
+    public fun setWriteAheadLoggingEnabled(enabled: Boolean)
 
     /**
      * Create and/or open a database that will be used for reading and writing. The first time this
@@ -67,7 +67,7 @@
      * @return a read/write database object valid until [close] is called
      * @throws SQLiteException if the database cannot be opened for writing
      */
-    val writableDatabase: SupportSQLiteDatabase
+    public val writableDatabase: SupportSQLiteDatabase
 
     /**
      * Create and/or open a database. This will be the same object returned by [writableDatabase]
@@ -82,7 +82,7 @@
      * @return a database object valid until [writableDatabase] or [close] is called.
      * @throws SQLiteException if the database cannot be opened
      */
-    val readableDatabase: SupportSQLiteDatabase
+    public val readableDatabase: SupportSQLiteDatabase
 
     /** Close any open database object. */
     override fun close()
@@ -93,13 +93,13 @@
      * Handles various lifecycle events for the SQLite connection, similar to
      * [room-runtime.SQLiteOpenHelper].
      */
-    abstract class Callback(
+    public abstract class Callback(
         /**
          * Version number of the database (starting at 1); if the database is older,
          * [Callback.onUpgrade] will be used to upgrade the database; if the database is newer,
          * [Callback.onDowngrade] will be used to downgrade the database.
          */
-        @JvmField val version: Int
+        @JvmField public val version: Int
     ) {
         /**
          * Called when the database connection is being configured, to enable features such as
@@ -117,7 +117,7 @@
          *
          * @param db The database.
          */
-        open fun onConfigure(db: SupportSQLiteDatabase) {}
+        public open fun onConfigure(db: SupportSQLiteDatabase) {}
 
         /**
          * Called when the database is created for the first time. This is where the creation of
@@ -125,7 +125,7 @@
          *
          * @param db The database.
          */
-        abstract fun onCreate(db: SupportSQLiteDatabase)
+        public abstract fun onCreate(db: SupportSQLiteDatabase)
 
         /**
          * Called when the database needs to be upgraded. The implementation should use this method
@@ -145,7 +145,7 @@
          * @param oldVersion The old database version.
          * @param newVersion The new database version.
          */
-        abstract fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int)
+        public abstract fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int)
 
         /**
          * Called when the database needs to be downgraded. This is strictly similar to [onUpgrade]
@@ -160,7 +160,7 @@
          * @param oldVersion The old database version.
          * @param newVersion The new database version.
          */
-        open fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+        public open fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
             throw SQLiteException(
                 "Can't downgrade database from version $oldVersion to $newVersion"
             )
@@ -177,7 +177,7 @@
          *
          * @param db The database.
          */
-        open fun onOpen(db: SupportSQLiteDatabase) {}
+        public open fun onOpen(db: SupportSQLiteDatabase) {}
 
         /**
          * The method invoked when database corruption is detected. Default implementation will
@@ -186,7 +186,7 @@
          * @param db the [SupportSQLiteDatabase] object representing the database on which
          *   corruption is detected.
          */
-        open fun onCorruption(db: SupportSQLiteDatabase) {
+        public open fun onCorruption(db: SupportSQLiteDatabase) {
             // the following implementation is taken from {@link DefaultDatabaseErrorHandler}.
             Log.e(TAG, "Corruption reported by sqlite on database: $db.path")
             // is the corruption detected even before database could be 'opened'?
@@ -245,26 +245,26 @@
     }
 
     /** The configuration to create an SQLite open helper object using [Factory]. */
-    class Configuration
+    public class Configuration
     @Suppress("ExecutorRegistration") // For backwards compatibility
     constructor(
         /** Context to use to open or create the database. */
-        @JvmField val context: Context,
+        @JvmField public val context: Context,
         /** Name of the database file, or null for an in-memory database. */
-        @JvmField val name: String?,
+        @JvmField public val name: String?,
         /** The callback class to handle creation, upgrade and downgrade. */
-        @JvmField val callback: Callback,
+        @JvmField public val callback: Callback,
         /** If `true` the database will be stored in the no-backup directory. */
-        @JvmField @Suppress("ListenerLast") val useNoBackupDirectory: Boolean = false,
+        @JvmField @Suppress("ListenerLast") public val useNoBackupDirectory: Boolean = false,
         /**
          * If `true` the database will be delete and its data loss in the case that it cannot be
          * opened.
          */
-        @JvmField @Suppress("ListenerLast") val allowDataLossOnRecovery: Boolean = false
+        @JvmField @Suppress("ListenerLast") public val allowDataLossOnRecovery: Boolean = false
     ) {
 
         /** Builder class for [Configuration]. */
-        open class Builder internal constructor(context: Context) {
+        public open class Builder internal constructor(context: Context) {
             private val context: Context
             private var name: String? = null
             private var callback: Callback? = null
@@ -281,7 +281,7 @@
              *
              * @return The [Configuration] instance
              */
-            open fun build(): Configuration {
+            public open fun build(): Configuration {
                 val callback = callback
                 requireNotNull(callback) { "Must set a callback to create the configuration." }
                 require(!useNoBackupDirectory || !name.isNullOrEmpty()) {
@@ -305,13 +305,15 @@
              * @param name Name of the database file, or null for an in-memory database.
              * @return This builder instance.
              */
-            open fun name(name: String?): Builder = apply { this.name = name }
+            public open fun name(name: String?): Builder = apply { this.name = name }
 
             /**
              * @param callback The callback class to handle creation, upgrade and downgrade.
              * @return This builder instance.
              */
-            open fun callback(callback: Callback): Builder = apply { this.callback = callback }
+            public open fun callback(callback: Callback): Builder = apply {
+                this.callback = callback
+            }
 
             /**
              * Sets whether to use a no backup directory or not.
@@ -320,7 +322,7 @@
              *   no-backup directory.
              * @return This builder instance.
              */
-            open fun noBackupDirectory(useNoBackupDirectory: Boolean): Builder = apply {
+            public open fun noBackupDirectory(useNoBackupDirectory: Boolean): Builder = apply {
                 this.useNoBackupDirectory = useNoBackupDirectory
             }
 
@@ -332,32 +334,33 @@
              *   case that it cannot be opened.
              * @return this
              */
-            open fun allowDataLossOnRecovery(allowDataLossOnRecovery: Boolean): Builder = apply {
-                this.allowDataLossOnRecovery = allowDataLossOnRecovery
-            }
+            public open fun allowDataLossOnRecovery(allowDataLossOnRecovery: Boolean): Builder =
+                apply {
+                    this.allowDataLossOnRecovery = allowDataLossOnRecovery
+                }
         }
 
-        companion object {
+        public companion object {
             /**
              * Creates a new Configuration.Builder to create an instance of Configuration.
              *
              * @param context to use to open or create the database.
              */
             @JvmStatic
-            fun builder(context: Context): Builder {
+            public fun builder(context: Context): Builder {
                 return Builder(context)
             }
         }
     }
 
     /** Factory class to create instances of [SupportSQLiteOpenHelper] using [Configuration]. */
-    fun interface Factory {
+    public fun interface Factory {
         /**
          * Creates an instance of [SupportSQLiteOpenHelper] using the given configuration.
          *
          * @param configuration The configuration to use while creating the open helper.
          * @return A SupportSQLiteOpenHelper which can be used to open a database.
          */
-        fun create(configuration: Configuration): SupportSQLiteOpenHelper
+        public fun create(configuration: Configuration): SupportSQLiteOpenHelper
     }
 }
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt
index 9bff8c4..572879d 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteProgram.android.kt
@@ -19,14 +19,14 @@
 
 /** An interface to map the behavior of [android.database.sqlite.SQLiteProgram]. */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteProgram : Closeable {
+public interface SupportSQLiteProgram : Closeable {
     /**
      * Bind a NULL value to this statement. The value remains bound until [.clearBindings] is
      * called.
      *
      * @param index The 1-based index to the parameter to bind null to
      */
-    fun bindNull(index: Int)
+    public fun bindNull(index: Int)
 
     /**
      * Bind a long value to this statement. The value remains bound until [clearBindings] is called.
@@ -35,7 +35,7 @@
      * @param index The 1-based index to the parameter to bind
      * @param value The value to bind
      */
-    fun bindLong(index: Int, value: Long)
+    public fun bindLong(index: Int, value: Long)
 
     /**
      * Bind a double value to this statement. The value remains bound until [.clearBindings] is
@@ -44,7 +44,7 @@
      * @param index The 1-based index to the parameter to bind
      * @param value The value to bind
      */
-    fun bindDouble(index: Int, value: Double)
+    public fun bindDouble(index: Int, value: Double)
 
     /**
      * Bind a String value to this statement. The value remains bound until [.clearBindings] is
@@ -53,7 +53,7 @@
      * @param index The 1-based index to the parameter to bind
      * @param value The value to bind, must not be null
      */
-    fun bindString(index: Int, value: String)
+    public fun bindString(index: Int, value: String)
 
     /**
      * Bind a byte array value to this statement. The value remains bound until [.clearBindings] is
@@ -62,8 +62,8 @@
      * @param index The 1-based index to the parameter to bind
      * @param value The value to bind, must not be null
      */
-    fun bindBlob(index: Int, value: ByteArray)
+    public fun bindBlob(index: Int, value: ByteArray)
 
     /** Clears all existing bindings. Unset bindings are treated as NULL. */
-    fun clearBindings()
+    public fun clearBindings()
 }
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt
index b5ffb7e..3db42f5 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQuery.android.kt
@@ -20,20 +20,20 @@
  * [android.database.sqlite.SQLiteDatabase.rawQuery] because it allows binding type safe parameters.
  */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteQuery {
+public interface SupportSQLiteQuery {
     /** The SQL query. This query can have placeholders(?) for bind arguments. */
-    val sql: String
+    public val sql: String
 
     /**
      * Callback to bind the query parameters to the compiled statement.
      *
      * @param statement The compiled statement
      */
-    fun bindTo(statement: SupportSQLiteProgram)
+    public fun bindTo(statement: SupportSQLiteProgram)
 
     /**
      * Is the number of arguments in this query. This is equal to the number of placeholders in the
      * query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details.
      */
-    val argCount: Int
+    public val argCount: Int
 }
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt
index 60f5817..770820c 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteQueryBuilder.android.kt
@@ -19,7 +19,7 @@
 
 /** A simple query builder to create SQL SELECT queries. */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-class SupportSQLiteQueryBuilder private constructor(private val table: String) {
+public class SupportSQLiteQueryBuilder private constructor(private val table: String) {
     private var distinct = false
     private var columns: Array<out String>? = null
     private var selection: String? = null
@@ -34,7 +34,7 @@
      *
      * @return this
      */
-    fun distinct(): SupportSQLiteQueryBuilder = apply { this.distinct = true }
+    public fun distinct(): SupportSQLiteQueryBuilder = apply { this.distinct = true }
 
     /**
      * Sets the given list of columns as the columns that will be returned.
@@ -42,7 +42,7 @@
      * @param columns The list of column names that should be returned.
      * @return this
      */
-    fun columns(columns: Array<out String>?): SupportSQLiteQueryBuilder = apply {
+    public fun columns(columns: Array<out String>?): SupportSQLiteQueryBuilder = apply {
         this.columns = columns
     }
 
@@ -53,11 +53,13 @@
      * @param bindArgs The list of bind arguments to match against these columns
      * @return this
      */
-    fun selection(selection: String?, bindArgs: Array<out Any?>?): SupportSQLiteQueryBuilder =
-        apply {
-            this.selection = selection
-            this.bindArgs = bindArgs
-        }
+    public fun selection(
+        selection: String?,
+        bindArgs: Array<out Any?>?
+    ): SupportSQLiteQueryBuilder = apply {
+        this.selection = selection
+        this.bindArgs = bindArgs
+    }
 
     /**
      * Adds a GROUP BY statement.
@@ -65,7 +67,9 @@
      * @param groupBy The value of the GROUP BY statement.
      * @return this
      */
-    fun groupBy(groupBy: String?): SupportSQLiteQueryBuilder = apply { this.groupBy = groupBy }
+    public fun groupBy(groupBy: String?): SupportSQLiteQueryBuilder = apply {
+        this.groupBy = groupBy
+    }
 
     /**
      * Adds a HAVING statement. You must also provide [groupBy] for this to work.
@@ -73,7 +77,7 @@
      * @param having The having clause.
      * @return this
      */
-    fun having(having: String?): SupportSQLiteQueryBuilder = apply { this.having = having }
+    public fun having(having: String?): SupportSQLiteQueryBuilder = apply { this.having = having }
 
     /**
      * Adds an ORDER BY statement.
@@ -81,7 +85,9 @@
      * @param orderBy The order clause.
      * @return this
      */
-    fun orderBy(orderBy: String?): SupportSQLiteQueryBuilder = apply { this.orderBy = orderBy }
+    public fun orderBy(orderBy: String?): SupportSQLiteQueryBuilder = apply {
+        this.orderBy = orderBy
+    }
 
     /**
      * Adds a LIMIT statement.
@@ -89,7 +95,7 @@
      * @param limit The limit value.
      * @return this
      */
-    fun limit(limit: String): SupportSQLiteQueryBuilder = apply {
+    public fun limit(limit: String): SupportSQLiteQueryBuilder = apply {
         val patternMatches = limitPattern.matcher(limit).matches()
         require(limit.isEmpty() || patternMatches) { "invalid LIMIT clauses:$limit" }
         this.limit = limit
@@ -100,7 +106,7 @@
      *
      * @return a new query
      */
-    fun create(): SupportSQLiteQuery {
+    public fun create(): SupportSQLiteQuery {
         require(!groupBy.isNullOrEmpty() || having.isNullOrEmpty()) {
             "HAVING clauses are only permitted when using a groupBy clause"
         }
@@ -146,7 +152,7 @@
         append(' ')
     }
 
-    companion object {
+    public companion object {
         private val limitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?")
 
         /**
@@ -156,7 +162,7 @@
          * @return A builder to create a query.
          */
         @JvmStatic
-        fun builder(tableName: String): SupportSQLiteQueryBuilder {
+        public fun builder(tableName: String): SupportSQLiteQueryBuilder {
             return SupportSQLiteQueryBuilder(tableName)
         }
     }
diff --git a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt
index 829612c..a5c36ae 100644
--- a/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt
+++ b/sqlite/sqlite/src/androidMain/kotlin/androidx/sqlite/db/SupportSQLiteStatement.android.kt
@@ -17,14 +17,14 @@
 
 /** An interface to map the behavior of [android.database.sqlite.SQLiteStatement]. */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SupportSQLiteStatement : SupportSQLiteProgram {
+public interface SupportSQLiteStatement : SupportSQLiteProgram {
     /**
      * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
      * CREATE / DROP table, view, trigger, index etc.
      *
      * @throws [android.database.SQLException] If the SQL string is invalid for some reason
      */
-    fun execute()
+    public fun execute()
 
     /**
      * Execute this SQL statement, if the the number of rows affected by execution of this SQL
@@ -33,7 +33,7 @@
      * @return the number of rows affected by this SQL statement execution.
      * @throws [android.database.SQLException] If the SQL string is invalid for some reason
      */
-    fun executeUpdateDelete(): Int
+    public fun executeUpdateDelete(): Int
 
     /**
      * Execute this SQL statement and return the ID of the row inserted due to this call. The SQL
@@ -42,7 +42,7 @@
      * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
      * @throws [android.database.SQLException] If the SQL string is invalid for some reason
      */
-    fun executeInsert(): Long
+    public fun executeInsert(): Long
 
     /**
      * Execute a statement that returns a 1 by 1 table with a numeric value. For example, SELECT
@@ -51,7 +51,7 @@
      * @return The result of the query.
      * @throws [android.database.sqlite.SQLiteDoneException] if the query returns zero rows
      */
-    fun simpleQueryForLong(): Long
+    public fun simpleQueryForLong(): Long
 
     /**
      * Execute a statement that returns a 1 by 1 table with a text value. For example, SELECT
@@ -60,5 +60,5 @@
      * @return The result of the query.
      * @throws [android.database.sqlite.SQLiteDoneException] if the query returns zero rows
      */
-    fun simpleQueryForString(): String?
+    public fun simpleQueryForString(): String?
 }
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt
index b997417..f930e84 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLite.kt
@@ -22,14 +22,14 @@
 
 /** Executes a single SQL statement that returns no values. */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-fun SQLiteConnection.execSQL(sql: String) {
+public fun SQLiteConnection.execSQL(sql: String) {
     prepare(sql).use { it.step() }
 }
 
 /** Use the receiver statement within the [block] and closes it once it is done. */
 // TODO(b/315461431): Migrate to a Closeable interface in KMP
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-inline fun <R> SQLiteStatement.use(block: (SQLiteStatement) -> R): R {
+public inline fun <R> SQLiteStatement.use(block: (SQLiteStatement) -> R): R {
     try {
         return block.invoke(this)
     } finally {
@@ -39,7 +39,7 @@
 
 /** Throws a [SQLiteException] with its message formed by the given [errorCode] amd [errorMsg]. */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-fun throwSQLiteException(errorCode: Int, errorMsg: String?): Nothing {
+public fun throwSQLiteException(errorCode: Int, errorMsg: String?): Nothing {
     val message = buildString {
         append("Error code: $errorCode")
         if (errorMsg != null) {
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt
index 5922ca2..128ef24 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteConnection.kt
@@ -26,7 +26,7 @@
  */
 // TODO(b/315461431): No common Closeable interface in KMP
 @Suppress("NotCloseable", "AcronymName") // SQL is a known term and should remain capitalized
-interface SQLiteConnection {
+public interface SQLiteConnection {
     /**
      * Prepares a new SQL statement.
      *
@@ -35,7 +35,7 @@
      * @param sql the SQL statement to prepare
      * @return the prepared statement.
      */
-    fun prepare(sql: String): SQLiteStatement
+    public fun prepare(sql: String): SQLiteStatement
 
     /**
      * Closes the database connection.
@@ -43,5 +43,5 @@
      * Once a connection is closed it should no longer be used. Calling this function on an already
      * closed database connection is a no-op.
      */
-    fun close()
+    public fun close()
 }
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
index a0fa75c..5cb23bf 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
@@ -18,12 +18,12 @@
 
 /** An interface to open database connections. */
 @Suppress("AcronymName") // SQL is a known term and should remain capitalized
-interface SQLiteDriver {
+public interface SQLiteDriver {
     /**
      * Opens a new database connection.
      *
      * @param fileName Name of the database file.
      * @return the database connection.
      */
-    fun open(fileName: String): SQLiteConnection
+    public fun open(fileName: String): SQLiteConnection
 }
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt
index 1276395..c2d575e 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteException.kt
@@ -23,6 +23,6 @@
  *
  * See [Result and Error codes](https://www.sqlite.org/rescode.html)
  */
-expect class SQLiteException
+public expect class SQLiteException
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 constructor(message: String) : RuntimeException
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
index 039d211..65ba44c 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
@@ -26,14 +26,14 @@
  */
 // TODO(b/315461431): No common Closeable interface in KMP
 @Suppress("NotCloseable", "AcronymName") // SQL is a known term and should remain capitalized
-interface SQLiteStatement {
+public interface SQLiteStatement {
     /**
      * Binds a ByteArray value to this statement at an index.
      *
      * @param index the 1-based index of the parameter to bind
      * @param value the value to bind
      */
-    fun bindBlob(index: Int, value: ByteArray)
+    public fun bindBlob(index: Int, value: ByteArray)
 
     /**
      * Binds a Double value to this statement at an index.
@@ -41,7 +41,7 @@
      * @param index the 1-based index of the parameter to bind
      * @param value the value to bind
      */
-    fun bindDouble(index: Int, value: Double)
+    public fun bindDouble(index: Int, value: Double)
 
     /**
      * Binds a Float value to this statement at an index.
@@ -49,7 +49,7 @@
      * @param index the 1-based index of the parameter to bind
      * @param value the value to bind
      */
-    fun bindFloat(index: Int, value: Float) {
+    public fun bindFloat(index: Int, value: Float) {
         bindDouble(index, value.toDouble())
     }
 
@@ -59,7 +59,7 @@
      * @param index the 1-based index of the parameter to bind
      * @param value the value to bind
      */
-    fun bindLong(index: Int, value: Long)
+    public fun bindLong(index: Int, value: Long)
 
     /**
      * Binds a Int value to this statement at an index.
@@ -67,7 +67,7 @@
      * @param index the 1-based index of the parameter to bind
      * @param value the value to bind
      */
-    fun bindInt(index: Int, value: Int) {
+    public fun bindInt(index: Int, value: Int) {
         bindLong(index, value.toLong())
     }
 
@@ -77,7 +77,7 @@
      * @param index the 1-based index of the parameter to bind
      * @param value the value to bind
      */
-    fun bindBoolean(index: Int, value: Boolean) {
+    public fun bindBoolean(index: Int, value: Boolean) {
         bindLong(index, if (value) 1L else 0L)
     }
 
@@ -87,14 +87,14 @@
      * @param index the 1-based index of the parameter to bind
      * @param value the value to bind
      */
-    fun bindText(index: Int, value: String)
+    public fun bindText(index: Int, value: String)
 
     /**
      * Binds a NULL value to this statement at an index.
      *
      * @param index the 1-based index of the parameter to bind
      */
-    fun bindNull(index: Int)
+    public fun bindNull(index: Int)
 
     /**
      * Returns the value of the column at [index] as a ByteArray.
@@ -102,7 +102,7 @@
      * @param index the 0-based index of the column
      * @return the value of the column
      */
-    fun getBlob(index: Int): ByteArray
+    public fun getBlob(index: Int): ByteArray
 
     /**
      * Returns the value of the column at [index] as a Double.
@@ -110,7 +110,7 @@
      * @param index the 0-based index of the column
      * @return the value of the column
      */
-    fun getDouble(index: Int): Double
+    public fun getDouble(index: Int): Double
 
     /**
      * Returns the value of the column at [index] as a Float.
@@ -118,7 +118,7 @@
      * @param index the 0-based index of the column
      * @return the value of the column
      */
-    fun getFloat(index: Int): Float {
+    public fun getFloat(index: Int): Float {
         return getDouble(index).toFloat()
     }
 
@@ -128,7 +128,7 @@
      * @param index the 0-based index of the column
      * @return the value of the column
      */
-    fun getLong(index: Int): Long
+    public fun getLong(index: Int): Long
 
     /**
      * Returns the value of the column at [index] as a Int.
@@ -136,7 +136,7 @@
      * @param index the 0-based index of the column
      * @return the value of the column
      */
-    fun getInt(index: Int): Int {
+    public fun getInt(index: Int): Int {
         return getLong(index).toInt()
     }
 
@@ -146,7 +146,7 @@
      * @param index the 0-based index of the column
      * @return the value of the column
      */
-    fun getBoolean(index: Int): Boolean {
+    public fun getBoolean(index: Int): Boolean {
         return getLong(index) != 0L
     }
 
@@ -156,7 +156,7 @@
      * @param index the 0-based index of the column
      * @return the value of the column
      */
-    fun getText(index: Int): String
+    public fun getText(index: Int): String
 
     /**
      * Returns true if the value of the column at [index] is NULL.
@@ -164,14 +164,14 @@
      * @param index the 0-based index of the column
      * @return true if the column value is NULL, false otherwise
      */
-    fun isNull(index: Int): Boolean
+    public fun isNull(index: Int): Boolean
 
     /**
      * Returns the number of columns in the result of the statement.
      *
      * @return the number of columns
      */
-    fun getColumnCount(): Int
+    public fun getColumnCount(): Int
 
     /**
      * Returns the name of a column at [index] in the result of the statement.
@@ -179,14 +179,14 @@
      * @param index the 0-based index of the column
      * @return the name of the column
      */
-    fun getColumnName(index: Int): String
+    public fun getColumnName(index: Int): String
 
     /**
      * Returns the name of the columns in the result of the statement ordered by their index.
      *
      * @return the names of the columns
      */
-    fun getColumnNames(): List<String> {
+    public fun getColumnNames(): List<String> {
         return List(getColumnCount()) { i -> getColumnName(i) }
     }
 
@@ -199,16 +199,16 @@
      *
      * @return true if there are more rows to evaluate or false if the statement is done executing
      */
-    fun step(): Boolean
+    public fun step(): Boolean
 
     /**
      * Resets the prepared statement back to initial state so that it can be re-executed via [step].
      * Any parameter bound via the bind*() APIs will retain their value.
      */
-    fun reset()
+    public fun reset()
 
     /** Clears all parameter bindings. Unset bindings are treated as NULL. */
-    fun clearBindings()
+    public fun clearBindings()
 
     /**
      * Closes the statement.
@@ -216,5 +216,5 @@
      * Once a statement is closed it should no longer be used. Calling this function on an already
      * closed statement is a no-op.
      */
-    fun close()
+    public fun close()
 }
diff --git a/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt b/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt
index cf6e78c..a3f49cb 100644
--- a/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt
+++ b/sqlite/sqlite/src/jvmMain/kotlin/androidx/sqlite/SQLiteException.jvm.kt
@@ -18,6 +18,6 @@
 
 import androidx.annotation.RestrictTo
 
-actual class SQLiteException
+public actual class SQLiteException
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 actual constructor(message: String) : RuntimeException(message)
diff --git a/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt b/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt
index cf6e78c..a3f49cb 100644
--- a/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt
+++ b/sqlite/sqlite/src/nativeMain/kotlin/androidx/sqlite/SQLiteException.native.kt
@@ -18,6 +18,6 @@
 
 import androidx.annotation.RestrictTo
 
-actual class SQLiteException
+public actual class SQLiteException
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 actual constructor(message: String) : RuntimeException(message)
diff --git a/testutils/testutils-datastore/build.gradle b/testutils/testutils-datastore/build.gradle
index befacf5..7d8f19b 100644
--- a/testutils/testutils-datastore/build.gradle
+++ b/testutils/testutils-datastore/build.gradle
@@ -32,6 +32,8 @@
     mac()
     linux()
     ios()
+    watchos()
+    tvos()
 
     sourceSets {
         commonMain {
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeScreenshotTest.kt
new file mode 100644
index 0000000..df52e66
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeScreenshotTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.shape.AbsoluteCutCornerShape
+import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
+import androidx.compose.foundation.shape.CutCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class AnimatedCornerShapeScreenshotTest {
+
+    @get:Rule val rule = createComposeRule()
+
+    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule val testName = TestName()
+
+    @Test
+    fun cutCornerShapeLtr() = verifyScreenshot {
+        FilledIconButton(
+            onClick = {},
+            shape =
+                CutCornerShape(topStart = 2.dp, topEnd = 4.dp, bottomEnd = 6.dp, bottomStart = 8.dp)
+        ) {}
+    }
+
+    @Test
+    fun cutCornerShapeRtl() =
+        verifyScreenshot(layoutDirection = LayoutDirection.Rtl) {
+            FilledIconButton(
+                onClick = {},
+                shape =
+                    CutCornerShape(
+                        topStart = 2.dp,
+                        topEnd = 4.dp,
+                        bottomEnd = 6.dp,
+                        bottomStart = 8.dp
+                    )
+            ) {}
+        }
+
+    @Test
+    fun absoluteCutCornerShapeLtr() = verifyScreenshot {
+        FilledIconButton(
+            onClick = {},
+            shape =
+                AbsoluteCutCornerShape(
+                    topLeft = 2.dp,
+                    topRight = 4.dp,
+                    bottomRight = 6.dp,
+                    bottomLeft = 8.dp
+                )
+        ) {}
+    }
+
+    @Test
+    fun absoluteCutCornerShapeRtl() =
+        verifyScreenshot(layoutDirection = LayoutDirection.Rtl) {
+            FilledIconButton(
+                onClick = {},
+                shape =
+                    AbsoluteCutCornerShape(
+                        topLeft = 2.dp,
+                        topRight = 4.dp,
+                        bottomRight = 6.dp,
+                        bottomLeft = 8.dp
+                    )
+            ) {}
+        }
+
+    @Test
+    fun absoluteRoundedCornerShapeLtr() = verifyScreenshot {
+        FilledIconButton(
+            onClick = {},
+            shape =
+                AbsoluteRoundedCornerShape(
+                    topLeft = 2.dp,
+                    topRight = 4.dp,
+                    bottomRight = 6.dp,
+                    bottomLeft = 8.dp
+                )
+        ) {}
+    }
+
+    @Test
+    fun absoluteRoundedCornerShapeRtl() =
+        verifyScreenshot(layoutDirection = LayoutDirection.Rtl) {
+            FilledIconButton(
+                onClick = {},
+                shape =
+                    AbsoluteRoundedCornerShape(
+                        topLeft = 2.dp,
+                        topRight = 4.dp,
+                        bottomRight = 6.dp,
+                        bottomLeft = 8.dp
+                    )
+            ) {}
+        }
+
+    private fun verifyScreenshot(
+        layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+        content: @Composable () -> Unit
+    ) {
+        rule.setContentWithTheme {
+            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+                Box(modifier = Modifier.background(Color.Black).testTag(TEST_TAG)) { content() }
+            }
+        }
+
+        rule
+            .onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, testName.methodName)
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeTest.kt
new file mode 100644
index 0000000..5886659
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AnimatedCornerShapeTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.foundation.shape.AbsoluteCutCornerShape
+import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
+import androidx.compose.foundation.shape.CutCornerShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+class AnimatedCornerShapeTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun convertsRoundedCornerShape() {
+        val roundedCornerShape = RoundedCornerShape(10.dp)
+
+        val roundedPolygon =
+            roundedCornerShape.toRoundedPolygonOrNull(
+                size = Size(100f, 100f),
+                density = Density(density = 2f),
+                layoutDirection = LayoutDirection.Ltr
+            )
+
+        assertThat(roundedPolygon).isNotNull()
+        assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+        assertThat(roundedPolygon.cubics.size).isEqualTo(9)
+
+        val points =
+            roundedPolygon.cubics.flatMap {
+                listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+            }
+        assertThat(points)
+            .containsAtLeast(
+                Offset(1.0f, 0.8f),
+                Offset(0.8f, 1.0f),
+                Offset(0.2f, 1.0f),
+                Offset(0.0f, 0.8f),
+                Offset(0.0f, 0.2f),
+                Offset(0.2f, 0.0f),
+                Offset(0.8f, 0.0f),
+                Offset(1.0f, 0.2f),
+            )
+    }
+
+    @Test
+    fun convertsCutCornerShape() {
+        val cutCornerShape = CutCornerShape(10.dp)
+
+        val roundedPolygon =
+            cutCornerShape.toRoundedPolygonOrNull(
+                size = Size(100f, 100f),
+                density = Density(density = 2f),
+                layoutDirection = LayoutDirection.Ltr
+            )
+
+        assertThat(roundedPolygon).isNotNull()
+        assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+        assertThat(roundedPolygon.cubics.size).isEqualTo(8)
+
+        val points =
+            roundedPolygon.cubics.flatMap {
+                listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+            }
+        assertThat(points)
+            .containsAtLeast(
+                Offset(1.0f, 0.8f),
+                Offset(0.8f, 1.0f),
+                Offset(0.2f, 1.0f),
+                Offset(0.0f, 0.8f),
+                Offset(0.0f, 0.2f),
+                Offset(0.2f, 0.0f),
+                Offset(0.8f, 0.0f),
+                Offset(1.0f, 0.2f),
+            )
+    }
+
+    @Test
+    fun convertsAbsoluteRoundedCornerShape() {
+        val roundedCornerShape = AbsoluteRoundedCornerShape(5.dp)
+
+        val roundedPolygon =
+            roundedCornerShape.toRoundedPolygonOrNull(
+                size = Size(100f, 100f),
+                density = Density(density = 2f),
+                layoutDirection = LayoutDirection.Ltr
+            )
+
+        assertThat(roundedPolygon).isNotNull()
+        assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+        assertThat(roundedPolygon.cubics.size).isEqualTo(9)
+
+        val points =
+            roundedPolygon.cubics.flatMap {
+                listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+            }
+        assertThat(points)
+            .containsAtLeast(
+                Offset(1.0f, 0.9f),
+                Offset(0.9f, 1.0f),
+                Offset(0.1f, 1.0f),
+                Offset(0.0f, 0.9f),
+                Offset(0.0f, 0.1f),
+                Offset(0.1f, 0.0f),
+                Offset(0.9f, 0.0f),
+                Offset(1.0f, 0.1f),
+            )
+    }
+
+    @Test
+    fun convertsAbsoluteCutCornerShape() {
+        val cutCornerShape = AbsoluteCutCornerShape(5.dp)
+
+        val roundedPolygon =
+            cutCornerShape.toRoundedPolygonOrNull(
+                size = Size(100f, 100f),
+                density = Density(density = 2f),
+                layoutDirection = LayoutDirection.Ltr
+            )
+
+        assertThat(roundedPolygon).isNotNull()
+        assertThat(roundedPolygon!!.calculateBounds()).isEqualTo(floatArrayOf(0f, 0f, 1f, 1f))
+        assertThat(roundedPolygon.cubics.size).isEqualTo(8)
+
+        val points =
+            roundedPolygon.cubics.flatMap {
+                listOf(Offset(it.anchor0X, it.anchor0Y), Offset(it.anchor1X, it.anchor1Y))
+            }
+        assertThat(points)
+            .containsAtLeast(
+                Offset(1.0f, 0.9f),
+                Offset(0.9f, 1.0f),
+                Offset(0.1f, 1.0f),
+                Offset(0.0f, 0.9f),
+                Offset(0.0f, 0.1f),
+                Offset(0.1f, 0.0f),
+                Offset(0.9f, 0.0f),
+                Offset(1.0f, 0.1f),
+            )
+    }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt
index 70fa44b..afca2cd 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedCornerShape.kt
@@ -170,7 +170,7 @@
  * @param size The size of the final Composable such as a Button.
  * @param density The density of the composition.
  */
-private fun CornerBasedShape.toRoundedPolygonOrNull(
+internal fun CornerBasedShape.toRoundedPolygonOrNull(
     size: Size,
     density: Density,
     layoutDirection: LayoutDirection