Merge "Move transformToScreen to android-only extension" into androidx-main
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index aa6cbc1..afea44c 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -63,6 +63,7 @@
         exclude group: "androidx.appcompat", module: "appcompat"
         exclude group: "androidx.core", module: "core"
     })
+    androidTestImplementation(project(":internal-testutils-fonts"))
 
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.testCore)
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
index c329690..65b0e5b 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
@@ -18,11 +18,14 @@
 import static androidx.appcompat.testutils.TestUtilsActions.setEnabled;
 import static androidx.appcompat.testutils.TestUtilsActions.setTextAppearance;
 import static androidx.appcompat.testutils.TestUtilsMatchers.isBackground;
+import static androidx.core.view.ViewKt.drawToBitmap;
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.scrollTo;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 
+import static junit.framework.Assert.assertFalse;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
@@ -30,8 +33,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.Instrumentation;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
@@ -67,6 +72,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
 
 import org.junit.Ignore;
@@ -476,7 +482,15 @@
     public void testFontVariation_setInXml() {
         final AppCompatTextView textView = mActivity.findViewById(
                 R.id.textview_fontVariation_textView);
-        assertEquals("'wdth' 30", textView.getFontVariationSettings());
+        assertEquals("'wght' 200", textView.getFontVariationSettings());
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        runCommandAndAssertBitmapChangedOrSame(textView, /* expectSame*/ true, () ->
+                instrumentation.runOnMainSync(() -> {
+                    textView.setFontVariationSettings("'wght' 100"); /* reset */
+                    textView.setFontVariationSettings("'wght' 200");
+                })
+        );
     }
 
     @SdkSuppress(minSdkVersion = 26)
@@ -485,6 +499,14 @@
         final AppCompatTextView textView = mActivity.findViewById(
                 R.id.textview_fontVariation_textAppearance);
         assertEquals("'wght' 300", textView.getFontVariationSettings());
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        runCommandAndAssertBitmapChangedOrSame(textView, /* expectSame*/ true, () ->
+                instrumentation.runOnMainSync(() -> {
+                    textView.setFontVariationSettings("'wght' 100"); /* reset */
+                    textView.setFontVariationSettings("'wght' 300");
+                })
+        );
     }
 
     @SdkSuppress(minSdkVersion = 26)
@@ -494,7 +516,7 @@
                 R.id.textview_fontVariation_textView_and_textAppearance);
         //FontVariation is set in both AppCompatTextView and textAppearance,
         //we should use the one in AppCompatTextView.
-        assertEquals("'wdth' 30", textView.getFontVariationSettings());
+        assertEquals("'wght' 700", textView.getFontVariationSettings());
     }
 
     @SdkSuppress(minSdkVersion = 26)
@@ -502,12 +524,60 @@
     @UiThreadTest
     public void testFontVariation_setTextAppearance() throws Throwable {
         final AppCompatTextView textView = mActivity.findViewById(
-                R.id.textview_simple
+                R.id.textview_fontVariation_textView
         );
         textView.setTextAppearance(textView.getContext(), R.style.TextView_FontVariation);
         assertEquals("'wght' 300", textView.getFontVariationSettings());
     }
 
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    public void testFontVariation_setTextAppearance_changesPixels() throws Throwable {
+        final AppCompatTextView textView = mActivity.findViewById(
+                R.id.textview_fontVariation_textView
+        );
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.runOnMainSync(() ->
+                textView.setFontVariationSettings("'wght' 900")
+        );
+        runCommandAndAssertBitmapChangedOrSame(textView, /* expectSame*/ false, () ->
+                instrumentation.runOnMainSync(() ->
+                        textView.setTextAppearance(textView.getContext(),
+                                R.style.TextView_FontVariation)
+                )
+        );
+    }
+
+    /**
+     * Run a command and confirm that the result of the command was a visible change to at least
+     * one pixel or exact similarity
+     *
+     * @param subject view to change
+     * @param expectSame when true, expect pixels to be identical, otherwise expect differences
+     * @param command comamnd to be called. it is not dispatched
+     */
+    public void runCommandAndAssertBitmapChangedOrSame(TextView subject, Boolean expectSame,
+            Runnable command) {
+        Bitmap before = drawToBitmap(subject, Bitmap.Config.ARGB_8888);
+        try {
+            command.run();
+            Bitmap after = drawToBitmap(subject, Bitmap.Config.ARGB_8888);
+            try {
+                if (expectSame) {
+                    assertTrue(before.sameAs(after));
+                } else {
+                    assertFalse(before.sameAs(after));
+                }
+            } finally {
+                after.recycle();
+            }
+        } finally {
+            before.recycle();
+        }
+    }
+
+
     @Test
     @UiThreadTest
     public void testBaselineAttributes() {
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml
index 151113a..6b88361 100644
--- a/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_textview_activity.xml
@@ -380,21 +380,25 @@
 
         <androidx.appcompat.widget.AppCompatTextView
             android:id="@+id/textview_fontVariation_textView"
+            android:text="A"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            app:fontVariationSettings="'wdth' 30"/>
+            android:fontFamily="@font/variable_font"
+            app:fontVariationSettings="'wght' 200"/>
 
         <androidx.appcompat.widget.AppCompatTextView
             android:id="@+id/textview_fontVariation_textAppearance"
+            android:text="A"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:fontFamily="@font/variable_font"
             android:textAppearance="@style/TextView_FontVariation"/>
 
         <androidx.appcompat.widget.AppCompatTextView
             android:id="@+id/textview_fontVariation_textView_and_textAppearance"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            app:fontVariationSettings="'wdth' 30"
+            app:fontVariationSettings="'wght' 700"
             android:textAppearance="@style/TextView_FontVariation"/>
 
         <androidx.appcompat.widget.AppCompatTextView
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 158f467..ef3029a8 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
@@ -76,6 +76,8 @@
     private int mStyle = Typeface.NORMAL;
     private int mFontWeight = TEXT_FONT_WEIGHT_UNSPECIFIED;
     private Typeface mFontTypeface;
+    @Nullable
+    private String mFontVariationSettings = null;
     private boolean mAsyncFontPending;
 
     AppCompatTextHelper(@NonNull TextView view) {
@@ -134,7 +136,6 @@
         ColorStateList textColor = null;
         ColorStateList textColorHint = null;
         ColorStateList textColorLink = null;
-        String fontVariation = null;
         String localeListString = null;
 
         // First check TextAppearance's textAllCaps value
@@ -164,10 +165,6 @@
             if (a.hasValue(R.styleable.TextAppearance_textLocale)) {
                 localeListString = a.getString(R.styleable.TextAppearance_textLocale);
             }
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
-                    && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
-                fontVariation = a.getString(R.styleable.TextAppearance_fontVariationSettings);
-            }
             a.recycle();
         }
 
@@ -197,10 +194,6 @@
             localeListString = a.getString(R.styleable.TextAppearance_textLocale);
         }
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
-                && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
-            fontVariation = a.getString(R.styleable.TextAppearance_fontVariationSettings);
-        }
         // In P, when the text size attribute is 0, this would not be set. Fix this here.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
                 && a.hasValue(R.styleable.TextAppearance_android_textSize)) {
@@ -224,16 +217,9 @@
         if (!hasPwdTm && allCapsSet) {
             setAllCaps(allCaps);
         }
-        if (mFontTypeface != null) {
-            if (mFontWeight == TEXT_FONT_WEIGHT_UNSPECIFIED) {
-                mView.setTypeface(mFontTypeface, mStyle);
-            } else {
-                mView.setTypeface(mFontTypeface);
-            }
-        }
-        if (fontVariation != null) {
-            Api26Impl.setFontVariationSettings(mView, fontVariation);
-        }
+
+        applyFontAndVariationSettings(/* forceNullSet */ false);
+
         if (localeListString != null) {
             if (Build.VERSION.SDK_INT >= 24) {
                 Api24Impl.setTextLocales(mView, Api24Impl.forLanguageTags(localeListString));
@@ -355,7 +341,42 @@
         }
     }
 
-    private void updateTypefaceAndStyle(Context context, TintTypedArray a) {
+    /**
+     * Apply mFontTypeface previously loaded from XML, and apply mFontVariationSettings to it.
+     *
+     * This should only be called from xml initialization, or setTextAppearance.
+     *
+     * @param forceNullSet Explicit null values should clobber existing Typefaces
+     */
+    private void applyFontAndVariationSettings(boolean forceNullSet) {
+        if (mFontTypeface != null) {
+            if (mFontWeight == TEXT_FONT_WEIGHT_UNSPECIFIED) {
+                mView.setTypeface(mFontTypeface, mStyle);
+            } else {
+                mView.setTypeface(mFontTypeface);
+            }
+        } else if (forceNullSet) {
+            mView.setTypeface(null);
+        }
+
+        if (mFontVariationSettings != null && Build.VERSION.SDK_INT >= 26) {
+            Api26Impl.setFontVariationSettings(mView, mFontVariationSettings);
+        }
+
+        mFontVariationSettings = null;
+        mFontTypeface = null;
+    }
+
+    /**
+     * Load mFontTypeface from an xml, may be called multiple times with e.g. style, textAppearance,
+     * and attribute items.
+     *
+     * Note: Setting multiple fonts at different levels currently triggers multiple font-loads.
+     *
+     * @param context to check if restrictions avoid loading downloadable fonts
+     * @param a attributes to read from, e.g. a textappearance
+     */
+    private boolean updateTypefaceAndStyle(Context context, TintTypedArray a) {
         mStyle = a.getInt(R.styleable.TextAppearance_android_textStyle, mStyle);
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -366,6 +387,11 @@
             }
         }
 
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
+            mFontVariationSettings = a.getString(R.styleable.TextAppearance_fontVariationSettings);
+        }
+
         if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
                 || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
             mFontTypeface = null;
@@ -375,27 +401,12 @@
             final int fontWeight = mFontWeight;
             final int style = mStyle;
             if (!context.isRestricted()) {
-                final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
-                ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
-                    @Override
-                    public void onFontRetrieved(@NonNull Typeface typeface) {
-                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                            if (fontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED) {
-                                typeface = Api28Impl.create(typeface, fontWeight,
-                                        (style & Typeface.ITALIC) != 0);
-                            }
-                        }
-                        onAsyncTypefaceReceived(textViewWeak, typeface);
-                    }
-
-                    @Override
-                    public void onFontRetrievalFailed(int reason) {
-                        // Do nothing.
-                    }
-                };
+                ResourcesCompat.FontCallback replyCallback = makeFontCallback(fontWeight,
+                        style);
                 try {
                     // Note the callback will be triggered on the UI thread.
                     final Typeface typeface = a.getFont(fontFamilyId, mStyle, replyCallback);
+                    // assume Typeface does have fontVariationSettings in this path
                     if (typeface != null) {
                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
                                 && mFontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED) {
@@ -426,7 +437,7 @@
                     }
                 }
             }
-            return;
+            return true;
         }
 
         if (a.hasValue(R.styleable.TextAppearance_android_typeface)) {
@@ -446,13 +457,39 @@
                     mFontTypeface = Typeface.MONOSPACE;
                     break;
             }
+            return true;
         }
+        return false;
+    }
+
+    @NonNull
+    private ResourcesCompat.FontCallback makeFontCallback(int fontWeight, int style) {
+        final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
+        return new ResourcesCompat.FontCallback() {
+            @Override
+            public void onFontRetrieved(@NonNull Typeface typeface) {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                    if (fontWeight != TEXT_FONT_WEIGHT_UNSPECIFIED) {
+                        typeface = Api28Impl.create(typeface, fontWeight,
+                                (style & Typeface.ITALIC) != 0);
+                    }
+                }
+                onAsyncTypefaceReceived(textViewWeak, typeface);
+            }
+
+            @Override
+            public void onFontRetrievalFailed(int reason) {
+                // Do nothing.
+            }
+        };
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, final Typeface typeface) {
         if (mAsyncFontPending) {
+            // we assume that typeface has the correct variation settings from androidx.core
             mFontTypeface = typeface;
+            // TODO(b/266112457) unset mFontVariationSettings here so we don't double apply it
             final TextView textView = textViewWeak.get();
             if (textView != null) {
                 if (textView.isAttachedToWindow()) {
@@ -512,20 +549,9 @@
             }
         }
 
-        updateTypefaceAndStyle(context, a);
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
-                && a.hasValue(R.styleable.TextAppearance_fontVariationSettings)) {
-            final String fontVariation = a.getString(
-                    R.styleable.TextAppearance_fontVariationSettings);
-            if (fontVariation != null) {
-                Api26Impl.setFontVariationSettings(mView, fontVariation);
-            }
-        }
+        boolean containsTypeface = updateTypefaceAndStyle(context, a);
         a.recycle();
-        if (mFontTypeface != null) {
-            mView.setTypeface(mFontTypeface, mStyle);
-        }
+        applyFontAndVariationSettings(containsTypeface);
     }
 
     void setAllCaps(boolean allCaps) {
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..7d78ebe 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextView.java
@@ -211,7 +211,13 @@
 
     @Override
     public void setTextAppearance(Context context, int resId) {
+        Typeface previousTypeface = getTypeface();
         super.setTextAppearance(context, resId);
+        // don't allow setTextAppearance to clobber Typeface, we will correctly read it in
+        // onSetTextAppearance if non-null
+        if (getTypeface() == null) {
+            super.setTypeface(previousTypeface);
+        }
         if (mTextHelper != null) {
             mTextHelper.onSetTextAppearance(context, resId);
         }
diff --git a/appcompat/integration-tests/receive-content-testapp/build.gradle b/appcompat/integration-tests/receive-content-testapp/build.gradle
index 2e4ddbe..e414862 100644
--- a/appcompat/integration-tests/receive-content-testapp/build.gradle
+++ b/appcompat/integration-tests/receive-content-testapp/build.gradle
@@ -32,7 +32,7 @@
     implementation(libs.material)
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
 
     androidTestImplementation("androidx.lifecycle:lifecycle-common:2.6.1")
     androidTestImplementation(libs.testCore)
diff --git a/benchmark/benchmark-common/build.gradle b/benchmark/benchmark-common/build.gradle
index a9743ac..024ae02 100644
--- a/benchmark/benchmark-common/build.gradle
+++ b/benchmark/benchmark-common/build.gradle
@@ -75,7 +75,7 @@
 dependencies {
     implementation(libs.kotlinStdlib)
     api("androidx.annotation:annotation:1.7.0")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     implementation("androidx.tracing:tracing-ktx:1.0.0")
     implementation("androidx.tracing:tracing-perfetto-handshake:1.0.0")
     implementation("androidx.test:monitor:1.6.1")
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
index c5837c8..bc0ea41 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import kotlin.test.assertEquals
 import kotlin.test.assertNotEquals
@@ -140,6 +141,7 @@
             expectedIterations = null, // iterations are dynamic
         )
 
+    @SdkSuppress(minSdkVersion = 22) // See b/300658578
     @Test
     fun profilerMethodTracing() =
         validateConfig(
@@ -160,6 +162,7 @@
             expectedProfilerIterations = 1,
         )
 
+    @SdkSuppress(minSdkVersion = 22) // See b/300658578
     @Test
     fun profilerMethodTracing_perfCompareMode() =
         validateConfig(
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt
index 2366c48..0aecc6e 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ProfilerTest.kt
@@ -55,14 +55,20 @@
             profiler == MethodTracing && Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP,
         )
 
-        val outputRelPath = profiler.start("test")!!.outputRelativePath
+        val result = profiler.start("test")!!
+        val outputRelPath = result.outputRelativePath
         profiler.stop()
         val file = File(Outputs.outputDirectory, outputRelPath)
-
         assertTrue(
             actual = regex.matches(outputRelPath),
             message = "expected profiler output path $outputRelPath to match $regex"
         )
+
+        if (result.convertBeforeSync != null) {
+            // profiler doesn't create file until conversion occurs
+            assertFalse(file.exists(), "Profiler should not yet create: ${file.absolutePath}")
+            result.convertBeforeSync?.invoke()
+        }
         assertTrue(file.exists(), "Profiler should create: ${file.absolutePath}")
 
         // we don't delete the file to enable inspecting the file
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
index 64752cd..d51f969 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -585,6 +585,7 @@
             return // nothing to report, BenchmarkState wasn't used
         }
 
+        profilerResult?.convertBeforeSync?.invoke()
         if (perfettoTracePath != null) {
             profilerResult?.embedInPerfettoTrace(perfettoTracePath)
         }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
index 100cdc0..35b8a6a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
@@ -53,6 +53,7 @@
         val type: ProfilerOutput.Type,
         val outputRelativePath: String,
         val source: Profiler?,
+        val convertBeforeSync: (() -> Unit)? = null
     ) {
 
         fun embedInPerfettoTrace(perfettoTracePath: String) {
@@ -86,13 +87,15 @@
                 label: String,
                 type: ProfilerOutput.Type,
                 outputRelativePath: String,
-                source: Profiler
+                source: Profiler,
+                convertBeforeSync: (() -> Unit)? = null
             ) =
                 ResultFile(
                     label = label,
                     outputRelativePath = outputRelativePath,
                     type = type,
-                    source = source
+                    source = source,
+                    convertBeforeSync = convertBeforeSync
                 )
         }
     }
@@ -329,19 +332,23 @@
             label = "Stack Sampling Trace",
             outputRelativePath = outputRelativePath!!,
             type = ProfilerOutput.Type.StackSamplingTrace,
-            source = this
+            source = this,
+            convertBeforeSync = this::convertBeforeSync
         )
     }
 
     @RequiresApi(29)
     override fun stop() {
         session!!.stopRecording()
+        securityPerfHarden.resetIfOverridden()
+    }
+
+    @RequiresApi(29)
+    fun convertBeforeSync() {
         Outputs.writeFile(fileName = outputRelativePath!!) {
             session!!.convertSimpleperfOutputToProto("simpleperf.data", it.absolutePath)
+            session = null
         }
-
-        session = null
-        securityPerfHarden.resetIfOverridden()
     }
 
     override fun config(packageNames: List<String>) =
diff --git a/browser/browser/build.gradle b/browser/browser/build.gradle
index cdae3ea..aa9834f 100644
--- a/browser/browser/build.gradle
+++ b/browser/browser/build.gradle
@@ -29,7 +29,7 @@
 dependencies {
     api("androidx.core:core:1.1.0")
     api("androidx.annotation:annotation:1.2.0")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     api(libs.guavaListenableFuture)
 
     implementation("androidx.collection:collection:1.1.0")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index e453f50..464914e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -293,7 +293,7 @@
         task: AbstractTestTask,
         anchorTask: Task,
     ) {
-        anchorTask.dependsOn(task)
+        if (task.name !in listOf("linuxx64StubsTest", "jvmStubsTest")) anchorTask.dependsOn(task)
         val ignoreFailuresProperty =
             project.providers.gradleProperty(TEST_FAILURES_DO_NOT_FAIL_TEST_TASK)
         val ignoreFailures = ignoreFailuresProperty.isPresent
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index f96abcd..eec2dc0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -618,7 +618,13 @@
     fun linuxX64Stubs(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
         supportedPlatforms.add(PlatformIdentifier.LINUX_X_64_STUBS)
         return if (project.enableLinux()) {
-            kotlinExtension.linuxX64("linuxx64Stubs").also { block?.execute(it) }
+            kotlinExtension.linuxX64("linuxx64Stubs") {
+                block?.execute(this)
+                project.tasks.named("linuxx64StubsTest").configure {
+                    // don't try running common tests for stubs target
+                    it.enabled = false
+                }
+            }
         } else {
             null
         }
@@ -756,7 +762,7 @@
      * does not appear in this list will use its [KotlinPlatformType] name.
      */
     private val allowedTargetNameSuffixes =
-        setOf("android", "desktop", "jvm", "jvmStubs", "linuxx64Stubs")
+        setOf("android", "desktop", "jvm", "commonStubs", "jvmStubs", "linuxx64Stubs")
 
     /** The preferred source file suffix for the target's platform type. */
     private val KotlinTarget.preferredSourceFileSuffix: String
@@ -772,4 +778,4 @@
  * Set of targets are there to serve as stubs, but are not expected to be consumed by library
  * consumers.
  */
-internal val setOfStubTargets = setOf("jvmStubs", "linuxx64Stubs")
+internal val setOfStubTargets = setOf("commonStubs", "jvmStubs", "linuxx64Stubs")
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 44442f2..b71053d 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -72,7 +72,7 @@
     androidTestImplementation(libs.kotlinCoroutinesAndroid)
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.truth)
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
     androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing")) {
         // Ensure camera-testing does not pull in androidx.test dependencies
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
index ef0c3d9..c23f556 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
@@ -40,7 +40,7 @@
  * take time to configure and become ready.
  */
 class CameraControllerSimulator(
-    private val cameraContext: CameraContext,
+    cameraContext: CameraContext,
     private val graphConfig: CameraGraph.Config,
     private val graphListener: GraphListener,
     private val streamGraph: StreamGraph
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
index df26736..7e4fea2 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
@@ -33,7 +33,6 @@
 import androidx.camera.camera2.pipe.RequestFailure
 import androidx.camera.camera2.pipe.StreamId
 import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.withTimeout
@@ -70,7 +69,6 @@
          * test completes and allows the test to provide more fine grained control over the
          * interactions.
          */
-        @OptIn(ExperimentalCoroutinesApi::class)
         fun create(
             scope: TestScope,
             context: Context,
@@ -150,6 +148,10 @@
         cameraController.simulateCameraError(graphStateError)
     }
 
+    /**
+     * Configure all streams in the CameraGraph with fake surfaces that match the size of the first
+     * output stream.
+     */
     fun simulateFakeSurfaceConfiguration() {
         check(!closed.value) {
             "Cannot call simulateFakeSurfaceConfiguration on $this after close."
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt
similarity index 100%
rename from camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
rename to camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraMetadata.kt
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt
index 0f7d4b0..bbd23b0 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeImageReader.kt
@@ -41,20 +41,32 @@
     val isClosed: Boolean
         get() = closed.value
 
-    fun simulateImage(timestamp: Long) {
+    /**
+     * Simulate an image at a specific [timestamp]. The timebase for an imageReader is undefined.
+     */
+    fun simulateImage(timestamp: Long): FakeImage {
         val outputId = outputs.keys.single()
-        simulateImage(outputId, timestamp)
+        return simulateImage(outputId, timestamp)
     }
 
-    fun simulateImage(outputId: OutputId, timestamp: Long) {
+    /**
+     * Simulate an image using a specific [outputId] and [timestamp]. The timebase for an
+     * imageReader is undefined.
+     */
+    fun simulateImage(outputId: OutputId, timestamp: Long): FakeImage {
         val size =
             checkNotNull(outputs[outputId]) {
                 "Unexpected $outputId! Available outputs are $outputs"
             }
         val image = FakeImage(size.width, size.height, format.value, timestamp)
         simulateImage(outputId, image)
+        return image
     }
 
+    /**
+     * Simulate an image using a specific [ImageWrapper] for the given outputId. The size must
+     * match.
+     */
     fun simulateImage(outputId: OutputId, image: ImageWrapper) {
         val size =
             checkNotNull(outputs[outputId]) {
@@ -111,3 +123,17 @@
         }
     }
 }
+
+class FakeOnImageListener : ImageReaderWrapper.OnImageListener {
+    val onImageEvents = mutableListOf<OnImageEvent>()
+
+    override fun onImage(streamId: StreamId, outputId: OutputId, image: ImageWrapper) {
+        onImageEvents.add(OnImageEvent(streamId, outputId, image))
+    }
+
+    data class OnImageEvent(
+        val streamId: StreamId,
+        val outputId: OutputId,
+        val image: ImageWrapper
+    )
+}
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt
index f447c70..fcf69f6 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeThreads.kt
@@ -21,20 +21,18 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 
 object FakeThreads {
     fun fromDispatcher(dispatcher: CoroutineDispatcher): Threads {
-        val scope = CoroutineScope(dispatcher.plus(CoroutineName("CXCP-TestScope")))
+        val scope = CoroutineScope(dispatcher + CoroutineName("CXCP-TestScope"))
         return create(scope, dispatcher)
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     fun fromTestScope(scope: TestScope): Threads {
-        val dispatcher = StandardTestDispatcher(scope.testScheduler)
+        val dispatcher = StandardTestDispatcher(scope.testScheduler, "CXCP-TestScope")
         return create(scope, dispatcher)
     }
 
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
index af4b275..a30a1e04 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
@@ -41,6 +41,7 @@
 import kotlinx.coroutines.flow.take
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.withContext
@@ -54,6 +55,7 @@
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class CameraGraphSimulatorTest {
+    private val testScope = TestScope()
     private val metadata =
         FakeCameraMetadata(
             mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT)
@@ -65,10 +67,11 @@
         CameraGraph.Config(camera = metadata.camera, streams = listOf(streamConfig))
 
     private val context = ApplicationProvider.getApplicationContext() as Context
+    private val simulator = CameraGraphSimulator.create(testScope, context, metadata, graphConfig)
 
     @Test
-    fun simulatorCanSimulateRepeatingFrames() = runTest {
-        CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+    fun simulatorCanSimulateRepeatingFrames() =
+        testScope.runTest {
             val stream = simulator.cameraGraph.streams[streamConfig]!!
             val listener = FakeRequestListener()
             val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -153,11 +156,10 @@
 
             simulateCallbacks.join()
         }
-    }
 
     @Test
-    fun simulatorAbortsRequests() = runTest {
-        CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+    fun simulatorAbortsRequests() =
+        testScope.runTest {
             val stream = simulator.cameraGraph.streams[streamConfig]!!
             val listener = FakeRequestListener()
             val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -168,11 +170,10 @@
             val abortedEvent = listener.onAbortedFlow.first()
             assertThat(abortedEvent.request).isSameInstanceAs(request)
         }
-    }
 
     @Test
-    fun simulatorCanIssueBufferLoss() = runTest {
-        CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+    fun simulatorCanIssueBufferLoss() =
+        testScope.runTest {
             val stream = simulator.cameraGraph.streams[streamConfig]!!
             val listener = FakeRequestListener()
             val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -193,11 +194,10 @@
             assertThat(lossEvent.requestMetadata.request).isSameInstanceAs(request)
             assertThat(lossEvent.streamId).isEqualTo(stream.id)
         }
-    }
 
     @Test
-    fun simulatorCanIssueMultipleFrames() = runTest {
-        CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+    fun simulatorCanIssueMultipleFrames() =
+        testScope.runTest {
             val stream = simulator.cameraGraph.streams[streamConfig]!!
             val listener = FakeRequestListener()
             val request = Request(streams = listOf(stream.id), listeners = listOf(listener))
@@ -279,11 +279,10 @@
 
             simulateCallbacks.join()
         }
-    }
 
     @Test
-    fun simulatorCanSimulateGraphState() = runTest {
-        CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+    fun simulatorCanSimulateGraphState() =
+        testScope.runTest {
             assertThat(simulator.cameraGraph.graphState.value).isEqualTo(GraphStateStopped)
 
             simulator.cameraGraph.start()
@@ -298,11 +297,10 @@
             simulator.simulateCameraStopped()
             assertThat(simulator.cameraGraph.graphState.value).isEqualTo(GraphStateStopped)
         }
-    }
 
     @Test
-    fun simulatorCanSimulateGraphError() = runTest {
-        CameraGraphSimulator.create(this, context, metadata, graphConfig).use { simulator ->
+    fun simulatorCanSimulateGraphError() =
+        testScope.runTest {
             val error = GraphStateError(CameraError.ERROR_CAMERA_DEVICE, willAttemptRetry = true)
 
             simulator.simulateCameraError(error)
@@ -325,5 +323,4 @@
             simulator.simulateCameraError(error)
             assertThat(simulator.cameraGraph.graphState.value).isEqualTo(GraphStateStopped)
         }
-    }
 }
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt
index 1ed5597..d91dd8e 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDevicesTest.kt
@@ -21,16 +21,14 @@
 import androidx.camera.camera2.pipe.CameraBackendId
 import androidx.camera.camera2.pipe.testing.FakeCameraBackend.Companion.FAKE_CAMERA_BACKEND_ID
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class FakeCameraDevicesTest {
+class FakeCameraDevicesTest {
     private val EXTERNAL_BACKEND_ID =
         CameraBackendId("androidx.camera.camera2.pipe.testing.FakeCameraDevicesTest")
     private val metadata1 =
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeImageReaderTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeImageReaderTest.kt
new file mode 100644
index 0000000..af283e0d
--- /dev/null
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeImageReaderTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.camera2.pipe.testing
+
+import android.os.Build
+import android.util.Size
+import androidx.camera.camera2.pipe.OutputId
+import androidx.camera.camera2.pipe.StreamFormat
+import androidx.camera.camera2.pipe.StreamId
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class FakeImageReaderTest {
+    private val imageReader =
+        FakeImageReader.create(StreamFormat.PRIVATE, StreamId(32), OutputId(42), Size(640, 480), 10)
+
+    @After
+    fun cleanup() {
+        imageReader.close()
+    }
+
+    @Test
+    fun imageReaderCanBeClosed() {
+        assertThat(imageReader.isClosed).isFalse()
+
+        imageReader.close()
+
+        assertThat(imageReader.isClosed).isTrue()
+    }
+
+    @Test
+    fun imageReaderCanSimulateImages() {
+        val fakeImage = imageReader.simulateImage(100)
+
+        assertThat(fakeImage.width).isEqualTo(640)
+        assertThat(fakeImage.height).isEqualTo(480)
+        assertThat(fakeImage.format).isEqualTo(StreamFormat.PRIVATE.value)
+        assertThat(fakeImage.timestamp).isEqualTo(100)
+        assertThat(fakeImage.isClosed).isFalse()
+    }
+
+    @Test
+    fun closingAnImageReaderDoesNotCloseImages() {
+        val fakeImage = imageReader.simulateImage(100)
+        imageReader.close()
+
+        assertThat(fakeImage.isClosed).isFalse()
+    }
+
+    @Test
+    fun imageReaderSimulatesANewImageEachTime() {
+        val fakeImage1 = imageReader.simulateImage(100)
+        val fakeImage2 = imageReader.simulateImage(100)
+
+        assertThat(fakeImage1).isNotSameInstanceAs(fakeImage2)
+        fakeImage2.close()
+
+        assertThat(fakeImage1.isClosed).isFalse()
+        assertThat(fakeImage2.isClosed).isTrue()
+    }
+
+    @Test
+    fun simulatingImagesArePassedToListener() {
+        val fakeListener = FakeOnImageListener()
+
+        imageReader.simulateImage(100)
+        imageReader.setOnImageListener(fakeListener)
+        val image2 = imageReader.simulateImage(200)
+        val image3 = imageReader.simulateImage(300)
+
+        assertThat(fakeListener.onImageEvents.size).isEqualTo(2)
+
+        assertThat(fakeListener.onImageEvents[0].image).isNotNull()
+        assertThat(fakeListener.onImageEvents[0].image).isSameInstanceAs(image2)
+        assertThat(fakeListener.onImageEvents[0].streamId).isEqualTo(StreamId(32))
+        assertThat(fakeListener.onImageEvents[0].outputId).isEqualTo(OutputId(42))
+
+        assertThat(fakeListener.onImageEvents[1].image).isNotNull()
+        assertThat(fakeListener.onImageEvents[1].image).isSameInstanceAs(image3)
+        assertThat(fakeListener.onImageEvents[1].streamId).isEqualTo(StreamId(32))
+        assertThat(fakeListener.onImageEvents[1].outputId).isEqualTo(OutputId(42))
+    }
+}
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt
index 973f28a..e276d6c 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadataTest.kt
@@ -25,7 +25,6 @@
 import androidx.camera.camera2.pipe.FrameNumber
 import androidx.camera.camera2.pipe.MetadataTransform
 import androidx.camera.camera2.pipe.Request
-import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -33,10 +32,9 @@
 import org.robolectric.annotation.Config
 
 @RunWith(JUnit4::class)
-@SdkSuppress(minSdkVersion = 21)
-public class MetadataTest {
+class MetadataTest {
     @Test
-    public fun testMetadataCanRetrieveValues() {
+    fun testMetadataCanRetrieveValues() {
         val metadata = FakeMetadata(mapOf(FakeMetadata.TEST_KEY to 42))
 
         assertThat(metadata[FakeMetadata.TEST_KEY]).isNotNull()
@@ -49,7 +47,7 @@
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class CameraMetadataTest {
+class CameraMetadataTest {
     @Test
     fun cameraMetadataIsNotEqual() {
         val metadata1 =
@@ -68,7 +66,7 @@
     }
 
     @Test
-    public fun canRetrieveCameraCharacteristicsOrCameraMetadataViaInterface() {
+    fun canRetrieveCameraCharacteristicsOrCameraMetadataViaInterface() {
         val metadata =
             FakeCameraMetadata(
                 mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT),
@@ -85,10 +83,10 @@
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class RequestMetadataTest {
+class RequestMetadataTest {
 
     @Test
-    public fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
+    fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
         val requestMetadata =
             FakeRequestMetadata(
                 requestParameters = mapOf(CaptureRequest.JPEG_QUALITY to 95),
@@ -114,9 +112,9 @@
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class FrameMetadataTest {
+class FrameMetadataTest {
     @Test
-    public fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
+    fun canRetrieveCaptureRequestOrCameraMetadataViaInterface() {
         val metadata =
             FakeFrameMetadata(
                 resultMetadata = mapOf(CaptureResult.JPEG_QUALITY to 95),
@@ -133,7 +131,7 @@
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class MetadataTransformTest {
+class MetadataTransformTest {
     private val metadata =
         FakeCameraMetadata(
             mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_FRONT),
@@ -154,7 +152,7 @@
     private val fakeFrameInfo = FakeFrameInfo(requestMetadata = requestMetadata)
 
     @Test
-    public fun defaultMetadataTransformIsNoOp() {
+    fun defaultMetadataTransformIsNoOp() {
         val transform = MetadataTransform()
         val overrides =
             transform.transformFn.computeOverridesFor(fakeFrameInfo, CameraId("Fake"), listOf())
@@ -163,7 +161,7 @@
     }
 
     @Test
-    public fun canCreateAndInvokeMetadataTransform() {
+    fun canCreateAndInvokeMetadataTransform() {
         val transform =
             MetadataTransform(
                 transformFn =
@@ -189,7 +187,7 @@
     }
 
     @Test
-    public fun canUseCameraMetadataForTransforms() {
+    fun canUseCameraMetadataForTransforms() {
         val transform =
             MetadataTransform(
                 transformFn =
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeThreadsTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeThreadsTest.kt
new file mode 100644
index 0000000..db747e0
--- /dev/null
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/FakeThreadsTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.camera2.pipe.testing
+
+import android.os.Build
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class FakeThreadsTest {
+    private val testScope = TestScope()
+    private val fakeThreads = FakeThreads.fromTestScope(testScope)
+
+    @Test
+    fun fakeThreadsUseDelaySkipping() =
+        testScope.runTest {
+            launch(fakeThreads.backgroundDispatcher) { delay(1000000) }.join()
+            launch(fakeThreads.blockingDispatcher) { delay(1000000) }.join()
+            launch(fakeThreads.lightweightDispatcher) { delay(1000000) }.join()
+            fakeThreads.globalScope.launch { delay(1000000) }.join()
+
+            var backgroundTaskExecuted = false
+            var blockingTaskExecuted = false
+            var lightweightTaskExecuted = false
+            fakeThreads.backgroundExecutor.execute { backgroundTaskExecuted = true }
+            fakeThreads.blockingExecutor.execute { blockingTaskExecuted = true }
+            fakeThreads.lightweightExecutor.execute { lightweightTaskExecuted = true }
+            advanceUntilIdle()
+
+            assertThat(backgroundTaskExecuted).isTrue()
+            assertThat(blockingTaskExecuted).isTrue()
+            assertThat(lightweightTaskExecuted).isTrue()
+        }
+
+    @Test
+    fun exceptionsInDispatcherPropagateToTestScopeFailure() {
+
+        // Exceptions in GlobalScope is propagated out of the test.
+        assertThrows(RuntimeException::class.java) {
+            val scope = TestScope()
+            val localFakeThreads = FakeThreads.fromTestScope(scope)
+            scope.runTest {
+                localFakeThreads.globalScope.launch { throw RuntimeException("globalScope") }
+            }
+        }
+
+        // Exceptions in Dispatchers are propagated out of the test.
+        assertThrows(RuntimeException::class.java) {
+            val scope = TestScope()
+            val localFakeThreads = FakeThreads.fromTestScope(scope)
+            scope.runTest {
+                launch(localFakeThreads.backgroundDispatcher) {
+                    throw RuntimeException("backgroundDispatcher")
+                }
+            }
+        }
+
+        // Exceptions in Executors are propagated out of the test.
+        assertThrows(RuntimeException::class.java) {
+            val scope = TestScope()
+            val localFakeThreads = FakeThreads.fromTestScope(scope)
+            scope.runTest {
+                localFakeThreads.backgroundExecutor.execute {
+                    throw RuntimeException("backgroundExecutor")
+                }
+            }
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
index 86b2ba2..b12508e 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
@@ -36,8 +36,7 @@
  * objects, constructors with default values for parameters, and data classes with inline classes.
  * We don't need shadowing of our classes because we want to use the actual objects in our tests.
  */
-public class RobolectricCameraPipeTestRunner(testClass: Class<*>) :
-    RobolectricTestRunner(testClass) {
+class RobolectricCameraPipeTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
     override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
         val builder = InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
         builder.doNotInstrumentPackage("androidx.camera.camera2.pipe")
@@ -46,19 +45,19 @@
     }
 }
 
-@JvmInline public value class TestValue(public val value: String)
+@JvmInline value class TestValue(public val value: String)
 
-public data class TestData(val value1: TestValue, val value2: String)
+data class TestData(val value1: TestValue, val value2: String)
 
 @RunWith(JUnit4::class)
-public class DataWithInlineClassJUnitTest {
+class DataWithInlineClassJUnitTest {
     @Test
-    public fun inlineClassesAreEqualInJUnit() {
+    fun inlineClassesAreEqualInJUnit() {
         assertThat(TestValue("42")).isEqualTo(TestValue("42"))
     }
 
     @Test
-    public fun dataWithInlineClassesAreEqualInJUnit() {
+    fun dataWithInlineClassesAreEqualInJUnit() {
         assertThat(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
             .isEqualTo(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
     }
@@ -66,14 +65,14 @@
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class DataWithInlineClassRobolectricTest {
+class DataWithInlineClassRobolectricTest {
     @Test
-    public fun inlineClassesAreEqualInRobolectric() {
+    fun inlineClassesAreEqualInRobolectric() {
         assertThat(TestValue("42")).isEqualTo(TestValue("42"))
     }
 
     @Test
-    public fun dataWithInlineClassesAreEqualInRobolectric() {
+    fun dataWithInlineClassesAreEqualInRobolectric() {
         assertThat(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
             .isEqualTo(TestData(value1 = TestValue("Test value #1"), value2 = "Test value #2"))
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
index 5db75d8..3b93611 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.async
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.ensureActive
@@ -172,29 +173,37 @@
     override fun <T> useSessionIn(
         scope: CoroutineScope,
         action: suspend CoroutineScope.(CameraGraph.Session) -> T
-    ): Deferred<T> =
-        scope.async(start = CoroutineStart.UNDISPATCHED) {
-            ensureActive() // Exit early if the parent scope has been canceled.
+    ): Deferred<T> {
+        // https://github.com/Kotlin/kotlinx.coroutines/issues/1578
+        // To handle `runBlocking` we need to use `job.complete()` in `result.invokeOnCompletion`.
+        // However, if we do this directly on the scope that is provided it will cause
+        // SupervisorScopes to block and never complete. To work around this, we create a childJob,
+        // propagate the existing context, and use that as the context for scope.async.
+        val childJob = Job(scope.coroutineContext[Job])
+        val context = scope.coroutineContext + childJob
+        val result =
+            scope.async(context = context, start = CoroutineStart.UNDISPATCHED) {
+                ensureActive() // Exit early if the parent scope has been canceled.
 
-            // It is very important to acquire *and* suspend here. Invoking a coroutine using
-            // UNDISPATCHED will execute on the current thread until the suspension point, and this
-            // will
-            // force the execution to switch to the provided scope after ensuring the lock is
-            // acquired
-            // or in the queue. This guarantees exclusion, ordering, and execution within the
-            // correct
-            // scope.
-            val token = sessionMutex.acquireTokenAndSuspend()
+                // It is very important to acquire *and* suspend here. Invoking a coroutine using
+                // UNDISPATCHED will execute on the current thread until the suspension point, and
+                // this will force the execution to switch to the provided scope after ensuring the
+                // lock is acquired or in the queue. This guarantees exclusion, ordering, and
+                // execution within the correct scope.
+                val token = sessionMutex.acquireTokenAndSuspend()
 
-            // Create and use the session.
-            createSessionFromToken(token).use {
-                // Wrap the block in a coroutineScope to ensure all operations are completed before
-                // exiting and releasing the lock. The lock can be released early if the calling
-                // action
-                // decided to call session.close() early.
-                coroutineScope { action(it) }
+                // Create and use the session
+                createSessionFromToken(token).use {
+                    // Wrap the block in a coroutineScope to ensure all operations are completed
+                    // before exiting and releasing the lock. The lock can be released early if the
+                    // calling action decides to call session.close() early.
+                    coroutineScope { action(it) }
+                }
             }
-        }
+
+        result.invokeOnCompletion { childJob.complete() }
+        return result
+    }
 
     private fun createSessionFromToken(token: Token) =
         CameraGraphSessionImpl(token, graphProcessor, controller3A, frameCaptureQueue)
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
index 8df2b23..78ebf5d 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
@@ -42,11 +42,21 @@
 import androidx.camera.camera2.pipe.testing.FakeThreads
 import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
 import androidx.test.core.app.ApplicationProvider
+import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.eq
@@ -61,6 +71,8 @@
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 internal class CameraGraphImplTest {
+    private val testScope = TestScope()
+
     private val context = ApplicationProvider.getApplicationContext() as Context
     private val metadata =
         FakeCameraMetadata(
@@ -77,184 +89,374 @@
     private val stream2Config =
         CameraStream.Config.create(Size(1920, 1080), StreamFormat.YUV_420_888)
 
-    private lateinit var cameraController: CameraControllerSimulator
-    private lateinit var stream1: CameraStream
-    private lateinit var stream2: CameraStream
+    private val graphConfig =
+        CameraGraph.Config(
+            camera = metadata.camera,
+            streams = listOf(stream1Config, stream2Config),
+        )
+    private val threads = FakeThreads.fromTestScope(testScope)
+    private val backend = FakeCameraBackend(fakeCameras = mapOf(metadata.camera to metadata))
+    private val backends =
+        CameraBackendsImpl(
+            defaultBackendId = backend.id,
+            cameraBackends = mapOf(backend.id to CameraBackendFactory { backend }),
+            context,
+            threads
+        )
+    private val cameraContext = CameraBackendsImpl.CameraBackendContext(context, threads, backends)
+    private val graphLifecycleManager = GraphLifecycleManager(threads)
+    private val streamGraph = StreamGraphImpl(metadata, graphConfig)
+    private val imageSourceMap = ImageSourceMap(graphConfig, streamGraph, threads)
+    private val frameCaptureQueue = FrameCaptureQueue()
+    private val frameDistributor =
+        FrameDistributor(imageSourceMap.imageSources, frameCaptureQueue) {}
+    private val cameraController =
+        CameraControllerSimulator(cameraContext, graphConfig, fakeGraphProcessor, streamGraph)
 
-    private fun initializeCameraGraphImpl(scope: TestScope): CameraGraphImpl {
-        val graphConfig =
-            CameraGraph.Config(
-                camera = metadata.camera,
-                streams = listOf(stream1Config, stream2Config),
-            )
-        val threads = FakeThreads.fromTestScope(scope)
-        val backend = FakeCameraBackend(fakeCameras = mapOf(metadata.camera to metadata))
-        val backends =
-            CameraBackendsImpl(
-                defaultBackendId = backend.id,
-                cameraBackends = mapOf(backend.id to CameraBackendFactory { backend }),
-                context,
-                threads
-            )
-        val cameraContext = CameraBackendsImpl.CameraBackendContext(context, threads, backends)
-        val graphLifecycleManager = GraphLifecycleManager(threads)
-        val streamGraph = StreamGraphImpl(metadata, graphConfig)
-        val imageSourceMap = ImageSourceMap(graphConfig, streamGraph, threads)
-        val frameCaptureQueue = FrameCaptureQueue()
-        val frameDistributor = FrameDistributor(imageSourceMap.imageSources, frameCaptureQueue) {}
-        cameraController =
-            CameraControllerSimulator(cameraContext, graphConfig, fakeGraphProcessor, streamGraph)
+    private val surfaceGraph =
+        SurfaceGraph(streamGraph, cameraController, cameraSurfaceManager, emptyMap())
+    private val audioRestriction = FakeAudioRestrictionController()
+    private val cameraGraphId = CameraGraphId.nextId()
+    private val cameraGraph =
+        CameraGraphImpl(
+            graphConfig,
+            metadata,
+            cameraGraphId,
+            graphLifecycleManager,
+            fakeGraphProcessor,
+            fakeGraphProcessor,
+            streamGraph,
+            surfaceGraph,
+            backend,
+            cameraController,
+            GraphState3A(),
+            Listener3A(),
+            frameDistributor,
+            frameCaptureQueue,
+            audioRestriction
+        )
+    private val stream1: CameraStream =
+        checkNotNull(cameraGraph.streams[stream1Config]) {
+            "Failed to find stream for $stream1Config!"
+        }
+
+    private val stream2 =
+        checkNotNull(cameraGraph.streams[stream2Config]) {
+            "Failed to find stream for $stream2Config!"
+        }
+
+    init {
         cameraSurfaceManager.addListener(fakeSurfaceListener)
-        val surfaceGraph =
-            SurfaceGraph(streamGraph, cameraController, cameraSurfaceManager, emptyMap())
-        val audioRestriction = FakeAudioRestrictionController()
-        val cameraGraphId = CameraGraphId.nextId()
-        val graph =
-            CameraGraphImpl(
-                graphConfig,
-                metadata,
-                cameraGraphId,
-                graphLifecycleManager,
-                fakeGraphProcessor,
-                fakeGraphProcessor,
-                streamGraph,
-                surfaceGraph,
-                backend,
-                cameraController,
-                GraphState3A(),
-                Listener3A(),
-                frameDistributor,
-                frameCaptureQueue,
-                audioRestriction
-            )
-        stream1 =
-            checkNotNull(graph.streams[stream1Config]) {
-                "Failed to find stream for $stream1Config!"
-            }
+    }
 
-        stream2 =
-            checkNotNull(graph.streams[stream2Config]) {
-                "Failed to find stream for $stream2Config!"
-            }
-        return graph
+    @Test fun createCameraGraphImpl() = testScope.runTest { assertThat(cameraGraph).isNotNull() }
+
+    @Test
+    fun testAcquireSession() =
+        testScope.runTest {
+            val session = cameraGraph.acquireSession()
+            assertThat(session).isNotNull()
+        }
+
+    @Test
+    fun testAcquireSessionOrNull() =
+        testScope.runTest {
+            val session = cameraGraph.acquireSessionOrNull()
+            assertThat(session).isNotNull()
+        }
+
+    @Test
+    fun testAcquireSessionOrNullAfterAcquireSession() =
+        testScope.runTest {
+            val session = cameraGraph.acquireSession()
+            assertThat(session).isNotNull()
+
+            // Since a session is already active, an attempt to acquire another session will fail.
+            val session1 = cameraGraph.acquireSessionOrNull()
+            assertThat(session1).isNull()
+
+            // Closing an active session should allow a new session instance to be created.
+            session.close()
+
+            val session2 = cameraGraph.acquireSessionOrNull()
+            assertThat(session2).isNotNull()
+        }
+
+    @Test
+    fun sessionSubmitsRequestsToGraphProcessor() =
+        testScope.runTest {
+            val session = checkNotNull(cameraGraph.acquireSessionOrNull())
+            val request = Request(listOf())
+            session.submit(request)
+            advanceUntilIdle()
+
+            assertThat(fakeGraphProcessor.requestQueue).contains(listOf(request))
+        }
+
+    @Test
+    fun sessionSetsRepeatingRequestOnGraphProcessor() =
+        testScope.runTest {
+            val session = checkNotNull(cameraGraph.acquireSessionOrNull())
+            val request = Request(listOf())
+            session.startRepeating(request)
+            advanceUntilIdle()
+
+            assertThat(fakeGraphProcessor.repeatingRequest).isSameInstanceAs(request)
+        }
+
+    @Test
+    fun sessionAbortsRequestOnGraphProcessor() =
+        testScope.runTest {
+            val session = checkNotNull(cameraGraph.acquireSessionOrNull())
+            val request = Request(listOf())
+            session.submit(request)
+            session.abort()
+            advanceUntilIdle()
+
+            assertThat(fakeGraphProcessor.requestQueue).isEmpty()
+        }
+
+    @Test
+    fun closingSessionDoesNotCloseGraphProcessor() =
+        testScope.runTest {
+            val session = cameraGraph.acquireSessionOrNull()
+            checkNotNull(session).close()
+            advanceUntilIdle()
+
+            assertThat(fakeGraphProcessor.closed).isFalse()
+        }
+
+    @Test
+    fun closingCameraGraphClosesGraphProcessor() =
+        testScope.runTest {
+            cameraGraph.close()
+            assertThat(fakeGraphProcessor.closed).isTrue()
+        }
+
+    @Test
+    fun stoppingCameraGraphStopsGraphProcessor() =
+        testScope.runTest {
+            assertThat(cameraController.started).isFalse()
+            assertThat(fakeGraphProcessor.closed).isFalse()
+            cameraGraph.start()
+            assertThat(cameraController.started).isTrue()
+            cameraGraph.stop()
+            assertThat(cameraController.started).isFalse()
+            assertThat(fakeGraphProcessor.closed).isFalse()
+            cameraGraph.start()
+            assertThat(cameraController.started).isTrue()
+            cameraGraph.close()
+            assertThat(cameraController.started).isFalse()
+            assertThat(fakeGraphProcessor.closed).isTrue()
+        }
+
+    @Test
+    fun closingCameraGraphClosesAssociatedSurfaces() =
+        testScope.runTest {
+            cameraGraph.setSurface(stream1.id, imageReader1.surface)
+            cameraGraph.setSurface(stream2.id, imageReader2.surface)
+            cameraGraph.close()
+
+            verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader1.surface))
+            verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader2.surface))
+            verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
+            verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
+        }
+
+    @Test
+    fun useSessionInOperatesInOrder() =
+        testScope.runTest {
+            val events = mutableListOf<Int>()
+            val job1 =
+                cameraGraph.useSessionIn(testScope) {
+                    yield()
+                    events += 2
+                }
+            val job2 =
+                cameraGraph.useSessionIn(testScope) {
+                    delay(100)
+                    events += 3
+                }
+            val job3 =
+                cameraGraph.useSessionIn(testScope) {
+                    yield()
+                    events += 4
+                }
+
+            events += 1
+            job1.join()
+            job2.join()
+            job3.join()
+
+            assertThat(events).containsExactly(1, 2, 3, 4).inOrder()
+        }
+
+    @Test
+    fun useSessionWithEarlyCloseAllowsInterleavedExecution() =
+        testScope.runTest {
+            val events = mutableListOf<Int>()
+            val job1 =
+                cameraGraph.useSessionIn(testScope) { session ->
+                    yield()
+                    events += 2
+                    session.close()
+                    delay(1000)
+                    events += 5
+                }
+            val job2 =
+                cameraGraph.useSessionIn(testScope) {
+                    delay(100)
+                    events += 3
+                }
+            val job3 =
+                cameraGraph.useSessionIn(testScope) {
+                    yield()
+                    events += 4
+                }
+
+            events += 1
+            job1.join()
+            job2.join()
+            job3.join()
+
+            assertThat(events).containsExactly(1, 2, 3, 4, 5).inOrder()
+        }
+
+    @Test
+    fun useSessionInWithRunBlockingDoesNotStall() = runBlocking {
+        val deferred = cameraGraph.useSessionIn(this) { delay(1) }
+        deferred.await() // Make sure this does not block.
     }
 
     @Test
-    fun createCameraGraphImpl() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        assertThat(cameraGraphImpl).isNotNull()
-    }
+    fun coroutineScope_isCanceledWithException() =
+        testScope.runTest {
+            val scope = CoroutineScope(Job())
+
+            val deferred = scope.async { throw RuntimeException() }
+            deferred.join()
+
+            // Ensure the deferred is completed with an exception, and that the scope is NOT active.
+            assertThat(deferred.isCompleted).isTrue()
+            assertThat(deferred.getCompletionExceptionOrNull())
+                .isInstanceOf(RuntimeException::class.java)
+            assertThrows<RuntimeException> { deferred.await() }
+            assertThat(scope.isActive).isFalse()
+        }
 
     @Test
-    fun testAcquireSession() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        val session = cameraGraphImpl.acquireSession()
-        assertThat(session).isNotNull()
-    }
+    fun coroutineSupervisorScope_isNotCanceledWithException() =
+        testScope.runTest {
+            val scope = CoroutineScope(SupervisorJob())
+
+            val deferred = scope.async { throw RuntimeException() }
+            deferred.join()
+
+            // Ensure the deferred is completed with an exception, and that the scope remains
+            // active.
+            assertThat(deferred.isCompleted).isTrue()
+            assertThat(deferred.getCompletionExceptionOrNull())
+                .isInstanceOf(RuntimeException::class.java)
+            assertThrows<RuntimeException> { deferred.await() }
+            assertThat(scope.isActive).isTrue()
+        }
 
     @Test
-    fun testAcquireSessionOrNull() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        val session = cameraGraphImpl.acquireSessionOrNull()
-        assertThat(session).isNotNull()
-    }
+    fun useSessionIn_scopeIsCanceledWithException() =
+        testScope.runTest {
+            val scope = CoroutineScope(Job())
+
+            val deferred = cameraGraph.useSessionIn(scope) { throw RuntimeException() }
+            deferred.join()
+
+            assertThat(deferred.isCompleted).isTrue()
+            assertThat(deferred.getCompletionExceptionOrNull())
+                .isInstanceOf(RuntimeException::class.java)
+            assertThrows<RuntimeException> { deferred.await() }
+            assertThat(scope.isActive).isFalse() // Regular scopes are canceled
+        }
 
     @Test
-    fun testAcquireSessionOrNullAfterAcquireSession() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        val session = cameraGraphImpl.acquireSession()
-        assertThat(session).isNotNull()
+    fun useSessionIn_supervisorScopeIsNotCanceledWithException() =
+        testScope.runTest {
+            val scope = CoroutineScope(SupervisorJob())
+            val deferred = cameraGraph.useSessionIn(scope) { throw RuntimeException() }
+            deferred.join()
 
-        // Since a session is already active, an attempt to acquire another session will fail.
-        val session1 = cameraGraphImpl.acquireSessionOrNull()
-        assertThat(session1).isNull()
-
-        // Closing an active session should allow a new session instance to be created.
-        session.close()
-
-        val session2 = cameraGraphImpl.acquireSessionOrNull()
-        assertThat(session2).isNotNull()
-    }
+            assertThat(deferred.isCompleted).isTrue()
+            assertThat(deferred.getCompletionExceptionOrNull())
+                .isInstanceOf(RuntimeException::class.java)
+            assertThrows<RuntimeException> { deferred.await() }
+            assertThat(scope.isActive).isTrue() // Supervisor scopes are not canceled
+        }
 
     @Test
-    fun sessionSubmitsRequestsToGraphProcessor() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        val session = checkNotNull(cameraGraphImpl.acquireSessionOrNull())
-        val request = Request(listOf())
-        session.submit(request)
-        advanceUntilIdle()
+    fun coroutineSupervisorTestScope_isNotCanceledWithException() =
+        testScope.runTest {
+            // This illustrates the correct way to create a scope that uses the testScope
+            // dispatcher, does delay skipping, but also does not fail the test if an exception
+            // occurs when doing scope.async. This is useful if, for example, in a real environment
+            // scope represents a supervisor job that will not crash if a coroutine fails and if
+            // some other system is handling the result of the deferred.
+            val scope = CoroutineScope(testScope.coroutineContext + Job())
 
-        assertThat(fakeGraphProcessor.requestQueue).contains(listOf(request))
-    }
+            val deferred =
+                scope.async {
+                    delay(100000) // Delay skipping
+                    throw RuntimeException()
+                }
+            deferred.join()
+
+            assertThat(deferred.isCompleted).isTrue()
+            assertThat(deferred.getCompletionExceptionOrNull())
+                .isInstanceOf(RuntimeException::class.java)
+            assertThrows<RuntimeException> { deferred.await() }
+            assertThat(scope.isActive).isFalse()
+            assertThat(testScope.isActive).isTrue()
+        }
 
     @Test
-    fun sessionSetsRepeatingRequestOnGraphProcessor() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        val session = checkNotNull(cameraGraphImpl.acquireSessionOrNull())
-        val request = Request(listOf())
-        session.startRepeating(request)
-        advanceUntilIdle()
+    fun useSessionIn_withSupervisorTestScopeDoesNotCancelTestScope() =
+        testScope.runTest {
+            // Create a scope that uses the testScope dispatcher and delaySkipping, but does not
+            // fail
+            // the test if an exception occurs in useSessionIn.
+            val scope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
 
-        assertThat(fakeGraphProcessor.repeatingRequest).isSameInstanceAs(request)
-    }
+            // If you pass in a testScope to useSessionIn, any exception will cause the test to
+            // fail. If, instead, you want to test that the deferred handles the exception, you must
+            // pass in an independent CoroutineScope.
+            val deferred = cameraGraph.useSessionIn(scope) { throw RuntimeException() }
+            deferred.join()
+
+            assertThat(deferred.isCompleted).isTrue()
+            assertThat(deferred.getCompletionExceptionOrNull())
+                .isInstanceOf(RuntimeException::class.java)
+            assertThat(scope.isActive).isTrue() // Supervisor scopes are not canceled
+            assertThat(testScope.isActive).isTrue()
+        }
 
     @Test
-    fun sessionAbortsRequestOnGraphProcessor() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        val session = checkNotNull(cameraGraphImpl.acquireSessionOrNull())
-        val request = Request(listOf())
-        session.submit(request)
-        session.abort()
-        advanceUntilIdle()
+    fun useSessionIn_withCancellationDoesNotFailTest() =
+        testScope.runTest {
+            val deferred =
+                cameraGraph.useSessionIn(testScope) {
+                    throw CancellationException() // Throwing cancellation does not cause the test
+                    // to fail.
+                }
+            deferred.join()
 
-        assertThat(fakeGraphProcessor.requestQueue).isEmpty()
-    }
+            assertThat(deferred.isActive).isFalse()
+            assertThat(deferred.isCompleted).isTrue()
+            assertThat(deferred.isCancelled).isTrue()
+            assertThat(deferred.getCompletionExceptionOrNull())
+                .isInstanceOf(CancellationException::class.java)
+            assertThat(testScope.isActive).isTrue()
+        }
 
     @Test
-    fun closingSessionDoesNotCloseGraphProcessor() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        val session = cameraGraphImpl.acquireSessionOrNull()
-        checkNotNull(session).close()
-        advanceUntilIdle()
-
-        assertThat(fakeGraphProcessor.closed).isFalse()
-    }
-
-    @Test
-    fun closingCameraGraphClosesGraphProcessor() = runTest {
-        val cameraGraphImpl = initializeCameraGraphImpl(this)
-        cameraGraphImpl.close()
-        assertThat(fakeGraphProcessor.closed).isTrue()
-    }
-
-    @Test
-    fun stoppingCameraGraphStopsGraphProcessor() = runTest {
-        val cameraGraph = initializeCameraGraphImpl(this)
-
-        assertThat(cameraController.started).isFalse()
-        assertThat(fakeGraphProcessor.closed).isFalse()
-        cameraGraph.start()
-        assertThat(cameraController.started).isTrue()
-        cameraGraph.stop()
-        assertThat(cameraController.started).isFalse()
-        assertThat(fakeGraphProcessor.closed).isFalse()
-        cameraGraph.start()
-        assertThat(cameraController.started).isTrue()
-        cameraGraph.close()
-        assertThat(cameraController.started).isFalse()
-        assertThat(fakeGraphProcessor.closed).isTrue()
-    }
-
-    @Test
-    fun closingCameraGraphClosesAssociatedSurfaces() = runTest {
-        val cameraGraph = initializeCameraGraphImpl(this)
-        cameraGraph.setSurface(stream1.id, imageReader1.surface)
-        cameraGraph.setSurface(stream2.id, imageReader2.surface)
-        cameraGraph.close()
-
-        verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader1.surface))
-        verify(fakeSurfaceListener, times(1)).onSurfaceActive(eq(imageReader2.surface))
-        verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
-        verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
-    }
+    fun useSession_throwsExceptions() =
+        testScope.runTest {
+            assertThrows<RuntimeException> { cameraGraph.useSession { throw RuntimeException() } }
+        }
 }
diff --git a/camera/camera-camera2/build.gradle b/camera/camera-camera2/build.gradle
index 5fdd589..9c33c0e 100644
--- a/camera/camera-camera2/build.gradle
+++ b/camera/camera-camera2/build.gradle
@@ -47,7 +47,7 @@
     testImplementation(libs.robolectric)
     testImplementation(libs.mockitoCore4)
     testImplementation(libs.kotlinCoroutinesTest)
-    testImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    testImplementation("androidx.annotation:annotation-experimental:1.4.1")
     testImplementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
     testImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.3.1")
     testImplementation(project(":camera:camera-video"))
@@ -72,7 +72,7 @@
     }
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.kotlinCoroutinesAndroid)
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.13.1")
     androidTestImplementation("androidx.exifinterface:exifinterface:1.0.0")
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index 8349b71..7277d2d 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -33,7 +33,7 @@
     api("androidx.annotation:annotation:1.2.0")
     api("androidx.lifecycle:lifecycle-livedata:2.1.0")
     api(libs.guavaListenableFuture)
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     api(libs.kotlinStdlib) // Added for annotation-experimental
     api("androidx.core:core:1.1.0")
     implementation(libs.kotlinCoroutinesAndroid)
diff --git a/camera/camera-lifecycle/build.gradle b/camera/camera-lifecycle/build.gradle
index 84855f8..6de8011 100644
--- a/camera/camera-lifecycle/build.gradle
+++ b/camera/camera-lifecycle/build.gradle
@@ -53,7 +53,7 @@
     }
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.kotlinCoroutinesAndroid)
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
     androidTestImplementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation("org.jetbrains.kotlinx:atomicfu:0.13.1")
diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle
index 34164df..77d81b0 100644
--- a/camera/camera-view/build.gradle
+++ b/camera/camera-view/build.gradle
@@ -36,7 +36,7 @@
     api(project(":camera:camera-video"))
 
     implementation(project(":camera:camera-lifecycle"))
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation(libs.guavaListenableFuture)
     implementation("androidx.core:core:1.3.2")
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
diff --git a/camera/camera-viewfinder-core/build.gradle b/camera/camera-viewfinder-core/build.gradle
index 9666c33..664b6ab 100644
--- a/camera/camera-viewfinder-core/build.gradle
+++ b/camera/camera-viewfinder-core/build.gradle
@@ -31,7 +31,7 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.2.0")
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation(libs.guavaListenableFuture)
     implementation("androidx.core:core:1.7.0")
     implementation("androidx.concurrent:concurrent-futures:1.1.0")
diff --git a/camera/camera-viewfinder/build.gradle b/camera/camera-viewfinder/build.gradle
index dc43d15..11a7321 100644
--- a/camera/camera-viewfinder/build.gradle
+++ b/camera/camera-viewfinder/build.gradle
@@ -32,7 +32,7 @@
 dependencies {
     api("androidx.annotation:annotation:1.2.0")
     api(project(':camera:camera-viewfinder-core'))
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation(libs.guavaListenableFuture)
     implementation("androidx.core:core:1.7.0")
     implementation("androidx.concurrent:concurrent-futures:1.1.0")
diff --git a/camera/integration-tests/avsynctestapp/build.gradle b/camera/integration-tests/avsynctestapp/build.gradle
index 58084b0..c64700e 100644
--- a/camera/integration-tests/avsynctestapp/build.gradle
+++ b/camera/integration-tests/avsynctestapp/build.gradle
@@ -60,7 +60,7 @@
     }
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
     androidTestImplementation(project(":annotation:annotation"))
     androidTestImplementation("androidx.lifecycle:lifecycle-common:2.8.3")
 
diff --git a/camera/integration-tests/coretestapp/build.gradle b/camera/integration-tests/coretestapp/build.gradle
index 2c76e8d..1d35fd6 100644
--- a/camera/integration-tests/coretestapp/build.gradle
+++ b/camera/integration-tests/coretestapp/build.gradle
@@ -95,7 +95,7 @@
     debugImplementation(libs.testRunner)
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
 
     // Testing framework
     androidTestImplementation(libs.testCore)
diff --git a/camera/integration-tests/diagnosetestapp/build.gradle b/camera/integration-tests/diagnosetestapp/build.gradle
index ecf5b7f..cceb4a2 100644
--- a/camera/integration-tests/diagnosetestapp/build.gradle
+++ b/camera/integration-tests/diagnosetestapp/build.gradle
@@ -61,7 +61,7 @@
     testImplementation("junit:junit:4.12")
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
 
     // Testing framework
     androidTestImplementation(libs.testExtJunit)
diff --git a/camera/integration-tests/extensionstestapp/build.gradle b/camera/integration-tests/extensionstestapp/build.gradle
index 850c2a0..fa75554 100644
--- a/camera/integration-tests/extensionstestapp/build.gradle
+++ b/camera/integration-tests/extensionstestapp/build.gradle
@@ -71,7 +71,7 @@
     implementation("androidx.viewpager2:viewpager2:1.0.0")
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
     androidTestImplementation(project(":concurrent:concurrent-futures"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
 
diff --git a/camera/integration-tests/timingtestapp/build.gradle b/camera/integration-tests/timingtestapp/build.gradle
index 454a1e0..8d51ef2 100644
--- a/camera/integration-tests/timingtestapp/build.gradle
+++ b/camera/integration-tests/timingtestapp/build.gradle
@@ -66,7 +66,7 @@
     implementation(libs.kotlinCoroutinesAndroid)
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
 
     // Testing framework
     androidTestImplementation(project(":concurrent:concurrent-futures"))
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index b791c61..453f43f 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -96,7 +96,7 @@
     implementation("androidx.compose.material:material-icons-extended:1.4.0")
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
     androidTestImplementation(project(":concurrent:concurrent-futures"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
 
diff --git a/camera/integration-tests/viewfindertestapp/build.gradle b/camera/integration-tests/viewfindertestapp/build.gradle
index d6be6e3..84dcc2a 100644
--- a/camera/integration-tests/viewfindertestapp/build.gradle
+++ b/camera/integration-tests/viewfindertestapp/build.gradle
@@ -55,7 +55,7 @@
     implementation(libs.constraintLayout)
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
 
     // Testing framework
     androidTestImplementation(libs.testExtJunit)
diff --git a/camera/integration-tests/viewtestapp/build.gradle b/camera/integration-tests/viewtestapp/build.gradle
index f3896a1..4cff2d8 100644
--- a/camera/integration-tests/viewtestapp/build.gradle
+++ b/camera/integration-tests/viewtestapp/build.gradle
@@ -80,7 +80,7 @@
     implementation("androidx.compose.foundation:foundation:1.4.0")
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
 
     // Testing framework
     androidTestImplementation(libs.testExtJunit)
diff --git a/car/app/app-automotive/build.gradle b/car/app/app-automotive/build.gradle
index 0c5bb12..dd4116b 100644
--- a/car/app/app-automotive/build.gradle
+++ b/car/app/app-automotive/build.gradle
@@ -31,7 +31,7 @@
 dependencies {
     api(project(":car:app:app"))
     api(libs.guavaListenableFuture)
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     implementation(libs.guavaAndroid)
     implementation("androidx.concurrent:concurrent-futures:1.1.0")
     implementation("androidx.fragment:fragment:1.3.0")
diff --git a/car/app/app-testing/build.gradle b/car/app/app-testing/build.gradle
index cb47a3a..9220169 100644
--- a/car/app/app-testing/build.gradle
+++ b/car/app/app-testing/build.gradle
@@ -34,7 +34,7 @@
     implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
     implementation 'androidx.annotation:annotation:1.1.0'
     implementation(libs.robolectric)
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
 
     testImplementation(project(":car:app:app-projected"))
     testImplementation(libs.junit)
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index 972e81b..1c26d21 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -62,7 +62,7 @@
     implementation ("androidx.media:media:1.6.0")
     // Session and Screen both implement LifeCycleOwner so this needs to be exposed.
     api("androidx.lifecycle:lifecycle-common-java8:2.2.0")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
 
     api(libs.kotlinStdlib)
     implementation(libs.kotlinStdlibCommon)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
index 023e31a..b431f18 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
@@ -1096,7 +1096,7 @@
      */
     private fun adjustStorage() {
         if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
-            resizeStorage(_capacity)
+            removeDeletedMarkers()
         } else {
             resizeStorage(nextCapacity(_capacity))
         }
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index 985b8c6..350a33b 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -125,8 +125,8 @@
     method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.KeyframesSpec<T> keyframes(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig<T>,kotlin.Unit> init);
-    method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
-    method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+    method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+    method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
     method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SnapSpec<T> snap(optional int delayMillis);
@@ -220,7 +220,7 @@
     property public final int mode;
   }
 
-  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @kotlin.jvm.JvmInline public final value class ArcMode {
+  @kotlin.jvm.JvmInline public final value class ArcMode {
     field public static final androidx.compose.animation.core.ArcMode.Companion Companion;
   }
 
@@ -496,7 +496,7 @@
     ctor public KeyframesSpec.KeyframesSpecConfig();
     method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> at(T, @IntRange(from=0L) int timeStamp);
     method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> atFraction(T, @FloatRange(from=0.0, to=1.0) float fraction);
-    method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
+    method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
     method @Deprecated public infix void with(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, androidx.compose.animation.core.Easing easing);
   }
 
@@ -512,7 +512,7 @@
     property @IntRange(from=0L) public final int durationMillis;
   }
 
-  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
     ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config);
     ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config, @FloatRange(from=0.0, to=1.0) float periodicBias);
     method public androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> getConfig();
@@ -520,7 +520,7 @@
     property public final androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config;
   }
 
-  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
+  public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
     ctor public KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig();
   }
 
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 49922b8..0bf428d 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -125,8 +125,8 @@
     method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.InfiniteRepeatableSpec<T> infiniteRepeatable(androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.KeyframesSpec<T> keyframes(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig<T>,kotlin.Unit> init);
-    method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
-    method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+    method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(@FloatRange(from=0.0, to=1.0) float periodicBias, kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
+    method public static <T> androidx.compose.animation.core.KeyframesWithSplineSpec<T> keyframesWithSpline(kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>,kotlin.Unit> init);
     method @Deprecated @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.RepeatableSpec<T> repeatable(int iterations, androidx.compose.animation.core.DurationBasedAnimationSpec<T> animation, optional androidx.compose.animation.core.RepeatMode repeatMode, optional long initialStartOffset);
     method @androidx.compose.runtime.Stable public static <T> androidx.compose.animation.core.SnapSpec<T> snap(optional int delayMillis);
@@ -220,7 +220,7 @@
     property public final int mode;
   }
 
-  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @kotlin.jvm.JvmInline public final value class ArcMode {
+  @kotlin.jvm.JvmInline public final value class ArcMode {
     field public static final androidx.compose.animation.core.ArcMode.Companion Companion;
   }
 
@@ -496,7 +496,7 @@
     ctor public KeyframesSpec.KeyframesSpecConfig();
     method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> at(T, @IntRange(from=0L) int timeStamp);
     method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> atFraction(T, @FloatRange(from=0.0, to=1.0) float fraction);
-    method @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
+    method public infix androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T> using(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, int arcMode);
     method @Deprecated public infix void with(androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>, androidx.compose.animation.core.Easing easing);
   }
 
@@ -512,7 +512,7 @@
     property @IntRange(from=0L) public final int durationMillis;
   }
 
-  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
+  @androidx.compose.runtime.Immutable public final class KeyframesWithSplineSpec<T> implements androidx.compose.animation.core.DurationBasedAnimationSpec<T> {
     ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config);
     ctor public KeyframesWithSplineSpec(androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config, @FloatRange(from=0.0, to=1.0) float periodicBias);
     method public androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> getConfig();
@@ -520,7 +520,7 @@
     property public final androidx.compose.animation.core.KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> config;
   }
 
-  @SuppressCompatibility @androidx.compose.animation.core.ExperimentalAnimationSpecApi public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
+  public static final class KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T> extends androidx.compose.animation.core.KeyframesSpecBaseConfig<T,androidx.compose.animation.core.KeyframesSpec.KeyframeEntity<T>> {
     ctor public KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig();
   }
 
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
index 4a4b87e..61e8977 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt
@@ -620,14 +620,12 @@
          * E.g.: [RectToVector] assigns its values as `[left, top, right, bottom]` so the pairs of
          * dimensions animated as arcs are: `[left, top]` and `[right, bottom]`.
          */
-        @ExperimentalAnimationSpecApi
         infix fun KeyframeEntity<T>.using(arcMode: ArcMode): KeyframeEntity<T> {
             this.arcMode = arcMode
             return this
         }
     }
 
-    @OptIn(ExperimentalAnimationSpecApi::class)
     override fun <V : AnimationVector> vectorize(
         converter: TwoWayConverter<T, V>
     ): VectorizedKeyframesSpec<V> {
@@ -664,7 +662,6 @@
     }
 
     /** Holder class for building a keyframes animation. */
-    @OptIn(ExperimentalAnimationSpecApi::class)
     class KeyframeEntity<T>
     internal constructor(
         value: T,
@@ -712,7 +709,6 @@
  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForDpOffsetWithSplines
  * @see keyframesWithSpline
  */
-@ExperimentalAnimationSpecApi
 @Immutable
 class KeyframesWithSplineSpec<T>(
     val config: KeyframesWithSplineSpecConfig<T>,
@@ -735,7 +731,6 @@
         this.periodicBias = periodicBias
     }
 
-    @ExperimentalAnimationSpecApi
     class KeyframesWithSplineSpecConfig<T> :
         KeyframesSpecBaseConfig<T, KeyframesSpec.KeyframeEntity<T>>() {
 
@@ -822,7 +817,13 @@
 }
 
 /**
- * Creates a [KeyframesWithSplineSpec] animation, initialized with [init]. For example:
+ * Creates a [KeyframesWithSplineSpec] animation, initialized with [init].
+ *
+ * For more details on implementation, see [KeyframesWithSplineSpec].
+ *
+ * Use overload that takes a [Float] parameter to use periodic splines.
+ *
+ * Example:
  *
  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForOffsetWithSplines
  * @param init Initialization function for the [KeyframesWithSplineSpec] animation
@@ -830,7 +831,6 @@
  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForDpOffsetWithSplines
  * @see KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig
  */
-@ExperimentalAnimationSpecApi
 fun <T> keyframesWithSpline(
     init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
 ): KeyframesWithSplineSpec<T> =
@@ -859,7 +859,6 @@
  * @param init Initialization function for the [KeyframesWithSplineSpec] animation
  * @see KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig
  */
-@ExperimentalAnimationSpecApi
 fun <T> keyframesWithSpline(
     @FloatRange(0.0, 1.0) periodicBias: Float,
     init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
index bc934c3..a816ef1 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt
@@ -29,7 +29,6 @@
  * @param y Array of values (of size n), where each value is spread on a [FloatArray] for each of
  *   its dimensions, expected to be of even size since two values are needed to interpolate arcs.
  */
-@ExperimentalAnimationSpecApi
 internal class ArcSpline(arcModes: IntArray, timePoints: FloatArray, y: Array<FloatArray>) {
     private val arcs: Array<Array<Arc>>
     private val isExtrapolate = true
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
index 00d0577..508d41d 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/MonoSpline.kt
@@ -22,7 +22,6 @@
  * This performs a spline interpolation in multiple dimensions time is an array of all positions and
  * y is a list of arrays each with the values at each point
  */
-@ExperimentalAnimationSpecApi
 internal class MonoSpline(time: FloatArray, y: Array<FloatArray>, periodicBias: Float) {
     private val timePoints: FloatArray
     private val values: Array<FloatArray>
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
index f937784..266e8dd 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
@@ -210,7 +210,6 @@
  *
  * @see [KeyframesSpec]
  */
-@OptIn(ExperimentalAnimationSpecApi::class)
 class VectorizedKeyframesSpec<V : AnimationVector>
 internal constructor(
     // List of all timestamps. Must include start (time = 0), end (time = durationMillis) and all
@@ -519,7 +518,6 @@
  * @see ArcLinear
  * @see ArcAnimationSpec
  */
-@ExperimentalAnimationSpecApi
 @JvmInline
 value class ArcMode internal constructor(internal val value: Int) {
 
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
index 89d9ab6..ddc9938 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedMonoSplineKeyframesSpec.kt
@@ -20,7 +20,6 @@
 import androidx.collection.IntObjectMap
 
 /** Implementation of [VectorizedMonoSplineKeyframesSpec] using [MonoSpline]. */
-@ExperimentalAnimationSpecApi
 internal class VectorizedMonoSplineKeyframesSpec<V : AnimationVector>(
     private val timestamps: IntList,
     private val keyframes: IntObjectMap<Pair<V, Easing>>,
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index f3a0352c..c33dea6 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -53,7 +53,7 @@
         }
         androidMain.dependencies {
             api("androidx.annotation:annotation:1.1.0")
-            api("androidx.annotation:annotation-experimental:1.4.0")
+            api("androidx.annotation:annotation-experimental:1.4.1")
             implementation("androidx.core:core-ktx:1.5.0")
         }
 
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index 6dd8e8b5..5294d9d 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -71,7 +71,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 5019aed..33c700e 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -63,7 +63,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 implementation("androidx.core:core:1.7.0")
                 implementation("androidx.compose.animation:animation-core:1.2.1")
             }
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 425b837..1bd3eb3 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -825,6 +825,10 @@
     method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
+  public final class LazyListAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.LazyListState state);
+  }
+
   public interface LazyListItemInfo {
     method public default Object? getContentType();
     method public int getIndex();
@@ -951,6 +955,10 @@
     property public final int currentLineSpan;
   }
 
+  public final class LazyGridAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.grid.LazyGridState state);
+  }
+
   public final class LazyGridDslKt {
     method @androidx.compose.runtime.Composable public static void LazyHorizontalGrid(androidx.compose.foundation.lazy.grid.GridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void LazyVerticalGrid(androidx.compose.foundation.lazy.grid.GridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
@@ -1106,6 +1114,19 @@
     property public final T value;
   }
 
+  public interface LazyLayoutAnimateScrollScope {
+    method public int calculateDistanceTo(int targetIndex, optional int targetOffset);
+    method public int getFirstVisibleItemIndex();
+    method public int getFirstVisibleItemScrollOffset();
+    method public int getItemCount();
+    method public int getLastVisibleItemIndex();
+    method public void snapToItem(androidx.compose.foundation.gestures.ScrollScope, int index, optional int offset);
+    property public abstract int firstVisibleItemIndex;
+    property public abstract int firstVisibleItemScrollOffset;
+    property public abstract int itemCount;
+    property public abstract int lastVisibleItemIndex;
+  }
+
   public abstract class LazyLayoutIntervalContent<Interval extends androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent.Interval> {
     ctor public LazyLayoutIntervalContent();
     method public final Object? getContentType(int index);
@@ -1209,6 +1230,10 @@
 
 package androidx.compose.foundation.lazy.staggeredgrid {
 
+  public final class LazyStaggeredGridAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state);
+  }
+
   public final class LazyStaggeredGridDslKt {
     method @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional float horizontalItemSpacing, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional float verticalItemSpacing, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
@@ -1394,6 +1419,10 @@
     property public abstract java.util.List<androidx.compose.foundation.pager.PageInfo> visiblePagesInfo;
   }
 
+  public final class PagerLazyAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.pager.PagerState state);
+  }
+
   public sealed interface PagerScope {
   }
 
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 82f15b1..52eacab 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -827,6 +827,10 @@
     method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
+  public final class LazyListAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.LazyListState state);
+  }
+
   public interface LazyListItemInfo {
     method public default Object? getContentType();
     method public int getIndex();
@@ -953,6 +957,10 @@
     property public final int currentLineSpan;
   }
 
+  public final class LazyGridAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.grid.LazyGridState state);
+  }
+
   public final class LazyGridDslKt {
     method @androidx.compose.runtime.Composable public static void LazyHorizontalGrid(androidx.compose.foundation.lazy.grid.GridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void LazyVerticalGrid(androidx.compose.foundation.lazy.grid.GridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.grid.LazyGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.grid.LazyGridScope,kotlin.Unit> content);
@@ -1108,6 +1116,19 @@
     property public final T value;
   }
 
+  public interface LazyLayoutAnimateScrollScope {
+    method public int calculateDistanceTo(int targetIndex, optional int targetOffset);
+    method public int getFirstVisibleItemIndex();
+    method public int getFirstVisibleItemScrollOffset();
+    method public int getItemCount();
+    method public int getLastVisibleItemIndex();
+    method public void snapToItem(androidx.compose.foundation.gestures.ScrollScope, int index, optional int offset);
+    property public abstract int firstVisibleItemIndex;
+    property public abstract int firstVisibleItemScrollOffset;
+    property public abstract int itemCount;
+    property public abstract int lastVisibleItemIndex;
+  }
+
   public abstract class LazyLayoutIntervalContent<Interval extends androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent.Interval> {
     ctor public LazyLayoutIntervalContent();
     method public final Object? getContentType(int index);
@@ -1211,6 +1232,10 @@
 
 package androidx.compose.foundation.lazy.staggeredgrid {
 
+  public final class LazyStaggeredGridAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state);
+  }
+
   public final class LazyStaggeredGridDslKt {
     method @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional float horizontalItemSpacing, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional float verticalItemSpacing, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
@@ -1396,6 +1421,10 @@
     property public abstract java.util.List<androidx.compose.foundation.pager.PageInfo> visiblePagesInfo;
   }
 
+  public final class PagerLazyAnimateScrollScopeKt {
+    method public static androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope LazyLayoutAnimateScrollScope(androidx.compose.foundation.pager.PagerState state);
+  }
+
   public sealed interface PagerScope {
   }
 
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 2afbfde..95118fd 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -68,7 +68,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 implementation("androidx.emoji2:emoji2:1.3.0")
                 implementation("androidx.core:core:1.13.1")
             }
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
index 3db1162..5dc0573 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
@@ -17,18 +17,24 @@
 package androidx.compose.foundation.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
@@ -37,11 +43,13 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
 
 @Sampled
 @Composable
@@ -142,3 +150,44 @@
 }
 
 @Composable private fun ScrollToTopButton(@Suppress("UNUSED_PARAMETER") listState: LazyListState) {}
+
+@Sampled
+@Composable
+fun CustomLazyListAnimateToItemScrollSample() {
+    val itemsList = (0..100).toList()
+    val state = rememberLazyListState()
+    val scope = rememberCoroutineScope()
+    val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        Button(
+            onClick = {
+                scope.launch {
+                    state.scroll {
+                        with(animatedScrollScope) {
+                            snapToItem(40, 0) // teleport to item 40
+                            val distance = calculateDistanceTo(50).toFloat()
+                            var previousValue = 0f
+                            androidx.compose.animation.core.animate(
+                                0f,
+                                distance,
+                                animationSpec = tween(5_000)
+                            ) { currentValue, _ ->
+                                previousValue += scrollBy(currentValue - previousValue)
+                            }
+                        }
+                    }
+                }
+            }
+        ) {
+            Text("Scroll To Item 50")
+        }
+        LazyRow(state = state) {
+            items(itemsList) {
+                Box(Modifier.padding(2.dp).background(Color.Red).size(45.dp)) {
+                    Text(it.toString())
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt
index 2025fc1..d7cc99e 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyGridSamples.kt
@@ -17,20 +17,29 @@
 package androidx.compose.foundation.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
 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.layout.wrapContentSize
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.grid.items
 import androidx.compose.foundation.lazy.grid.itemsIndexed
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
@@ -39,11 +48,13 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
 
 @Sampled
 @Composable
@@ -191,3 +202,48 @@
 }
 
 @Composable private fun ScrollToTopButton(@Suppress("UNUSED_PARAMETER") gridState: LazyGridState) {}
+
+@Sampled
+@Composable
+fun CustomLazyGridAnimateToItemScrollSample() {
+    val itemsList = (0..100).toList()
+    val state = rememberLazyGridState()
+    val scope = rememberCoroutineScope()
+    val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        Button(
+            onClick = {
+                scope.launch {
+                    state.scroll {
+                        with(animatedScrollScope) {
+                            snapToItem(40, 0) // teleport to item 40
+                            val distance = calculateDistanceTo(50).toFloat()
+                            var previousValue = 0f
+                            androidx.compose.animation.core.animate(
+                                0f,
+                                distance,
+                                animationSpec = tween(5_000)
+                            ) { currentValue, _ ->
+                                previousValue += scrollBy(currentValue - previousValue)
+                            }
+                        }
+                    }
+                }
+            }
+        ) {
+            Text("Scroll To Item 50")
+        }
+        LazyHorizontalGrid(
+            state = state,
+            rows = GridCells.Fixed(3),
+            modifier = Modifier.height(600.dp).fillMaxWidth()
+        ) {
+            items(itemsList) {
+                Box(Modifier.padding(2.dp).background(Color.Red).size(45.dp)) {
+                    Text(it.toString())
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt
index 5110d5c..3063491 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyStaggeredGridSamples.kt
@@ -17,30 +17,44 @@
 package androidx.compose.foundation.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
 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.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid
+import androidx.compose.foundation.lazy.staggeredgrid.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
 import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
 import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
 import androidx.compose.foundation.lazy.staggeredgrid.items
 import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
+import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
 
 @Sampled
 @Preview
@@ -146,3 +160,48 @@
         }
     }
 }
+
+@Sampled
+@Composable
+fun CustomLazyStaggeredGridAnimateToItemScrollSample() {
+    val itemsList = (0..100).toList()
+    val state = rememberLazyStaggeredGridState()
+    val scope = rememberCoroutineScope()
+    val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        Button(
+            onClick = {
+                scope.launch {
+                    state.scroll {
+                        with(animatedScrollScope) {
+                            snapToItem(40, 0) // teleport to item 40
+                            val distance = calculateDistanceTo(50).toFloat()
+                            var previousValue = 0f
+                            androidx.compose.animation.core.animate(
+                                0f,
+                                distance,
+                                animationSpec = tween(5_000)
+                            ) { currentValue, _ ->
+                                previousValue += scrollBy(currentValue - previousValue)
+                            }
+                        }
+                    }
+                }
+            }
+        ) {
+            Text("Scroll To Item 50")
+        }
+        LazyHorizontalStaggeredGrid(
+            state = state,
+            rows = StaggeredGridCells.Fixed(3),
+            modifier = Modifier.height(600.dp).fillMaxWidth()
+        ) {
+            items(itemsList) {
+                Box(Modifier.padding(2.dp).background(Color.Red).size(45.dp)) {
+                    Text(it.toString())
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
index a5deb22..ec737e2 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.animation.core.animate
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -28,7 +29,10 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.pager.PageSize
 import androidx.compose.foundation.pager.PagerState
 import androidx.compose.foundation.pager.VerticalPager
@@ -52,6 +56,7 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
@@ -340,3 +345,44 @@
             }
     }
 }
+
+@Preview
+@Sampled
+@Composable
+fun CustomPagerAnimateToPageScrollSample() {
+    val itemsList = (0..100).toList()
+    val state = rememberPagerState { itemsList.size }
+    val scope = rememberCoroutineScope()
+    val animatedScrollScope = remember(state) { LazyLayoutAnimateScrollScope(state) }
+
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        Button(
+            onClick = {
+                scope.launch {
+                    state.scroll {
+                        with(animatedScrollScope) {
+                            snapToItem(40, 0) // teleport to item 40
+                            val distance = calculateDistanceTo(50).toFloat()
+                            var previousValue = 0f
+                            androidx.compose.animation.core.animate(
+                                0f,
+                                distance,
+                                animationSpec = tween(5_000)
+                            ) { currentValue, _ ->
+                                previousValue += scrollBy(currentValue - previousValue)
+                            }
+                        }
+                    }
+                }
+            }
+        ) {
+            Text("Scroll To Item 50")
+        }
+
+        HorizontalPager(state) {
+            Box(Modifier.padding(2.dp).background(Color.Red).height(600.dp).fillMaxWidth()) {
+                Text(itemsList[it].toString())
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextIntrinsicWidthWrappingRegressionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextIntrinsicWidthWrappingRegressionTest.kt
new file mode 100644
index 0000000..b22e34f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextIntrinsicWidthWrappingRegressionTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.foundation.text
+
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Regression test for [b/346918500](https://issuetracker.google.com/346918500).
+ *
+ * This specific reproduction case would cause a visual overflow or line wrapping for start indices
+ * 5..7. We expect instead that there is no visual overflow nor line wrapping for any of these
+ * cases.
+ */
+@MediumTest
+@RunWith(Parameterized::class)
+class BasicTextIntrinsicWidthWrappingRegressionTest(spanStartIndex: Int) {
+    companion object {
+        private const val TEXT = "\u8AAD\u307F\u8FBC\u307F\u4E2D..."
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "spanStartIndex={0}")
+        fun data(): Array<Array<Any?>> {
+            return Array(TEXT.length + 1) { arrayOf(it) }
+        }
+    }
+
+    @get:Rule val rule = createComposeRule()
+
+    // These values are exact for the reproduction case (along with TEXT above).
+    private val densityScale = 2.625f
+    private val fontScale = 1f
+    private val fontSize = 16.sp
+    private val lineHeight = 20.sp
+
+    // This value is not exact for the reproduction case.
+    private val textColor = Color.Black
+
+    // These values are all derived from other values.
+    private val density = Density(densityScale, fontScale)
+    private val spanTextColor = textColor.copy(alpha = 0.3f)
+    private val spanStyle = SpanStyle(color = spanTextColor)
+    private val spanStyles = listOf(AnnotatedString.Range(spanStyle, spanStartIndex, TEXT.length))
+    private val annotatedString = AnnotatedString(TEXT, spanStyles)
+    private val style = TextStyle(color = textColor, fontSize = fontSize, lineHeight = lineHeight)
+
+    private fun runTest(softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE) {
+        lateinit var textLayout: TextLayoutResult
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides density) {
+                BasicText(
+                    text = annotatedString,
+                    style = style,
+                    softWrap = softWrap,
+                    maxLines = maxLines,
+                    onTextLayout = { textLayout = it }
+                )
+            }
+        }
+        assertThat(textLayout.hasVisualOverflow).isFalse()
+        assertThat(textLayout.lineCount).isEqualTo(1)
+    }
+
+    @Test fun whenSoftWrapFalse_lineCountIsOne() = runTest(softWrap = false)
+
+    @Test fun whenMaxLinesOne_lineCountIsOne() = runTest(maxLines = 1)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
index 803ecd5..9b02c77 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
@@ -17,11 +17,23 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastSumBy
 
-internal fun LazyLayoutAnimateScrollScope(state: LazyListState): LazyLayoutAnimateScrollScope {
+/**
+ * An implementation of [LazyLayoutAnimateScrollScope] that can be used with LazyLists. Please refer
+ * to the sample to learn how to use this API.
+ *
+ * @param state The [LazyListState] associated with the layout where this animated scroll should be
+ *   performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with [LazyRow] and
+ *   [LazyColumn].
+ * @sample androidx.compose.foundation.samples.CustomLazyListAnimateToItemScrollSample
+ */
+fun LazyLayoutAnimateScrollScope(state: LazyListState): LazyLayoutAnimateScrollScope {
 
     return object : LazyLayoutAnimateScrollScope {
         override val firstVisibleItemIndex: Int
@@ -36,8 +48,8 @@
         override val itemCount: Int
             get() = state.layoutInfo.totalItemsCount
 
-        override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
-            state.snapToItemIndexInternal(index, scrollOffset, forceRemeasure = true)
+        override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+            state.snapToItemIndexInternal(index, offset, forceRemeasure = true)
         }
 
         override fun calculateDistanceTo(targetIndex: Int, targetOffset: Int): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
index 73f286a..46353b5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
@@ -19,10 +19,20 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
 import androidx.compose.ui.util.fastFirstOrNull
 import kotlin.math.max
 
-internal fun LazyLayoutAnimateScrollScope(state: LazyGridState): LazyLayoutAnimateScrollScope {
+/**
+ * An implementation of [LazyLayoutAnimateScrollScope] that can be used with LazyGrids.
+ *
+ * @param state The [LazyGridState] associated with the layout where this animated scroll should be
+ *   performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with [LazyHorizontalGrid]
+ *   and [LazyVerticalGrid].
+ * @sample androidx.compose.foundation.samples.CustomLazyGridAnimateToItemScrollSample
+ */
+fun LazyLayoutAnimateScrollScope(state: LazyGridState): LazyLayoutAnimateScrollScope {
     return object : LazyLayoutAnimateScrollScope {
         override val firstVisibleItemIndex: Int
             get() = state.firstVisibleItemIndex
@@ -36,8 +46,8 @@
         override val itemCount: Int
             get() = state.layoutInfo.totalItemsCount
 
-        override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
-            state.snapToItemIndexInternal(index, scrollOffset, forceRemeasure = true)
+        override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+            state.snapToItemIndexInternal(index, offset, forceRemeasure = true)
         }
 
         override fun calculateDistanceTo(targetIndex: Int, targetOffset: Int): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
similarity index 89%
rename from compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt
rename to compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
index c66d785..03125f9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimateScroll.kt
@@ -46,8 +46,15 @@
 /**
  * A scope to allow customization of animated scroll in LazyLayouts. This scope contains all needed
  * information to perform an animatedScroll in a scrollable LazyLayout.
+ *
+ * For implementations for the most common layouts see:
+ *
+ * @see androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
+ * @see androidx.compose.foundation.lazy.staggeredgrid.LazyLayoutAnimateScrollScope
+ * @see androidx.compose.foundation.lazy.LazyLayoutAnimateScrollScope
+ * @see androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
  */
-internal interface LazyLayoutAnimateScrollScope {
+interface LazyLayoutAnimateScrollScope {
 
     /** The index of the first visible item in the lazy layout. */
     val firstVisibleItemIndex: Int
@@ -64,14 +71,24 @@
     /** The total item count. */
     val itemCount: Int
 
-    /** Immediately scroll to [index] and settle in [scrollOffset]. */
-    fun ScrollScope.snapToItem(index: Int, scrollOffset: Int)
+    /**
+     * Immediately scroll to [index] and settle in [offset].
+     *
+     * @param index The position index where we should immediately snap to.
+     * @param offset The offset where we should immediately snap to.
+     */
+    fun ScrollScope.snapToItem(index: Int, offset: Int = 0)
 
     /**
      * The "expected" distance to [targetIndex]. This means the "expected" offset of [targetIndex]
      * in the layout. In a LazyLayout, non-visible items don't have an actual offset, so this method
      * should return an approximation of the scroll offset to [targetIndex]. If [targetIndex] is
      * visible, then an "exact" offset should be provided.
+     *
+     * @param targetIndex The index position with respect to which this calculation should be done.
+     * @param targetOffset The offset with respect to which this calculation should be done.
+     * @return The expected distance to scroll so [targetIndex] is the firstVisibleItemIndex with
+     *   [targetOffset] as the firstVisibleItemScrollOffset.
      */
     fun calculateDistanceTo(targetIndex: Int, targetOffset: Int = 0): Int
 }
@@ -196,10 +213,7 @@
                                 ) {
                                     // Teleport
                                     debugLog { "Teleport forward" }
-                                    snapToItem(
-                                        index = index - numOfItemsForTeleport,
-                                        scrollOffset = 0
-                                    )
+                                    snapToItem(index = index - numOfItemsForTeleport, offset = 0)
                                 }
                             } else {
                                 if (
@@ -208,10 +222,7 @@
                                 ) {
                                     // Teleport
                                     debugLog { "Teleport backward" }
-                                    snapToItem(
-                                        index = index + numOfItemsForTeleport,
-                                        scrollOffset = 0
-                                    )
+                                    snapToItem(index = index + numOfItemsForTeleport, offset = 0)
                                 }
                             }
                         }
@@ -225,7 +236,7 @@
                                 "item $firstVisibleItemIndex at  $firstVisibleItemScrollOffset," +
                                 " target is $scrollOffset"
                         }
-                        snapToItem(index = index, scrollOffset = scrollOffset)
+                        snapToItem(index = index, offset = scrollOffset)
                         loop = false
                         cancelAnimation()
                         return@animateTo
@@ -277,7 +288,7 @@
             // rounding error (otherwise we tend to end up with the previous item scrolled the
             // tiniest bit onscreen)
             // TODO: prevent temporarily scrolling *past* the item
-            snapToItem(index = index, scrollOffset = scrollOffset)
+            snapToItem(index = index, offset = scrollOffset)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
index 036dce0..88978cc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
@@ -18,13 +18,22 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.lazy.grid.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
+import androidx.compose.foundation.pager.LazyLayoutAnimateScrollScope
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastSumBy
 
-internal fun LazyLayoutAnimateScrollScope(
-    state: LazyStaggeredGridState
-): LazyLayoutAnimateScrollScope {
+/**
+ * An implementation of [LazyLayoutAnimateScrollScope] that can be used with LazyStaggeredGrids.
+ *
+ * @param state The [LazyStaggeredGridState] associated with the layout where this animated scroll
+ *   should be performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with
+ *   [LazyHorizontalStaggeredGrid] and [LazyVerticalStaggeredGrid].
+ * @sample androidx.compose.foundation.samples.CustomLazyStaggeredGridAnimateToItemScrollSample
+ */
+fun LazyLayoutAnimateScrollScope(state: LazyStaggeredGridState): LazyLayoutAnimateScrollScope {
 
     return object : LazyLayoutAnimateScrollScope {
         override val firstVisibleItemIndex: Int
@@ -39,8 +48,8 @@
         override val itemCount: Int
             get() = state.layoutInfo.totalItemsCount
 
-        override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
-            with(state) { snapToItemInternal(index, scrollOffset, forceRemeasure = true) }
+        override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+            with(state) { snapToItemInternal(index, offset, forceRemeasure = true) }
         }
 
         override fun calculateDistanceTo(targetIndex: Int, targetOffset: Int): Int {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
index 2177800..695d831 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
@@ -24,8 +24,14 @@
  * A [LazyLayoutAnimateScrollScope] that allows customization of animated scroll in [Pager]. The
  * scope contains information about the layout where animated scroll can be performed as well as the
  * necessary tools to do that respecting the scroll mutation priority.
+ *
+ * @param state The [PagerState] associated with the layout where this animated scroll should be
+ *   performed.
+ * @return An implementation of [LazyLayoutAnimateScrollScope] that works with [HorizontalPager] and
+ *   [VerticalPager].
+ * @sample androidx.compose.foundation.samples.CustomPagerAnimateToPageScrollSample
  */
-internal fun LazyLayoutAnimateScrollScope(state: PagerState): LazyLayoutAnimateScrollScope {
+fun LazyLayoutAnimateScrollScope(state: PagerState): LazyLayoutAnimateScrollScope {
     return object : LazyLayoutAnimateScrollScope {
 
         override val firstVisibleItemIndex: Int
@@ -40,8 +46,8 @@
         override val itemCount: Int
             get() = state.pageCount
 
-        override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
-            val offsetFraction = scrollOffset / state.pageSizeWithSpacing.toFloat()
+        override fun ScrollScope.snapToItem(index: Int, offset: Int) {
+            val offsetFraction = offset / state.pageSizeWithSpacing.toFloat()
             state.snapToItem(index, offsetFraction, forceRemeasure = true)
         }
 
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 3b446ea..a550d824 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -70,7 +70,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
 
                 // TODO: remove next 3 dependencies when b/202810604 is fixed
                 implementation("androidx.savedstate:savedstate:1.2.1")
diff --git a/compose/material3/adaptive/adaptive-layout/build.gradle b/compose/material3/adaptive/adaptive-layout/build.gradle
index 987896ee..bc2a2d1 100644
--- a/compose/material3/adaptive/adaptive-layout/build.gradle
+++ b/compose/material3/adaptive/adaptive-layout/build.gradle
@@ -68,7 +68,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
diff --git a/compose/material3/adaptive/adaptive-navigation/build.gradle b/compose/material3/adaptive/adaptive-navigation/build.gradle
index e6f2983..9c75325 100644
--- a/compose/material3/adaptive/adaptive-navigation/build.gradle
+++ b/compose/material3/adaptive/adaptive-navigation/build.gradle
@@ -62,7 +62,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 implementation("androidx.activity:activity-compose:1.8.2")
             }
         }
diff --git a/compose/material3/adaptive/adaptive/build.gradle b/compose/material3/adaptive/adaptive/build.gradle
index 2e28d64..2289202 100644
--- a/compose/material3/adaptive/adaptive/build.gradle
+++ b/compose/material3/adaptive/adaptive/build.gradle
@@ -62,7 +62,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 api("androidx.window:window:1.3.0-rc01")
             }
         }
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 35ca4949..83a6e86 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -63,7 +63,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index 1f443e0..e415e38 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -61,7 +61,7 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 implementation("androidx.window:window:1.0.0")
             }
         }
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index ba89d47..deda2bc 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1645,14 +1645,20 @@
   }
 
   public final class ShapeDefaults {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
     method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
     method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraExtraLarge;
     property public final androidx.compose.foundation.shape.CornerBasedShape ExtraLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraLargeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape ExtraSmall;
     property public final androidx.compose.foundation.shape.CornerBasedShape Large;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape LargeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape Medium;
     property public final androidx.compose.foundation.shape.CornerBasedShape Small;
     field public static final androidx.compose.material3.ShapeDefaults INSTANCE;
@@ -1660,15 +1666,23 @@
 
   @androidx.compose.runtime.Immutable public final class Shapes {
     ctor public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
     method public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
     method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
     method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge;
     property public final androidx.compose.foundation.shape.CornerBasedShape extraLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape extraSmall;
     property public final androidx.compose.foundation.shape.CornerBasedShape large;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape largeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape medium;
     property public final androidx.compose.foundation.shape.CornerBasedShape small;
   }
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index ba89d47..deda2bc 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1645,14 +1645,20 @@
   }
 
   public final class ShapeDefaults {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
     method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
     method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraExtraLarge;
     property public final androidx.compose.foundation.shape.CornerBasedShape ExtraLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape ExtraLargeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape ExtraSmall;
     property public final androidx.compose.foundation.shape.CornerBasedShape Large;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape LargeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape Medium;
     property public final androidx.compose.foundation.shape.CornerBasedShape Small;
     field public static final androidx.compose.material3.ShapeDefaults INSTANCE;
@@ -1660,15 +1666,23 @@
 
   @androidx.compose.runtime.Immutable public final class Shapes {
     ctor public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    ctor @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
     method public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge, optional androidx.compose.foundation.shape.CornerBasedShape largeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased, optional androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraExtraLarge();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getExtraLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
     method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public androidx.compose.foundation.shape.CornerBasedShape getLargeIncreased();
     method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
     method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraExtraLarge;
     property public final androidx.compose.foundation.shape.CornerBasedShape extraLarge;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape extraLargeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape extraSmall;
     property public final androidx.compose.foundation.shape.CornerBasedShape large;
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3ExpressiveApi public final androidx.compose.foundation.shape.CornerBasedShape largeIncreased;
     property public final androidx.compose.foundation.shape.CornerBasedShape medium;
     property public final androidx.compose.foundation.shape.CornerBasedShape small;
   }
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 07129e1..4b4ba3e 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -70,7 +70,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 implementation("androidx.activity:activity-compose:1.8.2")
 
                 implementation("androidx.lifecycle:lifecycle-common-java8:2.6.1")
@@ -132,4 +132,4 @@
     sourceSets.androidTest.assets.srcDirs +=
             project.rootDir.absolutePath + "/../../golden/compose/material3/material3"
     namespace "androidx.compose.material3"
-}
\ No newline at end of file
+}
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
index b34e08d..59a1689 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/ShapeDemos.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -32,6 +33,7 @@
 import androidx.compose.ui.unit.dp
 
 @Composable
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 fun ShapeDemo() {
     val shapes = MaterialTheme.shapes
     Column(
@@ -48,8 +50,14 @@
         Spacer(modifier = Modifier.height(16.dp))
         Button(onClick = {}, shape = shapes.large) { Text("Large") }
         Spacer(modifier = Modifier.height(16.dp))
+        Button(onClick = {}, shape = shapes.largeIncreased) { Text("Large Increased") }
+        Spacer(modifier = Modifier.height(16.dp))
         Button(onClick = {}, shape = shapes.extraLarge) { Text("Extra Large") }
         Spacer(modifier = Modifier.height(16.dp))
+        Button(onClick = {}, shape = shapes.extraLargeIncreased) { Text("Extra Large Increased") }
+        Spacer(modifier = Modifier.height(16.dp))
+        Button(onClick = {}, shape = shapes.extraExtraLarge) { Text("Extra Extra Large") }
+        Spacer(modifier = Modifier.height(16.dp))
         Button(onClick = {}, shape = CircleShape) { Text("Full") }
     }
 }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt
index 5000da1..d8a1f0f 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ShapesScreenshotTest.kt
@@ -44,6 +44,7 @@
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 class ShapesScreenshotTest {
 
     @get:Rule val rule = createComposeRule()
@@ -69,8 +70,18 @@
                     Spacer(modifier = Modifier.height(16.dp))
                     Button(onClick = {}, shape = shapes.large) { Text("Large") }
                     Spacer(modifier = Modifier.height(16.dp))
+                    Button(onClick = {}, shape = shapes.largeIncreased) { Text("Large Increased") }
+                    Spacer(modifier = Modifier.height(16.dp))
                     Button(onClick = {}, shape = shapes.extraLarge) { Text("Extra Large") }
                     Spacer(modifier = Modifier.height(16.dp))
+                    Button(onClick = {}, shape = shapes.extraLargeIncreased) {
+                        Text("Extra Large Increased")
+                    }
+                    Spacer(modifier = Modifier.height(16.dp))
+                    Button(onClick = {}, shape = shapes.extraExtraLarge) {
+                        Text("Extra Extra Large")
+                    }
+                    Spacer(modifier = Modifier.height(16.dp))
                     Button(onClick = {}, shape = CircleShape) { Text("Full") }
                 }
             }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
index cd9c254..ed14759 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.CornerBasedShape
 import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.tokens.ShapeKeyTokens
 import androidx.compose.material3.tokens.ShapeTokens
 import androidx.compose.runtime.Composable
@@ -40,8 +41,9 @@
  * - Extra Small
  * - Small
  * - Medium
- * - Large
- * - Extra Large
+ * - Large, Large Increased
+ * - Extra Large, Extra Large Increased
+ * - Extra Extra Large
  *
  * You can customize the shape system for all components in the [MaterialTheme] or you can do it on
  * a per component basis.
@@ -66,16 +68,138 @@
  *   use this shape.
  * @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
  *   [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
+ * @param largeIncreased A shape style with 4 same-sized corners whose size are bigger than
+ *   [Shapes.medium] and smaller than [Shapes.extraLarge]. Slightly larger variant to
+ *   [Shapes.large].
+ * @param extraLargeIncreased A shape style with 4 same-sized corners whose size are bigger than
+ *   [Shapes.large] and smaller than [Shapes.extraExtraLarge]. Slightly larger variant to
+ *   [Shapes.extraLarge].
+ * @param extraExtraLarge A shape style with 4 same-sized corners whose size are bigger than
+ *   [Shapes.extraLarge] and smaller than [CircleShape].
  */
+// TODO: Update new shape descriptions to list what components leverage them by default.
 @Immutable
-class Shapes(
+class Shapes
+@ExperimentalMaterial3ExpressiveApi
+constructor(
     // Shapes None and Full are omitted as None is a RectangleShape and Full is a CircleShape.
     val extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
     val small: CornerBasedShape = ShapeDefaults.Small,
     val medium: CornerBasedShape = ShapeDefaults.Medium,
     val large: CornerBasedShape = ShapeDefaults.Large,
     val extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge,
+    largeIncreased: CornerBasedShape = ShapeDefaults.LargeIncreased,
+    extraLargeIncreased: CornerBasedShape = ShapeDefaults.ExtraLargeIncreased,
+    extraExtraLarge: CornerBasedShape = ShapeDefaults.ExtraExtraLarge,
 ) {
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /**
+     * A shape style with 4 same-sized corners whose size are bigger than [Shapes.medium] and
+     * smaller than [Shapes.extraLarge]. Slightly larger variant to [Shapes.large].
+     */
+    val largeIncreased = largeIncreased
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /**
+     * A shape style with 4 same-sized corners whose size are bigger than [Shapes.large] and smaller
+     * than [Shapes.extraExtraLarge]. Slightly larger variant to [Shapes.extraLarge].
+     */
+    val extraLargeIncreased = extraLargeIncreased
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /**
+     * A shape style with 4 same-sized corners whose size are bigger than [Shapes.extraLarge] and
+     * smaller than [CircleShape].
+     */
+    val extraExtraLarge = extraExtraLarge
+
+    /**
+     * Material surfaces can be displayed in different shapes. Shapes direct attention, identify
+     * components, communicate state, and express brand.
+     *
+     * The shape scale defines the style of container corners, offering a range of roundedness from
+     * square to fully circular.
+     *
+     * There are different sizes of shapes:
+     * - Extra Small
+     * - Small
+     * - Medium
+     * - Large, Large Increased
+     * - Extra Large, Extra Large Increased
+     * - Extra Extra Large
+     *
+     * You can customize the shape system for all components in the [MaterialTheme] or you can do it
+     * on a per component basis.
+     *
+     * You can change the shape that a component has by overriding the shape parameter for that
+     * component. For example, by default, buttons use the shape style “full.” If your product
+     * requires a smaller amount of roundedness, you can override the shape parameter with a
+     * different shape value like [MaterialTheme.shapes.small].
+     *
+     * To learn more about shapes, see
+     * [Material Design shapes](https://m3.material.io/styles/shape/overview).
+     *
+     * @param extraSmall A shape style with 4 same-sized corners whose size are bigger than
+     *   [RectangleShape] and smaller than [Shapes.small]. By default autocomplete menu, select
+     *   menu, snackbars, standard menu, and text fields use this shape.
+     * @param small A shape style with 4 same-sized corners whose size are bigger than
+     *   [Shapes.extraSmall] and smaller than [Shapes.medium]. By default chips use this shape.
+     * @param medium A shape style with 4 same-sized corners whose size are bigger than
+     *   [Shapes.small] and smaller than [Shapes.large]. By default cards and small FABs use this
+     *   shape.
+     * @param large A shape style with 4 same-sized corners whose size are bigger than
+     *   [Shapes.medium] and smaller than [Shapes.extraLarge]. By default extended FABs, FABs, and
+     *   navigation drawers use this shape.
+     * @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
+     *   [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
+     */
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    constructor(
+        extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
+        small: CornerBasedShape = ShapeDefaults.Small,
+        medium: CornerBasedShape = ShapeDefaults.Medium,
+        large: CornerBasedShape = ShapeDefaults.Large,
+        extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge,
+    ) : this(
+        extraSmall = extraSmall,
+        small = small,
+        medium = medium,
+        large = large,
+        extraLarge = extraLarge,
+        largeIncreased = ShapeDefaults.LargeIncreased,
+        extraLargeIncreased = ShapeDefaults.ExtraLargeIncreased,
+        extraExtraLarge = ShapeDefaults.ExtraExtraLarge,
+    )
+
+    /** Returns a copy of this Shapes, optionally overriding some of the values. */
+    @ExperimentalMaterial3ExpressiveApi
+    fun copy(
+        extraSmall: CornerBasedShape = this.extraSmall,
+        small: CornerBasedShape = this.small,
+        medium: CornerBasedShape = this.medium,
+        large: CornerBasedShape = this.large,
+        extraLarge: CornerBasedShape = this.extraLarge,
+        largeIncreased: CornerBasedShape = this.largeIncreased,
+        extraLargeIncreased: CornerBasedShape = this.extraLargeIncreased,
+        extraExtraLarge: CornerBasedShape = this.extraExtraLarge,
+    ): Shapes =
+        Shapes(
+            extraSmall = extraSmall,
+            small = small,
+            medium = medium,
+            large = large,
+            extraLarge = extraLarge,
+            largeIncreased = largeIncreased,
+            extraLargeIncreased = extraLargeIncreased,
+            extraExtraLarge = extraExtraLarge,
+        )
+
     /** Returns a copy of this Shapes, optionally overriding some of the values. */
     fun copy(
         extraSmall: CornerBasedShape = this.extraSmall,
@@ -92,6 +216,7 @@
             extraLarge = extraLarge,
         )
 
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is Shapes) return false
@@ -100,25 +225,36 @@
         if (medium != other.medium) return false
         if (large != other.large) return false
         if (extraLarge != other.extraLarge) return false
+        if (largeIncreased != other.largeIncreased) return false
+        if (extraLargeIncreased != other.extraLargeIncreased) return false
+        if (extraExtraLarge != other.extraExtraLarge) return false
         return true
     }
 
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     override fun hashCode(): Int {
         var result = extraSmall.hashCode()
         result = 31 * result + small.hashCode()
         result = 31 * result + medium.hashCode()
         result = 31 * result + large.hashCode()
         result = 31 * result + extraLarge.hashCode()
+        result = 31 * result + largeIncreased.hashCode()
+        result = 31 * result + extraLargeIncreased.hashCode()
+        result = 31 * result + extraExtraLarge.hashCode()
         return result
     }
 
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
     override fun toString(): String {
         return "Shapes(" +
             "extraSmall=$extraSmall, " +
             "small=$small, " +
             "medium=$medium, " +
             "large=$large, " +
-            "extraLarge=$extraLarge)"
+            "largeIncreased=$largeIncreased, " +
+            "extraLarge=$extraLarge, " +
+            "extralargeIncreased=$extraLargeIncreased, " +
+            "extraExtraLarge=$extraExtraLarge)"
     }
 }
 
@@ -136,38 +272,96 @@
     /** Large sized corner shape */
     val Large: CornerBasedShape = ShapeTokens.CornerLarge
 
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Large sized corner shape, slightly larger than [Large] */
+    val LargeIncreased: CornerBasedShape = RoundedCornerShape(16.dp)
+
     /** Extra large sized corner shape */
     val ExtraLarge: CornerBasedShape = ShapeTokens.CornerExtraLarge
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** Extra large sized corner shape, slightly larger than [ExtraLarge] */
+    val ExtraLargeIncreased: CornerBasedShape = RoundedCornerShape(32.dp)
+
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalMaterial3ExpressiveApi
+    @ExperimentalMaterial3ExpressiveApi
+    /** An extra extra large (XXL) sized corner shape */
+    val ExtraExtraLarge: CornerBasedShape = RoundedCornerShape(48.dp)
+
+    /** A non-rounded corner size */
+    internal val CornerNone: CornerSize = CornerSize(0.dp)
+
+    /** An extra small rounded corner size */
+    internal val CornerExtraSmall: CornerSize = CornerSize(4.dp)
+
+    /** A small rounded corner size */
+    internal val CornerSmall: CornerSize = CornerSize(8.dp)
+
+    /** A medium rounded corner size */
+    internal val CornerMedium: CornerSize = CornerSize(12.dp)
+
+    /** A large rounded corner size */
+    internal val CornerLarge: CornerSize = CornerSize(16.dp)
+
+    /** A large rounded corner size, slightly larger than [CornerLarge] */
+    internal val CornerLargeIncreased: CornerSize = CornerSize(20.dp)
+
+    /** An extra large rounded corner size */
+    internal val CornerExtraLarge: CornerSize = CornerSize(28.dp)
+
+    /** An extra large rounded corner size, slightly larger than [CornerExtraLarge] */
+    internal val CornerExtraLargeIncreased: CornerSize = CornerSize(32.dp)
+
+    /** An extra extra large (XXL) rounded corner size */
+    internal val CornerExtraExtraLarge: CornerSize = CornerSize(48.dp)
+
+    /** A fully rounded corner size */
+    internal val CornerFull: CornerSize = CornerSize(100)
 }
 
 /** Helper function for component shape tokens. Used to grab the top values of a shape parameter. */
-internal fun CornerBasedShape.top(): CornerBasedShape {
-    return copy(bottomStart = CornerSize(0.0.dp), bottomEnd = CornerSize(0.0.dp))
+internal fun CornerBasedShape.top(
+    bottomSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+    return copy(bottomStart = bottomSize, bottomEnd = bottomSize)
 }
 
 /**
  * Helper function for component shape tokens. Used to grab the bottom values of a shape parameter.
  */
-internal fun CornerBasedShape.bottom(): CornerBasedShape {
-    return copy(topStart = CornerSize(0.0.dp), topEnd = CornerSize(0.0.dp))
+internal fun CornerBasedShape.bottom(
+    topSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+    return copy(topStart = topSize, topEnd = topSize)
 }
 
 /**
  * Helper function for component shape tokens. Used to grab the start values of a shape parameter.
  */
-internal fun CornerBasedShape.start(): CornerBasedShape {
-    return copy(topEnd = CornerSize(0.0.dp), bottomEnd = CornerSize(0.0.dp))
+internal fun CornerBasedShape.start(
+    endSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+    return copy(topEnd = endSize, bottomEnd = endSize)
 }
 
 /** Helper function for component shape tokens. Used to grab the end values of a shape parameter. */
-internal fun CornerBasedShape.end(): CornerBasedShape {
-    return copy(topStart = CornerSize(0.0.dp), bottomStart = CornerSize(0.0.dp))
+internal fun CornerBasedShape.end(
+    startSize: CornerSize = ShapeDefaults.CornerNone
+): CornerBasedShape {
+    return copy(topStart = startSize, bottomStart = startSize)
 }
 
 /**
  * Helper function for component shape tokens. Here is an example on how to use component color
  * tokens: ``MaterialTheme.shapes.fromToken(FabPrimarySmallTokens.ContainerShape)``
  */
+// TODO: When tokens are available, add mappings for largeIncreased, extraLargeIncreased,
+// extraExtraLarge
 internal fun Shapes.fromToken(value: ShapeKeyTokens): Shape {
     return when (value) {
         ShapeKeyTokens.CornerExtraLarge -> extraLarge
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
index feb749e..1eb3f62 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SplitButton.kt
@@ -34,6 +34,7 @@
 import androidx.compose.material3.SplitButtonDefaults.InnerCornerRadiusPercentage
 import androidx.compose.material3.SplitButtonDefaults.LeadingButtonShape
 import androidx.compose.material3.internal.ProvideContentColorTextStyle
+import androidx.compose.material3.tokens.SplitButtonSmallTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
@@ -477,7 +478,7 @@
 @ExperimentalMaterial3ExpressiveApi
 object SplitButtonDefaults {
     /** Default spacing between the `leading` and `trailing` button */
-    val Spacing = 2.dp
+    val Spacing = SplitButtonSmallTokens.BetweenSpace
 
     /**
      * Default corner radius percentage for the inner corners, a.k.a. leading button `end` corners
@@ -505,7 +506,7 @@
      * Default minimum height of the split button. This applies to both [LeadingButton] and
      * [TrailingButton]. Applies to all 4 variants of the split button
      */
-    private val MinHeight = ButtonDefaults.MinHeight
+    private val MinHeight = SplitButtonSmallTokens.ContainerHeight
 
     /** Default minimum width of the [TrailingButton]. */
     private val TrailingButtonMinWidth = LeadingButtonMinWidth
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
similarity index 63%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
index 9df4b05..749a2aa 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SplitButtonSmallTokens.kt
@@ -14,10 +14,16 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+// VERSION: v0_5_0
+// GENERATED CODE - DO NOT MODIFY BY HAND
 
-actual class NativePaint
+package androidx.compose.material3.tokens
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
+import androidx.compose.ui.unit.dp
 
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal object SplitButtonSmallTokens {
+    val BetweenSpace = 2.0.dp
+    val ContainerHeight = 40.0.dp
+    val ContainerShape = ShapeKeyTokens.CornerFull
+    val InnerCornerShape = ShapeTokens.CornerExtraSmall
+}
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index aee37a5..9618c50 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -88,7 +88,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api(libs.kotlinCoroutinesAndroid)
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index e988544..49a573a 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -34,6 +34,7 @@
 androidXMultiplatform {
     android()
     jvmStubs()
+    linuxX64Stubs()
 
     defaultPlatform(PlatformIdentifier.ANDROID)
 
@@ -65,10 +66,17 @@
             }
         }
 
+        commonStubsMain {
+            dependsOn(commonMain)
+        }
+
         jvmStubsMain {
             dependsOn(jvmMain)
-            dependencies {
-            }
+            dependsOn(commonStubsMain)
+        }
+
+        linuxx64StubsMain {
+            dependsOn(commonStubsMain)
         }
 
         androidInstrumentedTest {
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index a8bf89b..d761f6d 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -34,6 +34,7 @@
 androidXMultiplatform {
     android()
     jvmStubs()
+    linuxX64Stubs()
 
     defaultPlatform(PlatformIdentifier.ANDROID)
 
@@ -56,28 +57,26 @@
             }
         }
 
-        jvmMain {
-            dependsOn(commonMain)
-            dependencies {
-            }
-        }
-
         androidMain {
-            dependsOn(jvmMain)
+            dependsOn(commonMain)
             dependencies {
                 // This has stub APIs for access to legacy Android APIs, so we don't want
                 // any dependency on this module.
                 compileOnly(project(":compose:ui:ui-android-stubs"))
                 implementation("androidx.graphics:graphics-path:1.0.1")
                 implementation libs.androidx.core
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
+        commonStubsMain {
+            dependsOn(commonMain)
+        }
         jvmStubsMain {
-            dependsOn(jvmMain)
-            dependencies {
-            }
+            dependsOn(commonStubsMain)
+        }
+        linuxx64StubsMain {
+            dependsOn(commonStubsMain)
         }
 
         androidInstrumentedTest {
diff --git a/compose/ui/ui-graphics/src/jvmMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.jvm.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.android.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.jvm.kt
rename to compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.android.kt
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
index 0904ef1..dd5fd27 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.util.fastMaxOf
 import androidx.compose.ui.util.fastMinOf
 import androidx.compose.ui.util.lerp
+import kotlin.math.PI
 import kotlin.math.abs
 import kotlin.math.acos
 import kotlin.math.cos
@@ -31,7 +32,7 @@
 import kotlin.math.sign
 import kotlin.math.sqrt
 
-private const val Tau = Math.PI * 2.0
+private const val Tau = PI * 2.0
 private const val Epsilon = 1e-7
 // We use a fairly high epsilon here because it's post double->float conversion
 // and because we use a fast approximation of cbrt(). The epsilon we use here is
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
index 1c87aef..24e2318 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float16.kt
@@ -16,6 +16,7 @@
 package androidx.compose.ui.graphics
 
 import androidx.compose.ui.util.floatFromBits
+import kotlin.jvm.JvmInline
 
 /**
  * The `Float16` class is a wrapper and a utility class to manipulate half-precision 16-bit
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/BlendMode.commonStubs.kt
similarity index 89%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/BlendMode.commonStubs.kt
index 9df4b05..2cba823 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/BlendMode.commonStubs.kt
@@ -16,8 +16,4 @@
 
 package androidx.compose.ui.graphics
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
 actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Canvas.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ColorFilter.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/NotImplemented.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Paint.commonStubs.kt
similarity index 90%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Paint.commonStubs.kt
index 9df4b05..92ef826 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Paint.commonStubs.kt
@@ -19,5 +19,3 @@
 actual class NativePaint
 
 actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Path.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Path.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Path.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Path.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathEffect.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathIterator.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/PathMeasure.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/RenderEffect.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Shader.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Shader.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Shader.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/Shader.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/TileMode.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.commonStubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.commonStubs.kt
index 9df4b05..4ea4274 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/internal/JvmDefaultWithCompatibility.commonStubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.graphics.internal
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual annotation class JvmDefaultWithCompatibility
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.jvmStubs.kt b/compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.jvmStubs.kt
rename to compose/ui/ui-graphics/src/commonStubsMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
index f5a0efe..11d18a0 100644
--- a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
+++ b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
@@ -18,6 +18,7 @@
 
 import kotlin.math.abs
 import kotlin.test.Test
+import kotlin.test.assertTrue
 
 class FastFloatParserTest {
     @Test
@@ -30,15 +31,16 @@
 
             val nodes = parser.parsePathString("H$number").toNodes()
 
-            assert(nodes.isNotEmpty()) { line }
+            assertTrue(nodes.isNotEmpty(), line)
 
             val x = (nodes[0] as PathNode.HorizontalTo).x
 
-            assert(abs(x.toBits() - bits) < 2) {
+            assertTrue(
+                abs(x.toBits() - bits) < 2,
                 "Expected: 0x$bits\n" +
                     "Actual:   0x${x.toBits()}\n" +
                     "    in $line (toFloat() = ${number.toFloat()})"
-            }
+            )
         }
     }
 }
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
index e05db87..17ea237 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
@@ -21,13 +21,20 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.padding
 import androidx.compose.material.Text
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.runEmptyComposeUiTest
+import androidx.compose.ui.unit.dp
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.commit
 import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.fragment.app.testing.withFragment
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import org.junit.Test
@@ -37,13 +44,37 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalTestApi::class)
 class ComposeInFragmentTest {
-    @Test
-    fun test() = runEmptyComposeUiTest {
-        launchFragmentInContainer<CustomFragment>()
-        onNodeWithText("Hello Compose").assertExists()
+    companion object {
+        const val fragment1Text = "Compose in fragment 1"
+        const val fragment2Text = "Compose in fragment 2"
     }
 
-    class CustomFragment : Fragment() {
+    @Test
+    fun test() {
+        runEmptyComposeUiTest {
+            val fragment1 = Fragment1()
+            val fragment2 = Fragment2()
+
+            // Launch fragment 1
+            val fragmentScenario = launchFragmentInContainer<Fragment1> { fragment1 }
+            onNodeWithText(fragment1Text).assertExists()
+            onNodeWithText(fragment2Text).assertDoesNotExist()
+
+            // Add fragment 2
+            fragmentScenario.withFragment {
+                parentFragmentManager.commit { add(android.R.id.content, fragment2) }
+            }
+            onNodeWithText(fragment1Text).assertExists()
+            onNodeWithText(fragment2Text).assertExists()
+
+            // Remove fragment 1
+            fragmentScenario.withFragment { parentFragmentManager.commit { remove(fragment1) } }
+            onNodeWithText(fragment1Text).assertDoesNotExist()
+            onNodeWithText(fragment2Text).assertExists()
+        }
+    }
+
+    class Fragment1 : Fragment() {
         override fun onCreateView(
             inflater: LayoutInflater,
             container: ViewGroup?,
@@ -52,12 +83,28 @@
             return container?.let {
                 ComposeView(container.context).apply {
                     layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+                    setContent {
+                        Text(fragment1Text, Modifier.background(Color.White).padding(10.dp))
+                    }
                 }
             }
         }
+    }
 
-        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-            (view as ComposeView).setContent { Text("Hello Compose") }
+    class Fragment2 : Fragment() {
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ): View? {
+            return container?.let {
+                ComposeView(container.context).apply {
+                    layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+                    setContent {
+                        Text(fragment2Text, Modifier.background(Color.White).padding(10.dp))
+                    }
+                }
+            }
         }
     }
 }
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index b843707..5c4c82a 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -34,6 +34,7 @@
 androidXMultiplatform {
     android()
     jvmStubs()
+    linuxX64Stubs()
 
     defaultPlatform(PlatformIdentifier.ANDROID)
 
@@ -48,7 +49,7 @@
 
                 // when updating the runtime version please also update the runtime-saveable version
                 implementation(project(":compose:runtime:runtime"))
-                implementation("androidx.compose.runtime:runtime-saveable:1.6.0")
+                implementation(project(":compose:runtime:runtime-saveable"))
 
                 implementation(project(":compose:ui:ui-util"))
             }
@@ -72,7 +73,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 implementation("androidx.core:core:1.7.0")
                 implementation("androidx.emoji2:emoji2:1.4.0")
                 implementation("androidx.collection:collection:1.4.0")
@@ -83,6 +84,10 @@
             dependsOn(jvmMain)
         }
 
+        linuxx64StubsMain {
+            dependsOn(commonMain)
+        }
+
         androidInstrumentedTest {
             dependsOn(commonTest)
             dependencies {
diff --git a/compose/ui/ui-text/src/commonTest/kotlin/androidx/compose/ui/text/SaversTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt
similarity index 100%
rename from compose/ui/ui-text/src/commonTest/kotlin/androidx/compose/ui/text/SaversTest.kt
rename to compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
index 66e075a..380f9d4 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.util.fastMap
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
+import kotlin.jvm.JvmName
 
 /**
  * The basic data structure of text with multiple styles. To construct an [AnnotatedString] you can
@@ -464,7 +465,7 @@
          * copied and any other information held in the sequence, such as Android `Span`s, will be
          * dropped.
          */
-        @Suppress("BuilderSetStyle")
+        @Suppress("BuilderSetStyle", "PARAMETER_NAME_CHANGED_ON_OVERRIDE")
         override fun append(text: CharSequence?): Builder {
             if (text is AnnotatedString) {
                 append(text)
@@ -487,7 +488,7 @@
          * @param start The index of the first character in [text] to copy over (inclusive).
          * @param end The index after the last character in [text] to copy over (exclusive).
          */
-        @Suppress("BuilderSetStyle")
+        @Suppress("BuilderSetStyle", "PARAMETER_NAME_CHANGED_ON_OVERRIDE")
         override fun append(text: CharSequence?, start: Int, end: Int): Builder {
             if (text is AnnotatedString) {
                 append(text, start, end)
@@ -498,6 +499,7 @@
         }
 
         // Kdoc comes from interface method.
+        @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
         override fun append(char: Char): Builder {
             this.text.append(char)
             return this
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
index b8e0abc..afeeab0 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.isSpecified
 import androidx.compose.ui.unit.isUnspecified
+import kotlin.jvm.JvmName
 
 private val DefaultLineHeight = TextUnit.Unspecified
 
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt
index 4c293b1..0ba312c 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/StringAnnotation.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.text
 
+import kotlin.jvm.JvmInline
+
 /**
  * An [AnnotatedString.Annotation] class which holds a String [value].
  *
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt
index 169a02e..9d0cc84 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextGranularity.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.text
 
+import kotlin.jvm.JvmInline
+
 /**
  * Used by [Paragraph.getRangeForRect]. It specifies the minimal unit of the text ranges that is
  * considered by the [Paragraph.getRangeForRect].
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
index 527f81a..6461877 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
@@ -40,6 +40,7 @@
 import androidx.compose.ui.text.style.TextMotion
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.TextUnit
+import kotlin.jvm.JvmName
 
 /**
  * Styling configuration for a `Text`.
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt
index 6b968f6..1275d48 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/GapBuffer.kt
@@ -186,8 +186,8 @@
      * @param builder The output string builder
      */
     fun append(builder: StringBuilder) {
-        builder.append(buffer, 0, gapStart)
-        builder.append(buffer, gapEnd, capacity - gapEnd)
+        builder.appendRange(buffer, 0, gapStart)
+        builder.appendRange(buffer, gapEnd, capacity)
     }
 
     /**
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
index f189eea..7ef0236 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.text.style
 
+import kotlin.jvm.JvmInline
+
 /**
  * Automatic hyphenation configuration.
  *
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt
index 5ad11e9..4e60d4b 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineHeightStyle.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.text.style
 
 import androidx.compose.ui.text.PlatformParagraphStyle
+import kotlin.jvm.JvmInline
 
 /**
  * The configuration for line height such as alignment of the line in the provided line height,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt
index 47b3aa7..9cd6708 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextForegroundStyle.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.graphics.lerp as lerpColor
 import androidx.compose.ui.text.lerpDiscrete
 import androidx.compose.ui.util.lerp
+import kotlin.jvm.JvmName
 
 /**
  * An internal interface to represent possible ways to draw Text e.g. color, brush. This interface
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
index 719a93f..9fac85a 100644
--- a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
+++ b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
@@ -18,17 +18,18 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.implementedInJetBrainsFork
 
 @Immutable
 @JvmInline
 actual value class LineBreak private constructor(internal val mask: Int) {
     actual companion object {
-        @Stable actual val Simple: LineBreak = LineBreak(1)
+        @Stable actual val Simple: LineBreak = implementedInJetBrainsFork()
 
-        @Stable actual val Heading: LineBreak = LineBreak(2)
+        @Stable actual val Heading: LineBreak = implementedInJetBrainsFork()
 
-        @Stable actual val Paragraph: LineBreak = LineBreak(3)
+        @Stable actual val Paragraph: LineBreak = implementedInJetBrainsFork()
 
-        @Stable actual val Unspecified: LineBreak = LineBreak(Int.MIN_VALUE)
+        @Stable actual val Unspecified: LineBreak = implementedInJetBrainsFork()
     }
 }
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt
index eb79c74..2ef3ee0 100644
--- a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt
+++ b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt
@@ -17,12 +17,13 @@
 package androidx.compose.ui.text.style
 
 import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.implementedInJetBrainsFork
 
 @Immutable
 actual class TextMotion private constructor() {
     actual companion object {
-        actual val Static: TextMotion = TextMotion()
+        actual val Static: TextMotion = implementedInJetBrainsFork()
 
-        actual val Animated: TextMotion = TextMotion()
+        actual val Animated: TextMotion = implementedInJetBrainsFork()
     }
 }
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt
new file mode 100644
index 0000000..be2547b
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+internal actual class AtomicReference<V> actual constructor(value: V) {
+    init {
+        implementedInJetBrainsFork()
+    }
+
+    actual fun get(): V = implementedInJetBrainsFork()
+
+    actual fun set(value: V): Unit = implementedInJetBrainsFork()
+
+    actual fun getAndSet(value: V): V = implementedInJetBrainsFork()
+
+    actual fun compareAndSet(expect: V, newValue: V): Boolean = implementedInJetBrainsFork()
+}
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt
index 9df4b05..4bd2666 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun AnnotatedString.transform(
+    transform: (String, Int, Int) -> String
+): AnnotatedString = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
similarity index 67%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
index 9df4b05..97fc3e3 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text
 
-actual class NativePaint
+internal actual fun String.findPrecedingBreak(index: Int): Int = implementedInJetBrainsFork()
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun String.findFollowingBreak(index: Int): Int = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
similarity index 64%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
index 9df4b05..bf9fe98 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun implementedInJetBrainsFork(): Nothing =
+    throw NotImplementedError(
+        """
+        Implemented only in JetBrains fork.
+        Please use `org.jetbrains.compose.ui:ui-text` package instead.
+        """
+            .trimIndent()
+    )
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt
new file mode 100644
index 0000000..9781139
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+import androidx.annotation.IntRange
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.drawscope.DrawStyle
+import androidx.compose.ui.text.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.text.style.TextDecoration
+
+@JvmDefaultWithCompatibility
+actual sealed interface Paragraph {
+    actual val width: Float
+    actual val height: Float
+    actual val minIntrinsicWidth: Float
+    actual val maxIntrinsicWidth: Float
+    actual val firstBaseline: Float
+    actual val lastBaseline: Float
+    actual val didExceedMaxLines: Boolean
+    actual val lineCount: Int
+    actual val placeholderRects: List<Rect?>
+
+    actual fun getPathForRange(start: Int, end: Int): Path
+
+    actual fun getCursorRect(offset: Int): Rect
+
+    actual fun getLineLeft(lineIndex: Int): Float
+
+    actual fun getLineRight(lineIndex: Int): Float
+
+    actual fun getLineTop(lineIndex: Int): Float
+
+    actual fun getLineBaseline(lineIndex: Int): Float
+
+    actual fun getLineBottom(lineIndex: Int): Float
+
+    actual fun getLineHeight(lineIndex: Int): Float
+
+    actual fun getLineWidth(lineIndex: Int): Float
+
+    actual fun getLineStart(lineIndex: Int): Int
+
+    actual fun getLineEnd(lineIndex: Int, visibleEnd: Boolean): Int
+
+    actual fun isLineEllipsized(lineIndex: Int): Boolean
+
+    actual fun getLineForOffset(offset: Int): Int
+
+    actual fun getHorizontalPosition(offset: Int, usePrimaryDirection: Boolean): Float
+
+    actual fun getParagraphDirection(offset: Int): ResolvedTextDirection
+
+    actual fun getBidiRunDirection(offset: Int): ResolvedTextDirection
+
+    actual fun getLineForVerticalPosition(vertical: Float): Int
+
+    actual fun getOffsetForPosition(position: Offset): Int
+
+    actual fun getRangeForRect(
+        rect: Rect,
+        granularity: TextGranularity,
+        inclusionStrategy: TextInclusionStrategy
+    ): TextRange
+
+    actual fun getBoundingBox(offset: Int): Rect
+
+    actual fun fillBoundingBoxes(
+        range: TextRange,
+        array: FloatArray,
+        @IntRange(from = 0) arrayStart: Int
+    )
+
+    actual fun getWordBoundary(offset: Int): TextRange
+
+    actual fun paint(canvas: Canvas, color: Color, shadow: Shadow?, textDecoration: TextDecoration?)
+
+    actual fun paint(
+        canvas: Canvas,
+        color: Color,
+        shadow: Shadow?,
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?,
+        blendMode: BlendMode
+    )
+
+    actual fun paint(
+        canvas: Canvas,
+        brush: Brush,
+        alpha: Float,
+        shadow: Shadow?,
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?,
+        blendMode: BlendMode
+    )
+}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt
new file mode 100644
index 0000000..edec094
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.ui.text
+
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.ui.text.style.LineBreak
+import androidx.compose.ui.text.style.TextMotion
+
+internal actual val PlatformParagraphStyle.Companion.Saver: Saver<PlatformParagraphStyle, Any>
+    get() = PlatformParagraphStyleSaver
+
+private val PlatformParagraphStyleSaver =
+    Saver<PlatformParagraphStyle, Any>(save = {}, restore = { PlatformParagraphStyle() })
+
+internal actual val LineBreak.Companion.Saver: Saver<LineBreak, Any>
+    get() = LineBreakSaver
+
+private val LineBreakSaver =
+    Saver<LineBreak, Any>(
+        save = { it.mask },
+        restore = {
+            val mask = it as Int
+            when (mask) {
+                1 -> LineBreak.Simple
+                2 -> LineBreak.Heading
+                3 -> LineBreak.Paragraph
+                else -> {
+                    LineBreak.Unspecified
+                }
+            }
+        }
+    )
+
+internal actual val TextMotion.Companion.Saver: Saver<TextMotion, Any>
+    get() = TextMotionSaver
+
+private val TextMotionSaver =
+    Saver<TextMotion, Any>(
+        save = { if (it == TextMotion.Static) 0 else 1 },
+        restore = {
+            if (it == 0) {
+                TextMotion.Static
+            } else {
+                TextMotion.Animated
+            }
+        }
+    )
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt
new file mode 100644
index 0000000..55558913
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+internal actual fun createPlatformTextStyle(
+    spanStyle: PlatformSpanStyle?,
+    paragraphStyle: PlatformParagraphStyle?
+): PlatformTextStyle = implementedInJetBrainsFork()
+
+actual class PlatformTextStyle {
+    actual val spanStyle: PlatformSpanStyle?
+        get() = implementedInJetBrainsFork()
+
+    actual val paragraphStyle: PlatformParagraphStyle?
+        get() = implementedInJetBrainsFork()
+}
+
+actual class PlatformParagraphStyle {
+    actual companion object {
+        actual val Default: PlatformParagraphStyle = implementedInJetBrainsFork()
+    }
+
+    actual fun merge(other: PlatformParagraphStyle?): PlatformParagraphStyle =
+        implementedInJetBrainsFork()
+}
+
+actual class PlatformSpanStyle {
+    actual companion object {
+        actual val Default: PlatformSpanStyle = implementedInJetBrainsFork()
+    }
+
+    actual fun merge(other: PlatformSpanStyle?): PlatformSpanStyle = implementedInJetBrainsFork()
+}
+
+actual fun lerp(
+    start: PlatformParagraphStyle,
+    stop: PlatformParagraphStyle,
+    fraction: Float
+): PlatformParagraphStyle = implementedInJetBrainsFork()
+
+actual fun lerp(
+    start: PlatformSpanStyle,
+    stop: PlatformSpanStyle,
+    fraction: Float
+): PlatformSpanStyle = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
similarity index 63%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
index 9df4b05..66bdcf0 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.font
 
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun createFontFamilyResolver(
+    @Suppress("DEPRECATION") fontResourceLoader: Font.ResourceLoader
+): FontFamily.Resolver = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt
similarity index 62%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt
index 9df4b05..abd737f 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.font
 
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun FontSynthesis.synthesizeTypeface(
+    typeface: Any,
+    font: Font,
+    requestedWeight: FontWeight,
+    requestedStyle: FontStyle
+): Any = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt
new file mode 100644
index 0000000..17a0131
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.font
+
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+internal actual class PlatformFontFamilyTypefaceAdapter actual constructor() :
+    FontFamilyTypefaceAdapter {
+
+    actual override fun resolve(
+        typefaceRequest: TypefaceRequest,
+        platformFontLoader: PlatformFontLoader,
+        onAsyncCompletion: (TypefaceResult.Immutable) -> Unit,
+        createDefaultTypeface: (TypefaceRequest) -> Any
+    ): TypefaceResult? = implementedInJetBrainsFork()
+}
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt
similarity index 63%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt
index 9df4b05..36833b1 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.input
 
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun String.toCharArray(
+    destination: CharArray,
+    destinationOffset: Int,
+    startIndex: Int,
+    endIndex: Int
+): Unit = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
index 9df4b05..2764f82 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.input
 
-actual class NativePaint
+import androidx.compose.runtime.Immutable
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+/** Used to configure the platform specific IME options. */
+@Immutable actual class PlatformImeOptions
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
index 9df4b05..14e41e4 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.internal
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual annotation class JvmDefaultWithCompatibility
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
similarity index 68%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
index 9df4b05..018c860 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.intl
 
-actual class NativePaint
+import androidx.compose.ui.text.implementedInJetBrainsFork
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun createPlatformLocaleDelegate(): PlatformLocaleDelegate =
+    implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt
new file mode 100644
index 0000000..aea1184
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+
+package androidx.compose.ui.text.intl
+
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+actual class PlatformLocale
+
+internal actual val PlatformLocale.language: String
+    get() = implementedInJetBrainsFork()
+
+internal actual val PlatformLocale.script: String
+    get() = implementedInJetBrainsFork()
+
+internal actual val PlatformLocale.region: String
+    get() = implementedInJetBrainsFork()
+
+internal actual fun PlatformLocale.getLanguageTag(): String = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
similarity index 64%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
index 9df4b05..2a9df32 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.text.platform
 
-actual class NativePaint
+import androidx.compose.ui.text.PlatformStringDelegate
+import androidx.compose.ui.text.implementedInJetBrainsFork
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual fun ActualStringDelegate(): PlatformStringDelegate = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt
new file mode 100644
index 0000000..8f9e6b7
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.platform
+
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.drawscope.DrawStyle
+import androidx.compose.ui.text.MultiParagraph
+import androidx.compose.ui.text.implementedInJetBrainsFork
+import androidx.compose.ui.text.style.TextDecoration
+
+internal actual fun MultiParagraph.drawMultiParagraph(
+    canvas: Canvas,
+    brush: Brush,
+    alpha: Float,
+    shadow: Shadow?,
+    decoration: TextDecoration?,
+    drawStyle: DrawStyle?,
+    blendMode: BlendMode
+): Unit = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt
new file mode 100644
index 0000000..fb9de57
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.compose.ui.text.platform
+
+import androidx.compose.ui.text.AnnotatedString.Range
+import androidx.compose.ui.text.Paragraph
+import androidx.compose.ui.text.ParagraphIntrinsics
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.implementedInJetBrainsFork
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+
+internal actual fun ActualParagraph(
+    text: String,
+    style: TextStyle,
+    spanStyles: List<Range<SpanStyle>>,
+    placeholders: List<Range<Placeholder>>,
+    maxLines: Int,
+    ellipsis: Boolean,
+    width: Float,
+    density: Density,
+    @Suppress("DEPRECATION") resourceLoader: Font.ResourceLoader
+): Paragraph = implementedInJetBrainsFork()
+
+internal actual fun ActualParagraph(
+    text: String,
+    style: TextStyle,
+    spanStyles: List<Range<SpanStyle>>,
+    placeholders: List<Range<Placeholder>>,
+    maxLines: Int,
+    ellipsis: Boolean,
+    constraints: Constraints,
+    density: Density,
+    fontFamilyResolver: FontFamily.Resolver
+): Paragraph = implementedInJetBrainsFork()
+
+internal actual fun ActualParagraph(
+    paragraphIntrinsics: ParagraphIntrinsics,
+    maxLines: Int,
+    ellipsis: Boolean,
+    constraints: Constraints
+): Paragraph = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt
new file mode 100644
index 0000000..8cbb81d
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.compose.ui.text.platform
+
+import androidx.compose.ui.text.AnnotatedString.Range
+import androidx.compose.ui.text.ParagraphIntrinsics
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.implementedInJetBrainsFork
+import androidx.compose.ui.unit.Density
+
+internal actual fun ActualParagraphIntrinsics(
+    text: String,
+    style: TextStyle,
+    spanStyles: List<Range<SpanStyle>>,
+    placeholders: List<Range<Placeholder>>,
+    density: Density,
+    fontFamilyResolver: FontFamily.Resolver
+): ParagraphIntrinsics = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt
new file mode 100644
index 0000000..ad58e16e
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.platform
+
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+@PublishedApi internal actual class SynchronizedObject
+
+internal actual fun createSynchronizedObject(): SynchronizedObject = implementedInJetBrainsFork()
+
+@PublishedApi
+internal actual inline fun <R> synchronized(lock: SynchronizedObject, block: () -> R): R = block()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt
new file mode 100644
index 0000000..8a547cd
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.style
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+@Immutable
+actual value class LineBreak private constructor(internal val mask: Int) {
+    actual companion object {
+        @Stable actual val Simple: LineBreak = implementedInJetBrainsFork()
+
+        @Stable actual val Heading: LineBreak = implementedInJetBrainsFork()
+
+        @Stable actual val Paragraph: LineBreak = implementedInJetBrainsFork()
+
+        @Stable actual val Unspecified: LineBreak = implementedInJetBrainsFork()
+    }
+}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt
new file mode 100644
index 0000000..2ef3ee0
--- /dev/null
+++ b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.style
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.implementedInJetBrainsFork
+
+@Immutable
+actual class TextMotion private constructor() {
+    actual companion object {
+        actual val Static: TextMotion = implementedInJetBrainsFork()
+
+        actual val Animated: TextMotion = implementedInJetBrainsFork()
+    }
+}
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index bb09c87..d7cd9ff 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -62,7 +62,7 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index 49c9961..57ef847 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -33,6 +33,7 @@
 androidXMultiplatform {
     android()
     jvmStubs()
+    linuxX64Stubs()
 
     defaultPlatform(PlatformIdentifier.ANDROID)
 
@@ -64,15 +65,17 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 implementation('androidx.collection:collection-ktx:1.2.0')
             }
         }
 
         jvmStubsMain {
             dependsOn(jvmMain)
-            dependencies {
-            }
+        }
+
+        linuxx64StubsMain {
+            dependsOn(commonMain)
         }
 
         androidInstrumentedTest {
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt
index e70985e..5dc4eb6 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Constraints.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.unit.Constraints.Companion.Infinity
+import kotlin.jvm.JvmInline
 import kotlin.math.min
 
 /**
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index 12868d2..4cc9af8 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.util.packFloats
 import androidx.compose.ui.util.unpackFloat1
 import androidx.compose.ui.util.unpackFloat2
+import kotlin.jvm.JvmInline
 import kotlin.math.max
 import kotlin.math.min
 
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
index 5453fc6..9bafe44 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/IntOffset.kt
@@ -26,6 +26,7 @@
 import androidx.compose.ui.util.packInts
 import androidx.compose.ui.util.unpackInt1
 import androidx.compose.ui.util.unpackInt2
+import kotlin.jvm.JvmInline
 
 /** Constructs a [IntOffset] from [x] and [y] position [Int] values. */
 @Stable fun IntOffset(x: Int, y: Int): IntOffset = IntOffset(packInts(x, y))
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
similarity index 69%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
index 9df4b05..9f1c181 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.unit
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+/** Converts [TextUnit] to [Dp] and vice-versa. */
+actual typealias FontScaling = FontScalingLinear
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
similarity index 75%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
index 9df4b05..44f7772 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.unit.internal
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+internal actual annotation class JvmDefaultWithCompatibility actual constructor()
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index c680394..d07e09f 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -34,6 +34,7 @@
 androidXMultiplatform {
     android()
     jvmStubs()
+    linuxX64Stubs()
 
     defaultPlatform(PlatformIdentifier.ANDROID)
 
@@ -59,12 +60,21 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
+        commonStubsMain {
+            dependsOn(commonMain)
+        }
+
         jvmStubsMain {
             dependsOn(jvmMain)
+            dependsOn(commonStubsMain)
+        }
+
+        linuxx64StubsMain {
+            dependsOn(commonStubsMain)
         }
 
         androidInstrumentedTest {
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/NotImplemented.commonStubs.kt
similarity index 64%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/NotImplemented.commonStubs.kt
index 9df4b05..8dff401 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/NotImplemented.commonStubs.kt
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.util
 
-actual class NativePaint
-
-actual fun Paint(): Paint = implementedInJetBrainsFork()
-
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun implementedInJetBrainsFork(): Nothing =
+    throw NotImplementedError(
+        """
+        Implemented only in JetBrains fork.
+        Please use `org.jetbrains.compose.ui:ui-util` package instead.
+        """
+            .trimIndent()
+    )
diff --git a/compose/ui/ui-util/src/jvmStubsMain/kotlin/androidx/compose/ui/util/Trace.jvmStubs.kt b/compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/Trace.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-util/src/jvmStubsMain/kotlin/androidx/compose/ui/util/Trace.jvmStubs.kt
rename to compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/Trace.commonStubs.kt
diff --git a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt b/compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt
similarity index 65%
copy from compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
copy to compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt
index 9df4b05..9a4c815 100644
--- a/compose/ui/ui-graphics/src/jvmStubsMain/kotlin/androidx/compose/ui/graphics/Paint.jvmStubs.kt
+++ b/compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.graphics
+package androidx.compose.ui.util
 
-actual class NativePaint
+actual fun floatFromBits(bits: Int): Float = implementedInJetBrainsFork()
 
-actual fun Paint(): Paint = implementedInJetBrainsFork()
+actual fun doubleFromBits(bits: Long): Double = implementedInJetBrainsFork()
 
-actual fun BlendMode.isSupported(): Boolean = implementedInJetBrainsFork()
+actual fun Float.fastRoundToInt(): Int = implementedInJetBrainsFork()
+
+actual fun Double.fastRoundToInt(): Int = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 1d2de81..ef1a521 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -74,7 +74,7 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
-                api("androidx.annotation:annotation-experimental:1.4.0")
+                api("androidx.annotation:annotation-experimental:1.4.1")
                 // This has stub APIs for access to legacy Android APIs, so we don't want
                 // any dependency on this module.
                 compileOnly(project(":compose:ui:ui-android-stubs"))
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 7a2f9bc95..390127d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -159,6 +159,7 @@
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertContentDescriptionEquals
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertIsNotSelected
 import androidx.compose.ui.test.assertIsOff
 import androidx.compose.ui.test.assertIsOn
@@ -2714,6 +2715,64 @@
     }
 
     @Test
+    fun sendClickedAndChangeSubtree_whenDescendantClicked() {
+        val textTag = "text_tag"
+        // Arrange.
+        setContent {
+            var selected by remember { mutableStateOf(true) }
+            Box {
+                Box(Modifier.testTag(textTag)) {
+                    if (selected) {
+                        BasicText(text = "DisappearingText")
+                    }
+                }
+                Box(Modifier.clickable(onClick = { selected = !selected }).testTag(tag)) {
+                    BasicText("ToggleableComponent")
+                }
+            }
+        }
+        val toggleableVirtualViewId = rule.onNodeWithTag(tag).assertIsDisplayed().semanticsId
+
+        // Act.
+        val actionPerformed =
+            rule.runOnUiThread {
+                provider.performAction(toggleableVirtualViewId, ACTION_CLICK, null)
+            }
+
+        // Assert that `TYPE_VIEW_CLICKED` event was sent.
+        rule.runOnIdle {
+            assertThat(actionPerformed).isTrue()
+            verify(container, times(1))
+                .requestSendAccessibilityEvent(
+                    eq(androidComposeView),
+                    argThat(
+                        ArgumentMatcher {
+                            getAccessibilityEventSourceSemanticsNodeId(it) ==
+                                toggleableVirtualViewId && it.eventType == TYPE_VIEW_CLICKED
+                        }
+                    )
+                )
+        }
+
+        rule.mainClock.advanceTimeBy(accessibilityEventLoopIntervalMs)
+
+        // Assert that `TYPE_WINDOW_CONTENT_CHANGED` event was also sent.
+        rule.onNodeWithTag(textTag).assertIsNotDisplayed()
+        rule.runOnIdle {
+            verify(container, atLeastOnce())
+                .requestSendAccessibilityEvent(
+                    eq(androidComposeView),
+                    argThat(
+                        ArgumentMatcher {
+                            it.eventType == TYPE_WINDOW_CONTENT_CHANGED &&
+                                it.contentChangeTypes == CONTENT_CHANGE_TYPE_SUBTREE
+                        }
+                    )
+                )
+        }
+    }
+
+    @Test
     fun sendStateChangeEvent_whenSelectedChange() {
         // Arrange.
         setContent {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index e3b46c6..f5e7c2c 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -2352,13 +2352,6 @@
             return
         }
 
-        // No need to send an event if an ancestor has also been changed
-        for (potentialAncestor in subtreeChangedLayoutNodes.indices) {
-            if (subtreeChangedLayoutNodes.valueAt(potentialAncestor).isAncestorOf(layoutNode)) {
-                return
-            }
-        }
-
         // When we finally send the event, make sure it is an accessibility-focusable node.
         val id =
             trace("GetSemanticsNode") {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
index 22c4b62..3f890eb 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
@@ -25,7 +25,6 @@
 import androidx.collection.mutableIntSetOf
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.OwnerScope
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.ScrollAxisRange
@@ -148,11 +147,6 @@
 internal fun AndroidViewsHandler.semanticsIdToView(id: Int): View? =
     layoutNodeToHolder.entries.firstOrNull { it.key.semanticsId == id }?.value
 
-internal fun LayoutNode.isAncestorOf(node: LayoutNode): Boolean {
-    val p = node.parent ?: return false
-    return (p == this) || isAncestorOf(p)
-}
-
 // TODO(mnuzen): refactor `currentSemanticsNodes` in the AccessibilityDelegate file to also use
 // IntObjectMap's. Then ACVADC can also call `getAllUncoveredSemanticsNodesToIntObjectMap` instead
 // of `getAllUncoveredSemanticsNodesToMap` as it does now.
diff --git a/core/core-telecom/build.gradle b/core/core-telecom/build.gradle
index e5b92f4..6600cb1 100644
--- a/core/core-telecom/build.gradle
+++ b/core/core-telecom/build.gradle
@@ -35,7 +35,7 @@
     api(libs.guavaListenableFuture)
     implementation("androidx.annotation:annotation:1.4.0")
     // @OptIn annotations
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     implementation("androidx.core:core:1.9.0")
     implementation(libs.kotlinCoroutinesCore)
     implementation(libs.kotlinCoroutinesGuava)
diff --git a/core/core-telecom/integration-tests/testapp/build.gradle b/core/core-telecom/integration-tests/testapp/build.gradle
index a998e35..a679012 100644
--- a/core/core-telecom/integration-tests/testapp/build.gradle
+++ b/core/core-telecom/integration-tests/testapp/build.gradle
@@ -50,7 +50,7 @@
     implementation('androidx.recyclerview:recyclerview:1.2.1')
 
     // Align dependencies in debugRuntimeClasspath and debugAndroidTestRuntimeClasspath.
-    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.0")
+    androidTestImplementation("androidx.annotation:annotation-experimental:1.4.1")
 
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRunner)
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 3d8ff29..4ca6ed5 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -72,6 +72,7 @@
     method public static androidx.core.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
     method public void requestUsageTimeReport(android.app.PendingIntent);
     method public androidx.core.app.ActivityOptionsCompat setLaunchBounds(android.graphics.Rect?);
+    method public androidx.core.app.ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(int);
     method public androidx.core.app.ActivityOptionsCompat setShareIdentityEnabled(boolean);
     method public android.os.Bundle? toBundle();
     method public void update(androidx.core.app.ActivityOptionsCompat);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index fd3badb..93b61a0 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -90,6 +90,7 @@
     method public static androidx.core.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
     method public void requestUsageTimeReport(android.app.PendingIntent);
     method public androidx.core.app.ActivityOptionsCompat setLaunchBounds(android.graphics.Rect?);
+    method public androidx.core.app.ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(int);
     method public androidx.core.app.ActivityOptionsCompat setShareIdentityEnabled(boolean);
     method public android.os.Bundle? toBundle();
     method public void update(androidx.core.app.ActivityOptionsCompat);
diff --git a/core/core/build.gradle b/core/core/build.gradle
index a646db3..d4cac38 100644
--- a/core/core/build.gradle
+++ b/core/core/build.gradle
@@ -22,7 +22,7 @@
     }
 
     api("androidx.annotation:annotation:1.8.0")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     api("androidx.lifecycle:lifecycle-runtime:2.6.2")
     api("androidx.versionedparcelable:versionedparcelable:1.1.1")
     implementation("androidx.collection:collection:1.0.0")
diff --git a/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java b/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
index ce091a8..5112fed 100644
--- a/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
@@ -61,7 +61,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -416,39 +415,6 @@
         assertNull(callback.mTypeface);
     }
 
-    @Test
-    public void testRequestFont_loadsOnExecutor() throws InterruptedException {
-        CountDownLatch loadingLatch = new CountDownLatch(1);
-        Executor loadingExecutor = c -> {
-            loadingLatch.countDown();
-            c.run();
-        };
-
-        CountDownLatch callbackExecutorLatch = new CountDownLatch(1);
-        Executor callbackExecutor = c -> {
-            callbackExecutorLatch.countDown();
-            c.run();
-        };
-
-        // invalid request, we're just checking for using the right threads
-        final FontRequest request = new FontRequest(
-                AUTHORITY, PACKAGE, MockFontProvider.INVALID_URI, SIGNATURE);
-        CountDownLatch cbLatch = new CountDownLatch(1);
-        FontCallback cb = new FontCallback(cbLatch);
-
-        FontsContractCompat.requestFont(mContext, request, Typeface.BOLD, loadingExecutor,
-                callbackExecutor, cb);
-
-        // it is not practical to assert the right runnable goes to the right executor, so just
-        // assert that both executors are invoked, and that the callback is eventually invoked
-        assertTrue("Loading happens on loading executor",
-                loadingLatch.await(5L, TimeUnit.SECONDS));
-        assertTrue("callback happens on callback executor",
-                callbackExecutorLatch.await(5L, TimeUnit.SECONDS));
-        assertTrue("callback happens", cbLatch.await(5L, TimeUnit.SECONDS));
-
-    }
-
     public static class FontCallback extends FontsContractCompat.FontRequestCallback {
         private final CountDownLatch mLatch;
         Typeface mTypeface;
diff --git a/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java b/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java
index 6287095..630ccda 100644
--- a/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java
+++ b/core/core/src/main/java/androidx/core/app/ActivityOptionsCompat.java
@@ -20,6 +20,7 @@
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.Build;
@@ -27,11 +28,16 @@
 import android.view.View;
 
 import androidx.annotation.DoNotInline;
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
 import androidx.core.util.Pair;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Helper for accessing features in {@link android.app.ActivityOptions} in a backwards compatible
  * fashion.
@@ -51,6 +57,20 @@
     public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
 
     /**
+     * Enumeration of background activity start modes.
+     * <p/>
+     * These define if an app wants to grant it's background activity start privileges to a
+     * {@link PendingIntent}.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef(value = {
+            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
+            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED})
+    public @interface BackgroundActivityStartMode {}
+
+    /**
      * Create an ActivityOptions specifying a custom animation to run when the
      * activity is displayed.
      *
@@ -289,6 +309,7 @@
             return Api24Impl.getLaunchBounds(mActivityOptions);
         }
 
+        @NonNull
         @Override
         public ActivityOptionsCompat setShareIdentityEnabled(boolean shareIdentity) {
             if (Build.VERSION.SDK_INT < 34) {
@@ -297,6 +318,21 @@
             return new ActivityOptionsCompatImpl(
                     Api34Impl.setShareIdentityEnabled(mActivityOptions, shareIdentity));
         }
+
+        @NonNull
+        @Override
+        public ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(
+                @BackgroundActivityStartMode int state) {
+            if (Build.VERSION.SDK_INT >= 34) {
+                Api34Impl.setPendingIntentBackgroundActivityStartMode(mActivityOptions, state);
+            } else if (Build.VERSION.SDK_INT >= 33) {
+                // Matches the behavior of isPendingIntentBackgroundActivityLaunchAllowed().
+                boolean isAllowed = state != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+                Api33Impl.setPendingIntentBackgroundActivityLaunchAllowed(
+                        mActivityOptions, isAllowed);
+            }
+            return this;
+        }
     }
 
     protected ActivityOptionsCompat() {
@@ -402,6 +438,21 @@
         return this;
     }
 
+    /**
+     * Sets the mode for allowing or denying the senders privileges to start background activities
+     * to the PendingIntent.
+     * <p/>
+     * This is typically used in when executing {@link PendingIntent#send(Context, int, Intent)} or
+     * similar methods. A privileged sender of a PendingIntent should only grant
+     * {@link ActivityOptions#MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a
+     * trusted source and/or executed on behalf the user.
+     */
+    @NonNull
+    public ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(
+            @BackgroundActivityStartMode int state) {
+        return this;
+    }
+
     @RequiresApi(23)
     static class Api23Impl {
         private Api23Impl() {
@@ -470,6 +521,20 @@
         }
     }
 
+    @RequiresApi(33)
+    static class Api33Impl {
+        private Api33Impl() {
+            // This class is not instantiable.
+        }
+
+        @SuppressWarnings("deprecation")
+        @DoNotInline
+        static void setPendingIntentBackgroundActivityLaunchAllowed(ActivityOptions activityOptions,
+                boolean allowed) {
+            activityOptions.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+        }
+    }
+
     @RequiresApi(34)
     static class Api34Impl {
         private Api34Impl() {
@@ -481,5 +546,11 @@
                 boolean shareIdentity) {
             return activityOptions.setShareIdentityEnabled(shareIdentity);
         }
+
+        @DoNotInline
+        static ActivityOptions setPendingIntentBackgroundActivityStartMode(
+                ActivityOptions activityOptions, int state) {
+            return activityOptions.setPendingIntentBackgroundActivityStartMode(state);
+        }
     }
 }
diff --git a/core/core/src/test/java/androidx/core/app/ActivityOptionsCompatTest.kt b/core/core/src/test/java/androidx/core/app/ActivityOptionsCompatTest.kt
new file mode 100644
index 0000000..2ba9b7d
--- /dev/null
+++ b/core/core/src/test/java/androidx/core/app/ActivityOptionsCompatTest.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.core.app
+
+import android.app.ActivityOptions
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+class ActivityOptionsCompatTest {
+
+    @Config(minSdk = 33)
+    @Test
+    fun testSetPendingIntentBackgroundActivityStartMode() {
+        val activityOptionsCompat = ActivityOptionsCompat.makeBasic()
+
+        activityOptionsCompat.setPendingIntentBackgroundActivityStartMode(
+            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+        )
+        activityOptionsCompat.setPendingIntentBackgroundActivityStartMode(
+            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+        )
+    }
+}
diff --git a/credentials/credentials-e2ee/src/main/java/androidx/credentials/playservices/e2ee/androidx-credentials-credentials-play-services-e2ee-documentation.md b/credentials/credentials-e2ee/src/main/java/androidx/credentials/playservices/e2ee/androidx-credentials-credentials-play-services-e2ee-documentation.md
new file mode 100644
index 0000000..85f3a1e
--- /dev/null
+++ b/credentials/credentials-e2ee/src/main/java/androidx/credentials/playservices/e2ee/androidx-credentials-credentials-play-services-e2ee-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+CREDENTIALS CREDENTIALS PLAY SERVICES E2EE
+
+# Package androidx.credentials.playservices.e2ee
+
+This package allows developers to use E2EE contact keys.
diff --git a/credentials/credentials-play-services-e2ee/OWNERS b/credentials/credentials-play-services-e2ee/OWNERS
new file mode 100644
index 0000000..ce145c2
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1487500
[email protected]
[email protected]
diff --git a/credentials/credentials-play-services-e2ee/api/current.txt b/credentials/credentials-play-services-e2ee/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-play-services-e2ee/api/res-current.txt b/credentials/credentials-play-services-e2ee/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/api/res-current.txt
diff --git a/credentials/credentials-play-services-e2ee/api/restricted_current.txt b/credentials/credentials-play-services-e2ee/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-play-services-e2ee/build.gradle b/credentials/credentials-play-services-e2ee/build.gradle
new file mode 100644
index 0000000..5ff33f5
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/build.gradle
@@ -0,0 +1,39 @@
+
+/*
+ * 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.
+ */
+import androidx.build.LibraryType
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+dependencies {
+    api(libs.kotlinStdlib)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.truth)
+}
+android {
+    namespace "androidx.credentials.playservices.e2ee"
+}
+androidx {
+    name = "androidx.credentials:credentials.playservices.e2ee"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2024"
+    description = "Play Services API for E2EE contact keys functionality."
+}
diff --git a/credentials/credentials-play-services-e2ee/src/androidTest/java/androidx/credentials/playservices/e2ee/CredentialsPlayServicesE2eeInstrumentedTest.java b/credentials/credentials-play-services-e2ee/src/androidTest/java/androidx/credentials/playservices/e2ee/CredentialsPlayServicesE2eeInstrumentedTest.java
new file mode 100644
index 0000000..bf3d391
--- /dev/null
+++ b/credentials/credentials-play-services-e2ee/src/androidTest/java/androidx/credentials/playservices/e2ee/CredentialsPlayServicesE2eeInstrumentedTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.credentials.playservices.e2ee;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CredentialsPlayServicesE2eeInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("androidx.credentials.playservices.e2ee.test", appContext.getPackageName());
+    }
+}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 38c3dfb..50e30d8 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -140,6 +140,7 @@
     docs(project(":credentials:credentials-fido"))
     docs(project(":credentials:credentials-play-services-auth"))
     docs(project(":credentials:credentials-e2ee"))
+    docs(project(":credentials:credentials-play-services-e2ee"))
     docs(project(":cursoradapter:cursoradapter"))
     docs(project(":customview:customview"))
     docs(project(":customview:customview-poolingcontainer"))
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index 1870df9..6734ce4 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -41,7 +41,7 @@
     api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     api("androidx.savedstate:savedstate:1.2.1")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     api(libs.kotlinStdlib)
 
     androidTestImplementation("androidx.appcompat:appcompat:1.1.0", {
diff --git a/glance/glance-template/build.gradle b/glance/glance-template/build.gradle
index b76e014..0db72c3 100644
--- a/glance/glance-template/build.gradle
+++ b/glance/glance-template/build.gradle
@@ -49,7 +49,7 @@
     implementation(libs.kotlinStdlib)
 
     // Force upgrade since 1.2.0 is not compatible with latest lint.
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
 
     testImplementation(libs.robolectric)
     testImplementation(libs.testCore)
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index 0a563e2..04462a0 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -46,7 +46,7 @@
     implementation(libs.kotlinStdlib)
 
     // Force upgrade since 1.2.0 is not compatible with latest lint.
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
 
     testImplementation(libs.robolectric)
     testImplementation(libs.testCore)
diff --git a/graphics/filters/filters/build.gradle b/graphics/filters/filters/build.gradle
index 374bfc3..b6f4fa6 100644
--- a/graphics/filters/filters/build.gradle
+++ b/graphics/filters/filters/build.gradle
@@ -43,7 +43,7 @@
     implementation('androidx.media3:media3-transformer:' + media3Version)
 
     // Force upgrade since 1.2.0 is not compatible with latest lint.
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
 
     // Test dependencies
     androidTestImplementation(libs.testExtJunit)
diff --git a/graphics/graphics-core/build.gradle b/graphics/graphics-core/build.gradle
index 7fd55aa..4860a59 100644
--- a/graphics/graphics-core/build.gradle
+++ b/graphics/graphics-core/build.gradle
@@ -33,7 +33,7 @@
 dependencies {
     api(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesAndroid)
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation("androidx.core:core:1.8.0")
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/graphics/graphics-shapes/build.gradle b/graphics/graphics-shapes/build.gradle
index a782cc0..a253ff9 100644
--- a/graphics/graphics-shapes/build.gradle
+++ b/graphics/graphics-shapes/build.gradle
@@ -71,7 +71,7 @@
             dependsOn(jvmMain)
             dependencies {
                 implementation("androidx.core:core-ktx:1.10.0")
-                implementation("androidx.annotation:annotation-experimental:1.4.0-rc01")
+                implementation("androidx.annotation:annotation-experimental:1.4.1")
             }
         }
 
diff --git a/lint-checks/integration-tests/build.gradle b/lint-checks/integration-tests/build.gradle
index e5c85f7..4966bb6 100644
--- a/lint-checks/integration-tests/build.gradle
+++ b/lint-checks/integration-tests/build.gradle
@@ -31,7 +31,7 @@
 
 dependencies {
     implementation("androidx.annotation:annotation:1.8.0")
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation(libs.kotlinStdlib)
 }
 
diff --git a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
index 319d7c5..b4b5ecc 100644
--- a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
@@ -69,8 +69,6 @@
                     BanSynchronizedMethods.ISSUE,
                     MetadataTagInsideApplicationTagDetector.ISSUE,
                     PrivateConstructorForUtilityClassDetector.ISSUE,
-                    ClassVerificationFailureDetector.METHOD_CALL_ISSUE,
-                    ClassVerificationFailureDetector.IMPLICIT_CAST_ISSUE,
                     IdeaSuppressionDetector.ISSUE,
                     CameraXQuirksClassDetector.ISSUE,
                     NullabilityAnnotationsDetector.ISSUE,
diff --git a/mediarouter/mediarouter/build.gradle b/mediarouter/mediarouter/build.gradle
index 019340b1..51397fd 100644
--- a/mediarouter/mediarouter/build.gradle
+++ b/mediarouter/mediarouter/build.gradle
@@ -42,9 +42,7 @@
     implementation("androidx.recyclerview:recyclerview:1.1.0")
     implementation("androidx.appcompat:appcompat-resources:1.2.0")
 
-    // TODO(b/307906685) Change to the stable version with the next release.
-    // See b/307906685#comment6 for details.
-    implementation("androidx.annotation:annotation-experimental:1.4.0-rc01")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
 
     testImplementation(libs.junit)
     testImplementation(libs.testCore)
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 91af7ba..64200c4 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -35,7 +35,7 @@
     api("androidx.activity:activity-ktx:1.7.1")
     api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
     api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     implementation('androidx.collection:collection:1.1.0')
     implementation(libs.kotlinSerializationCore)
 
diff --git a/navigation/navigation-ui/build.gradle b/navigation/navigation-ui/build.gradle
index baa2420..58e46d0 100644
--- a/navigation/navigation-ui/build.gradle
+++ b/navigation/navigation-ui/build.gradle
@@ -42,7 +42,7 @@
     api("androidx.drawerlayout:drawerlayout:1.1.1")
     api("com.google.android.material:material:1.4.0")
     implementation("androidx.transition:transition:1.3.0")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
 
     androidTestImplementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
index b069102..ceeecc6 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataPresenter.kt
@@ -34,9 +34,12 @@
 import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.withContext
+import kotlinx.coroutines.yield
 
 /**
  * The class that connects the UI layer to the underlying Paging operations. Takes input from UI
@@ -160,6 +163,9 @@
                             )
                         }
                         event is Insert -> {
+                            if (inGetItem.value) {
+                                yield()
+                            }
                             // Process APPEND/PREPEND and send to presenter
                             presentPagingDataEvent(pageStore.processEvent(event))
 
@@ -215,6 +221,9 @@
                             }
                         }
                         event is Drop -> {
+                            if (inGetItem.value) {
+                                yield()
+                            }
                             // Process DROP and send to presenter
                             presentPagingDataEvent(pageStore.processEvent(event))
 
@@ -249,6 +258,8 @@
         }
     }
 
+    private val inGetItem = MutableStateFlow(false)
+
     /**
      * Returns the presented item at the specified position, notifying Paging of the item access to
      * trigger any loads necessary to fulfill [prefetchDistance][PagingConfig.prefetchDistance].
@@ -258,12 +269,13 @@
      */
     @MainThread
     public operator fun get(@IntRange(from = 0) index: Int): T? {
+        inGetItem.update { true }
         lastAccessedIndexUnfulfilled = true
         lastAccessedIndex = index
 
         log(VERBOSE) { "Accessing item index[$index]" }
         hintReceiver?.accessHint(pageStore.accessHintForPresenterIndex(index))
-        return pageStore.get(index)
+        return pageStore.get(index).also { inGetItem.update { false } }
     }
 
     /**
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
index 28116aa..bfc1e11 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
@@ -1033,6 +1033,329 @@
     }
 
     @Test
+    fun prependYieldsToRecyclerView() {
+        Dispatchers.resetMain() // reset MainDispatcherRule
+        // collection on immediate dispatcher to simulate real lifecycle dispatcher
+        val mainDispatcher = Dispatchers.Main.immediate
+        runTest {
+            val events = mutableListOf<String>()
+            val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+            val asyncDiffer =
+                AsyncPagingDataDiffer(
+                    diffCallback =
+                        object : DiffUtil.ItemCallback<Int>() {
+                            override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+
+                            override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+                        },
+                    // override default Dispatcher.Main with Dispatchers.main.immediate so that
+                    // main tasks run without queueing, we need this to simulate real life order of
+                    // events
+                    mainDispatcher = mainDispatcher,
+                    updateCallback = listUpdateCapture,
+                    workerDispatcher = backgroundScope.coroutineContext
+                )
+
+            val pager =
+                Pager(
+                    config =
+                        PagingConfig(
+                            pageSize = 5,
+                            enablePlaceholders = false,
+                            prefetchDistance = 3,
+                            initialLoadSize = 10,
+                        ),
+                    initialKey = 30
+                ) {
+                    TestPagingSource(loadDelay = 0)
+                }
+
+            val collectPager =
+                launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            assertThat(events).containsExactly("Inserted(position=0, count=10)")
+            events.clear()
+
+            // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+            // from upstream should yield until dispatch layout completes or else
+            // LoadState-based RV updates will crash. See original bug b/150162465.
+            withContext(mainDispatcher) {
+                events.add("start dispatchLayout")
+                asyncDiffer.getItem(3)
+                asyncDiffer.getItem(2) // this triggers prepend
+                events.add("end dispatchLayout")
+            }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            // make sure the prepend was not processed until RV is done with layouts
+            assertThat(events)
+                .containsExactly(
+                    "start dispatchLayout",
+                    "end dispatchLayout",
+                    "Inserted(position=0, count=5)"
+                )
+                .inOrder()
+            collectPager.cancel()
+        }
+    }
+
+    @Test
+    fun appendYieldsToRecyclerView() {
+        Dispatchers.resetMain() // reset MainDispatcherRule
+        // collection on immediate dispatcher to simulate real lifecycle dispatcher
+        val mainDispatcher = Dispatchers.Main.immediate
+        runTest {
+            val events = mutableListOf<String>()
+            val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+            val asyncDiffer =
+                AsyncPagingDataDiffer(
+                    diffCallback =
+                        object : DiffUtil.ItemCallback<Int>() {
+                            override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+
+                            override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+                        },
+                    // override default Dispatcher.Main with Dispatchers.main.immediate so that
+                    // main tasks run without queueing, we need this to simulate real life order of
+                    // events
+                    mainDispatcher = mainDispatcher,
+                    updateCallback = listUpdateCapture,
+                    workerDispatcher = backgroundScope.coroutineContext
+                )
+
+            val pager =
+                Pager(
+                    config =
+                        PagingConfig(
+                            pageSize = 5,
+                            enablePlaceholders = false,
+                            prefetchDistance = 3,
+                            initialLoadSize = 10,
+                        ),
+                ) {
+                    TestPagingSource(loadDelay = 0)
+                }
+
+            val collectPager =
+                launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            assertThat(events).containsExactly("Inserted(position=0, count=10)")
+            events.clear()
+
+            // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+            // from upstream should yield until dispatch layout completes or else
+            // LoadState-based RV updates will crash. See original bug b/150162465.
+            withContext(mainDispatcher) {
+                events.add("start dispatchLayout")
+                asyncDiffer.getItem(6)
+                asyncDiffer.getItem(7) // this triggers append
+                events.add("end dispatchLayout")
+            }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            // make sure the append was not processed until RV is done with layouts
+            assertThat(events)
+                .containsExactly(
+                    "start dispatchLayout",
+                    "end dispatchLayout",
+                    "Inserted(position=10, count=5)"
+                )
+                .inOrder()
+            collectPager.cancel()
+        }
+    }
+
+    @Test
+    fun dropPrependYieldsToRecyclerView() {
+        Dispatchers.resetMain() // reset MainDispatcherRule
+        // collection on immediate dispatcher to simulate real lifecycle dispatcher
+        val mainDispatcher = Dispatchers.Main.immediate
+        runTest {
+            val events = mutableListOf<String>()
+            val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+            val asyncDiffer =
+                AsyncPagingDataDiffer(
+                    diffCallback =
+                        object : DiffUtil.ItemCallback<Int>() {
+                            override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+
+                            override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+                        },
+                    // override default Dispatcher.Main with Dispatchers.main.immediate so that
+                    // main tasks run without queueing, we need this to simulate real life order of
+                    // events
+                    mainDispatcher = mainDispatcher,
+                    updateCallback = listUpdateCapture,
+                    workerDispatcher = backgroundScope.coroutineContext
+                )
+
+            val pager =
+                Pager(
+                    config =
+                        PagingConfig(
+                            pageSize = 5,
+                            enablePlaceholders = false,
+                            prefetchDistance = 3,
+                            initialLoadSize = 10,
+                            maxSize = 16
+                        ),
+                    initialKey = 30,
+                ) {
+                    TestPagingSource(loadDelay = 0)
+                }
+
+            val collectPager =
+                launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            assertThat(events).containsExactly("Inserted(position=0, count=10)")
+            events.clear()
+
+            // trigger a prepend that will be dropped later
+            asyncDiffer.getItem(0)
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            assertThat(events).containsExactly("Inserted(position=0, count=5)")
+            events.clear()
+
+            // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+            // from upstream should yield until dispatch layout completes or else
+            // LoadState-based RV updates will crash. See original bug b/150162465.
+            withContext(mainDispatcher) {
+                events.add("start dispatchLayout")
+                asyncDiffer.getItem(11)
+                // this triggers append which will cause prepend to drop
+                asyncDiffer.getItem(12)
+                events.add("end dispatchLayout")
+            }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            // make sure the append was not processed until RV is done with layouts
+            assertThat(events)
+                .containsExactly(
+                    "start dispatchLayout",
+                    "end dispatchLayout",
+                    "Removed(position=0, count=5)", // drop prepend
+                    "Inserted(position=10, count=5)" // append
+                )
+                .inOrder()
+            collectPager.cancel()
+        }
+    }
+
+    @Test
+    fun dropAppendYieldsToRecyclerView() {
+        Dispatchers.resetMain() // reset MainDispatcherRule
+        // collection on immediate dispatcher to simulate real lifecycle dispatcher
+        val mainDispatcher = Dispatchers.Main.immediate
+        runTest {
+            val events = mutableListOf<String>()
+            val listUpdateCapture = ListUpdateCapture { event -> events.add(event.toString()) }
+            val asyncDiffer =
+                AsyncPagingDataDiffer(
+                    diffCallback =
+                        object : DiffUtil.ItemCallback<Int>() {
+                            override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+
+                            override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
+                                return oldItem == newItem
+                            }
+                        },
+                    // override default Dispatcher.Main with Dispatchers.main.immediate so that
+                    // main tasks run without queueing, we need this to simulate real life order of
+                    // events
+                    mainDispatcher = mainDispatcher,
+                    updateCallback = listUpdateCapture,
+                    workerDispatcher = backgroundScope.coroutineContext
+                )
+
+            val pager =
+                Pager(
+                    config =
+                        PagingConfig(
+                            pageSize = 5,
+                            enablePlaceholders = false,
+                            prefetchDistance = 3,
+                            initialLoadSize = 10,
+                            maxSize = 16
+                        ),
+                    initialKey = 30,
+                ) {
+                    TestPagingSource(loadDelay = 0)
+                }
+
+            val collectPager =
+                launch(mainDispatcher) { pager.flow.collectLatest { asyncDiffer.submitData(it) } }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            assertThat(events).containsExactly("Inserted(position=0, count=10)")
+            events.clear()
+
+            // trigger a prepend that will be dropped later
+            asyncDiffer.getItem(9)
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            assertThat(events).containsExactly("Inserted(position=10, count=5)")
+            events.clear()
+
+            // Simulate RV dispatching layout which calls multi onBind --> getItem. LoadStateUpdates
+            // from upstream should yield until dispatch layout completes or else
+            // LoadState-based RV updates will crash. See original bug b/150162465.
+            withContext(mainDispatcher) {
+                events.add("start dispatchLayout")
+                asyncDiffer.getItem(3)
+                // this triggers prepend which will cause append to drop
+                asyncDiffer.getItem(2)
+                events.add("end dispatchLayout")
+            }
+
+            // wait till we get all expected events
+            asyncDiffer.loadStateFlow.awaitNotLoading()
+
+            // make sure the append was not processed until RV is done with layouts
+            assertThat(events)
+                .containsExactly(
+                    "start dispatchLayout",
+                    "end dispatchLayout",
+                    "Removed(position=10, count=5)", // drop append
+                    "Inserted(position=0, count=5)" // prepend
+                )
+                .inOrder()
+            collectPager.cancel()
+        }
+    }
+
+    @Test
     fun loadStateListenerYieldsToGetItem() {
         Dispatchers.resetMain() // reset MainDispatcherRule
         // collection on immediate dispatcher to simulate real lifecycle dispatcher
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
index 0f6a1d5..e39527a 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/ListUpdateCapture.kt
@@ -18,25 +18,26 @@
 
 import androidx.recyclerview.widget.ListUpdateCallback
 
-class ListUpdateCapture : ListUpdateCallback {
+class ListUpdateCapture(
+    private val events: MutableList<ListUpdateEvent> = mutableListOf(),
+    val callBack: (event: ListUpdateEvent) -> Unit = { events.add(it) },
+) : ListUpdateCallback {
     private var lastEventsListIndex = -1
 
-    val events = mutableListOf<ListUpdateEvent>()
-
     override fun onChanged(position: Int, count: Int, payload: Any?) {
-        events.add(ListUpdateEvent.Changed(position, count, payload))
+        callBack(ListUpdateEvent.Changed(position, count, payload))
     }
 
     override fun onMoved(fromPosition: Int, toPosition: Int) {
-        events.add(ListUpdateEvent.Moved(fromPosition, toPosition))
+        callBack(ListUpdateEvent.Moved(fromPosition, toPosition))
     }
 
     override fun onInserted(position: Int, count: Int) {
-        events.add(ListUpdateEvent.Inserted(position, count))
+        callBack(ListUpdateEvent.Inserted(position, count))
     }
 
     override fun onRemoved(position: Int, count: Int) {
-        events.add(ListUpdateEvent.Removed(position, count))
+        callBack(ListUpdateEvent.Removed(position, count))
     }
 
     fun newEvents(): List<ListUpdateEvent> {
diff --git a/paging/samples/build.gradle b/paging/samples/build.gradle
index f6bc2c0..a7b20e3 100644
--- a/paging/samples/build.gradle
+++ b/paging/samples/build.gradle
@@ -38,7 +38,7 @@
     compileOnly(project(":annotation:annotation-sampled"))
 
     implementation("androidx.appcompat:appcompat:1.2.0")
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation("androidx.fragment:fragment-ktx:1.3.0")
     implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0")
     implementation("androidx.recyclerview:recyclerview:1.2.0")
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt
index f0225b7..67db655 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkTestUtils.kt
@@ -27,10 +27,11 @@
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
 import androidx.privacysandbox.sdkruntime.core.Versions
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
+import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
 import java.lang.reflect.InvocationTargetException
 import java.lang.reflect.Proxy
+import java.lang.reflect.UndeclaredThrowableException
 import java.util.concurrent.CountDownLatch
-import kotlin.reflect.cast
 
 /** Extract value of [Versions.API_VERSION] from loaded SDK. */
 internal fun LocalSdkProvider.extractApiVersion(): Int = extractVersionValue("API_VERSION")
@@ -38,45 +39,34 @@
 /** Extract value of [Versions.CLIENT_VERSION] from loaded SDK. */
 internal fun LocalSdkProvider.extractClientVersion(): Int = extractVersionValue("CLIENT_VERSION")
 
+/** Extract value of static [versionFieldName] from [Versions] class. */
+private fun LocalSdkProvider.extractVersionValue(versionFieldName: String): Int =
+    extractSdkProviderClassloader()
+        .getClass(Versions::class.java.name)
+        .getStaticField(versionFieldName) as Int
+
 /** Extract [SandboxedSdkProviderCompat.context] from loaded SDK. */
-internal fun LocalSdkProvider.extractSdkContext(): Context {
-    val getContextMethod = sdkProvider.javaClass.getMethod("getContext")
-
-    val rawContext = getContextMethod.invoke(sdkProvider)
-
-    return Context::class.cast(rawContext)
-}
+internal fun LocalSdkProvider.extractSdkContext(): Context =
+    sdkProvider.callMethod("getContext") as Context
 
 /** Extract field value from [SandboxedSdkProviderCompat] */
 internal inline fun <reified T> LocalSdkProvider.extractSdkProviderFieldValue(
     fieldName: String
-): T {
-    return sdkProvider.javaClass.getField(fieldName).get(sdkProvider)!! as T
-}
+): T = sdkProvider.getField(fieldName) as T
 
 /** Extract classloader that was used for loading of [SandboxedSdkProviderCompat]. */
 internal fun LocalSdkProvider.extractSdkProviderClassloader(): ClassLoader =
     sdkProvider.javaClass.classLoader!!
 
-/**
- * Reflection wrapper for TestSDK object. Underlying TestSDK should implement and delegate to
- * [androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat]:
- * 1) getSandboxedSdks() : List<SandboxedSdkCompat>
- * 2) getAppOwnedSdkSandboxInterfaces() : List<AppOwnedSdkSandboxInterfaceCompat>
- * 3) registerSdkSandboxActivityHandler(SdkSandboxActivityHandlerCompat) : IBinder
- * 4) unregisterSdkSandboxActivityHandler(SdkSandboxActivityHandlerCompat)
- * 5) loadSdk(sdkName, sdkParams) : SandboxedSdkCompat
- */
-internal class TestSdkWrapper(private val sdk: Any) {
+/** Reflection wrapper for [SdkSandboxControllerCompat] */
+internal class SdkControllerWrapper(private val controller: Any) {
     fun loadSdk(sdkName: String, sdkParams: Bundle): SandboxedSdkWrapper {
-        val loadSdkMethod =
-            sdk.javaClass.getMethod("loadSdk", String::class.java, Bundle::class.java)
-
         try {
-            val rawSandboxedSdkCompat = loadSdkMethod.invoke(sdk, sdkName, sdkParams) as Any
+            val rawSandboxedSdkCompat =
+                controller.callSuspendMethod("loadSdk", sdkName, sdkParams) as Any
             return SandboxedSdkWrapper(rawSandboxedSdkCompat)
-        } catch (ex: InvocationTargetException) {
-            throw tryRebuildCompatException(ex.targetException)
+        } catch (ex: Exception) {
+            throw tryRebuildCompatException(ex)
         }
     }
 
@@ -90,53 +80,34 @@
     }
 
     fun getSandboxedSdks(): List<SandboxedSdkWrapper> {
-        val sdks = sdk.callMethod(methodName = "getSandboxedSdks") as List<*>
+        val sdks = controller.callMethod(methodName = "getSandboxedSdks") as List<*>
         return sdks.map { SandboxedSdkWrapper(it!!) }
     }
 
     fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkWrapper> {
-        val sdks = sdk.callMethod(methodName = "getAppOwnedSdkSandboxInterfaces") as List<*>
+        val sdks = controller.callMethod(methodName = "getAppOwnedSdkSandboxInterfaces") as List<*>
         return sdks.map { AppOwnedSdkWrapper(it!!) }
     }
 
     fun registerSdkSandboxActivityHandler(handler: CatchingSdkActivityHandler): IBinder {
-        val classLoader = sdk.javaClass.classLoader!!
-        val activityHandlerClass =
-            Class.forName(SdkSandboxActivityHandlerCompat::class.java.name, false, classLoader)
+        val classLoader = controller.javaClass.classLoader!!
 
         val proxy =
-            Proxy.newProxyInstance(classLoader, arrayOf(activityHandlerClass)) { proxy, method, args
-                ->
-                when (method.name) {
-                    "hashCode" -> hashCode()
-                    "equals" -> proxy === args[0]
-                    "onActivityCreated" -> handler.setResult(args[0])
-                    else -> {
-                        throw UnsupportedOperationException(
-                            "Unexpected method call object:$proxy, method: $method, args: $args"
-                        )
-                    }
-                }
+            classLoader.createProxyFor(
+                SdkSandboxActivityHandlerCompat::class.java.name,
+                "onActivityCreated"
+            ) {
+                handler.setResult(it!![0]!!)
             }
 
-        val registerMethod =
-            sdk.javaClass.getMethod("registerSdkSandboxActivityHandler", activityHandlerClass)
-
-        val token = registerMethod.invoke(sdk, proxy) as IBinder
+        val token = controller.callMethod("registerSdkSandboxActivityHandler", proxy) as IBinder
         handler.proxy = proxy
 
         return token
     }
 
     fun unregisterSdkSandboxActivityHandler(handler: CatchingSdkActivityHandler) {
-        val classLoader = sdk.javaClass.classLoader!!
-        val activityHandlerClass =
-            Class.forName(SdkSandboxActivityHandlerCompat::class.java.name, false, classLoader)
-
-        val unregisterMethod =
-            sdk.javaClass.getMethod("unregisterSdkSandboxActivityHandler", activityHandlerClass)
-
-        unregisterMethod.invoke(sdk, handler.proxy)
+        controller.callMethod("unregisterSdkSandboxActivityHandler", handler.proxy)
         handler.proxy = null
     }
 }
@@ -184,8 +155,8 @@
 }
 
 /**
- * ActivityHandler to use with [TestSdkWrapper.registerSdkSandboxActivityHandler]. Store received
- * ActivityHolder.
+ * ActivityHandler to use with [SdkControllerWrapper.registerSdkSandboxActivityHandler]. Store
+ * received ActivityHolder.
  */
 internal class CatchingSdkActivityHandler {
     var proxy: Any? = null
@@ -217,30 +188,146 @@
     }
 }
 
-/**
- * Load SDK and wrap it as TestSDK.
- *
- * @see [TestSdkWrapper]
- */
-internal fun LocalSdkProvider.loadTestSdk(): TestSdkWrapper {
-    return onLoadSdk(Bundle()).asTestSdk()
+/** Create [SandboxedSdkWrapper] using SDK context from [SandboxedSdkProviderCompat.context]. */
+internal fun LocalSdkProvider.loadTestSdk(): SdkControllerWrapper {
+    return createSdkControllerWrapperFor(extractSdkProviderClassloader(), extractSdkContext())
 }
 
 /**
- * Wrap SandboxedSdkCompat as TestSDK.
+ * Create [SandboxedSdkWrapper] using SDK context from TestSDK.
+ *
+ * TestSDK must expose public "context" property.
  *
  * @see [SandboxedSdkWrapper]
  */
-internal fun SandboxedSdkCompat.asTestSdk(): TestSdkWrapper {
-    return TestSdkWrapper(sdk = getInterface()!!)
+internal fun SandboxedSdkCompat.asTestSdk(): SdkControllerWrapper {
+    val testSdk = getInterface()!!
+    val context = testSdk.callMethod("getContext")!!
+    return createSdkControllerWrapperFor(testSdk.javaClass.classLoader!!, context)
 }
 
-private fun Any.callMethod(methodName: String): Any? {
-    return javaClass.getMethod(methodName).invoke(this)
+/**
+ * Creates [SdkControllerWrapper] for instance of [SdkSandboxControllerCompat] class loaded by SDK
+ * classloader.
+ */
+private fun createSdkControllerWrapperFor(
+    classLoader: ClassLoader,
+    sdkContext: Any
+): SdkControllerWrapper {
+    val sdkController =
+        classLoader
+            .getClass(SdkSandboxControllerCompat::class.java.name)
+            .callStaticMethod("from", sdkContext)!!
+    return SdkControllerWrapper(sdkController)
 }
 
-private fun LocalSdkProvider.extractVersionValue(versionFieldName: String): Int {
-    val versionsClass =
-        Class.forName(Versions::class.java.name, false, extractSdkProviderClassloader())
-    return versionsClass.getDeclaredField(versionFieldName).get(null) as Int
+private fun ClassLoader.getClass(className: String): Class<*> =
+    Class.forName(className, false, this)
+
+private fun Class<*>.getStaticField(fieldName: String): Any? = getDeclaredField(fieldName).get(null)
+
+private fun Any.getField(fieldName: String): Any? = javaClass.getField(fieldName).get(this)
+
+/**
+ * Call static method and return result. Unwraps [InvocationTargetException] to actual exception.
+ */
+private fun Class<*>.callStaticMethod(methodName: String, vararg args: Any?): Any? {
+    try {
+        return methods.single { it.name == methodName }.invoke(null, *args)
+    } catch (ex: InvocationTargetException) {
+        throw ex.targetException
+    }
+}
+
+/** Call method and return result. Unwraps [InvocationTargetException] to actual exception. */
+private fun Any.callMethod(methodName: String, vararg args: Any?): Any? {
+    try {
+        return javaClass.methods.single { it.name == methodName }.invoke(this, *args)
+    } catch (ex: InvocationTargetException) {
+        throw ex.targetException
+    }
+}
+
+/**
+ * Call suspend method and wait for result (via runBlocking).
+ *
+ * KCallable#callSuspend can't be used here as it will pass Continuation instance loaded by app
+ * classloader while SDK will expect instance loaded by SDK classloader.
+ *
+ * Instead this method calls runBlocking() using classes loaded by SDK classloader.
+ */
+private fun Any.callSuspendMethod(methodName: String, vararg args: Any?): Any? {
+    val classLoader = javaClass.classLoader!!
+
+    val method = javaClass.methods.single { it.name == methodName }
+
+    val coroutineContextClass = classLoader.getClass("kotlin.coroutines.CoroutineContext")
+    val functionClass = classLoader.getClass("kotlin.jvm.functions.Function2")
+    val runBlockingMethod =
+        classLoader
+            .getClass("kotlinx.coroutines.BuildersKt")
+            .getMethod("runBlocking", coroutineContextClass, functionClass)
+
+    val coroutineContextInstance =
+        classLoader.getClass("kotlin.coroutines.EmptyCoroutineContext").getStaticField("INSTANCE")
+
+    val functionProxy =
+        classLoader.createProxyFor("kotlin.jvm.functions.Function2", "invoke") {
+            try {
+                // Receive Continuation as second function argument and pass it as last method
+                // argument
+                method.invoke(this, *args, it!![1])
+            } catch (ex: InvocationTargetException) {
+                // Rethrow original exception to correctly handle it later.
+                throw ex.targetException
+            }
+        }
+
+    try {
+        return runBlockingMethod.invoke(null, coroutineContextInstance, functionProxy)
+    } catch (ex: InvocationTargetException) {
+        // First unwrap InvocationTargetException to get exception while calling runBlocking
+        val runBlockingException = ex.targetException
+
+        // runBlocking doesn't declare exceptions, need to unwrap actual method exception
+        if (runBlockingException is UndeclaredThrowableException) {
+            throw runBlockingException.undeclaredThrowable
+        } else {
+            throw ex
+        }
+    }
+}
+
+/** Create Dynamic Proxy that handles single [methodName]. */
+private fun ClassLoader.createProxyFor(
+    className: String,
+    methodName: String,
+    handler: (args: Array<Any?>?) -> Any?
+): Any {
+    return createProxyFor(className) { calledMethodName, args ->
+        if (calledMethodName == methodName) {
+            handler.invoke(args)
+        } else {
+            throw UnsupportedOperationException(
+                "Unexpected method call: $calledMethodName, args: $args"
+            )
+        }
+    }
+}
+
+/** Create Dynamic Proxy that handles all methods of [className]. */
+private fun ClassLoader.createProxyFor(
+    className: String,
+    handler: (methodName: String, args: Array<Any?>?) -> Any?
+): Any {
+    return Proxy.newProxyInstance(this, arrayOf(getClass(className))) { proxy, method, args ->
+        when (method.name) {
+            "equals" -> proxy === args?.get(0)
+            "hashCode" -> hashCode()
+            "toString" -> toString()
+            else -> {
+                handler.invoke(method.name, args)
+            }
+        }
+    }
 }
diff --git a/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index 8fa12f8..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -19,15 +19,10 @@
 import android.content.Context
 import android.os.Binder
 import android.os.Bundle
-import android.os.IBinder
 import android.view.View
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
-import kotlinx.coroutines.runBlocking
 
 @Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
 class CompatProvider : SandboxedSdkProviderCompat() {
@@ -39,7 +34,7 @@
 
     @Throws(LoadSdkCompatException::class)
     override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
-        val result = CurrentVersionSdkTest(context!!)
+        val result = SdkImpl(context!!)
         onLoadSdkBinder = result
 
         lastOnLoadSdkParams = params
@@ -57,22 +52,8 @@
         return View(windowContext)
     }
 
-    internal class CurrentVersionSdkTest(private val context: Context) : Binder() {
-        fun getSandboxedSdks(): List<SandboxedSdkCompat> =
-            SdkSandboxControllerCompat.from(context).getSandboxedSdks()
-
-        fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
-            SdkSandboxControllerCompat.from(context).getAppOwnedSdkSandboxInterfaces()
-
-        fun registerSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat): IBinder =
-            SdkSandboxControllerCompat.from(context).registerSdkSandboxActivityHandler(handler)
-
-        fun unregisterSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat) {
-            SdkSandboxControllerCompat.from(context).unregisterSdkSandboxActivityHandler(handler)
-        }
-
-        fun loadSdk(sdkName: String, sdkParams: Bundle): SandboxedSdkCompat = runBlocking {
-            SdkSandboxControllerCompat.from(context).loadSdk(sdkName, sdkParams)
-        }
-    }
+    internal class SdkImpl(
+        @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+        val context: Context
+    ) : Binder()
 }
diff --git a/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index 3df8a54..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -23,7 +23,6 @@
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
 
 @Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
 class CompatProvider : SandboxedSdkProviderCompat() {
@@ -53,8 +52,8 @@
         return View(windowContext)
     }
 
-    internal class SdkImpl(private val context: Context) : Binder() {
-        fun getSandboxedSdks(): List<SandboxedSdkCompat> =
-            SdkSandboxControllerCompat.from(context).getSandboxedSdks()
-    }
+    internal class SdkImpl(
+        @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+        val context: Context
+    ) : Binder()
 }
diff --git a/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index b115b53..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -19,14 +19,10 @@
 import android.content.Context
 import android.os.Binder
 import android.os.Bundle
-import android.os.IBinder
 import android.view.View
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
 
 @Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
 class CompatProvider : SandboxedSdkProviderCompat() {
@@ -56,18 +52,8 @@
         return View(windowContext)
     }
 
-    internal class SdkImpl(private val context: Context) : Binder() {
-        fun getSandboxedSdks(): List<SandboxedSdkCompat> =
-            SdkSandboxControllerCompat.from(context).getSandboxedSdks()
-
-        fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
-            SdkSandboxControllerCompat.from(context).getAppOwnedSdkSandboxInterfaces()
-
-        fun registerSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat): IBinder =
-            SdkSandboxControllerCompat.from(context).registerSdkSandboxActivityHandler(handler)
-
-        fun unregisterSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat) {
-            SdkSandboxControllerCompat.from(context).unregisterSdkSandboxActivityHandler(handler)
-        }
-    }
+    internal class SdkImpl(
+        @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+        val context: Context
+    ) : Binder()
 }
diff --git a/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index 62484c6..a7bcdfc 100644
--- a/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -19,15 +19,10 @@
 import android.content.Context
 import android.os.Binder
 import android.os.Bundle
-import android.os.IBinder
 import android.view.View
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
-import kotlinx.coroutines.runBlocking
 
 @Suppress("unused") // Reflection usage from tests in privacysandbox:sdkruntime:sdkruntime-client
 class CompatProvider : SandboxedSdkProviderCompat() {
@@ -57,22 +52,8 @@
         return View(windowContext)
     }
 
-    internal class SdkImpl(private val context: Context) : Binder() {
-        fun getSandboxedSdks(): List<SandboxedSdkCompat> =
-            SdkSandboxControllerCompat.from(context).getSandboxedSdks()
-
-        fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
-            SdkSandboxControllerCompat.from(context).getAppOwnedSdkSandboxInterfaces()
-
-        fun registerSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat): IBinder =
-            SdkSandboxControllerCompat.from(context).registerSdkSandboxActivityHandler(handler)
-
-        fun unregisterSdkSandboxActivityHandler(handler: SdkSandboxActivityHandlerCompat) {
-            SdkSandboxControllerCompat.from(context).unregisterSdkSandboxActivityHandler(handler)
-        }
-
-        fun loadSdk(sdkName: String, sdkParams: Bundle): SandboxedSdkCompat = runBlocking {
-            SdkSandboxControllerCompat.from(context).loadSdk(sdkName, sdkParams)
-        }
-    }
+    internal class SdkImpl(
+        @Suppress("MemberVisibilityCanBePrivate") // Reflection usage from LocalSdkTestUtils
+        val context: Context
+    ) : Binder()
 }
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
index d778033..dbc7d6e 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
@@ -28,7 +28,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.GridView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -2128,7 +2127,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static boolean isAccessibilityFocused(@NonNull View view) {
             return view.isAccessibilityFocused();
         }
diff --git a/room/integration-tests/testapp/lint-baseline.xml b/room/integration-tests/testapp/lint-baseline.xml
index 171a19c..2daddb0 100644
--- a/room/integration-tests/testapp/lint-baseline.xml
+++ b/room/integration-tests/testapp/lint-baseline.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha12" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha12)" variant="all" version="8.4.0-alpha12">
+<issues format="6" by="lint 8.6.0-alpha07" type="baseline" client="gradle" dependencies="false"
+    name="AGP (8.6.0-alpha07)" variant="all" version="8.6.0-alpha07">
 
     <issue
         id="BanThreadSleep"
@@ -100,6 +101,13 @@
             file="src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerBehavioralTest.java"/>
     </issue>
 
+    <issue id="BanThreadSleep" message="Uses Thread.sleep()"
+        errorLine1="                        Thread.sleep(200);"
+        errorLine2="                               ~~~~~">
+        <location
+            file="src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java" />
+    </issue>
+
     <issue
         id="MissingTestSizeAnnotation"
         message="Missing test size annotation"
@@ -181,6 +189,22 @@
             file="src/main/java/androidx/room/integration/testapp/database/CustomerDao.java"/>
     </issue>
 
+    <issue id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
+        errorLine1="    boolean contains(int id, String name, String lastName);"
+        errorLine2="                             ~~~~~~">
+        <location
+            file="src/main/java/androidx/room/integration/testapp/database/CustomerDao.java" />
+    </issue>
+
+    <issue id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
+        errorLine1="    boolean contains(int id, String name, String lastName);"
+        errorLine2="                                          ~~~~~~">
+        <location
+            file="src/main/java/androidx/room/integration/testapp/database/CustomerDao.java" />
+    </issue>
+
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
@@ -301,33 +325,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static Intent intentFor(Context context, String databaseName) {"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static Intent intentFor(Context context, String databaseName) {"
-        errorLine2="                                   ~~~~~~~">
-        <location
-            file="src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static Intent intentFor(Context context, String databaseName) {"
-        errorLine2="                                                    ~~~~~~">
-        <location
-            file="src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public abstract ProductFtsDao getProductDao();"
         errorLine2="                    ~~~~~~~~~~~~~">
         <location
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
index 43e65e1..2df7d0e 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
@@ -16,21 +16,15 @@
 
 package androidx.room.integration.testapp.test;
 
-import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.Context;
-import android.os.SystemClock;
 
 import androidx.annotation.NonNull;
+import androidx.arch.core.executor.ArchTaskExecutor;
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
 import androidx.collection.SimpleArrayMap;
-import androidx.core.util.Pair;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.Observer;
 import androidx.room.InvalidationTracker;
@@ -39,7 +33,6 @@
 import androidx.room.integration.testapp.ISampleDatabaseService;
 import androidx.room.integration.testapp.SampleDatabaseService;
 import androidx.room.integration.testapp.database.Customer;
-import androidx.room.integration.testapp.database.CustomerDao;
 import androidx.room.integration.testapp.database.Description;
 import androidx.room.integration.testapp.database.Product;
 import androidx.room.integration.testapp.database.SampleDatabase;
@@ -52,14 +45,12 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -119,30 +110,29 @@
         if (mService != null) {
             serviceRule.unbindService();
         }
-        for (int i = 0, size = mDatabases.size(); i < size; i++) {
-            mDatabases.get(i).close();
+        for (RoomDatabase db : mDatabases) {
+            db.close();
         }
-        mExecutorRule.drainTasks(2, TimeUnit.SECONDS);
+        mExecutorRule.drainTasks(3, TimeUnit.SECONDS);
+        assertWithMessage("Executor isIdle")
+                .that(mExecutorRule.isIdle())
+                .isTrue();
     }
 
-    // TODO(324609478): broken test
-    @Ignore
     @Test
     public void invalidateInAnotherInstance() throws Exception {
         final SampleDatabase db1 = openDatabase(true);
         final SampleDatabase db2 = openDatabase(true);
 
         final CountDownLatch invalidated1 = prepareTableObserver(db1);
-        final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
 
         db2.getCustomerDao().insert(CUSTOMER_1);
 
-        assertTrue(invalidated1.await(3, TimeUnit.SECONDS));
-        assertTrue(changed1.await(3, TimeUnit.SECONDS));
+        assertWithMessage("Observer invalidation")
+                .that(invalidated1.await(3, TimeUnit.SECONDS))
+                .isTrue();
     }
 
-    // TODO(324274513): broken test
-    @Ignore
     @Test
     public void invalidateInAnotherInstanceFts() throws Exception {
         final SampleFtsDatabase db1 = openFtsDatabase(true);
@@ -162,10 +152,13 @@
 
         db2.getProductDao().addDescription(new Description(1, "Wonderful candy."));
 
-        assertTrue(changed.await(3, TimeUnit.SECONDS));
+        assertWithMessage("Observer invalidation")
+                .that(changed.await(3, TimeUnit.SECONDS))
+                .isTrue();
+
         List<Product> result = db1.getProductDao().getProductsWithDescription("candy");
-        assertThat(result.size(), is(1));
-        assertThat(result.get(0), is(theProduct));
+        assertThat(result).hasSize(1);
+        assertThat(result).containsExactly(theProduct);
     }
 
     @Test
@@ -174,18 +167,17 @@
         final SampleDatabase db2 = openDatabase(false);
 
         final CountDownLatch invalidated1 = prepareTableObserver(db1);
-        final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
         final CountDownLatch invalidated2 = prepareTableObserver(db2);
-        final CountDownLatch changed2 = prepareLiveDataObserver(db2).first;
 
         db2.getCustomerDao().insert(CUSTOMER_1);
 
-        assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
-        assertTrue(changed2.await(3, TimeUnit.SECONDS));
+        assertWithMessage("Observer invalidation")
+                .that(invalidated1.await(200, TimeUnit.MILLISECONDS))
+                .isFalse();
 
-        assertFalse(invalidated1.await(300, TimeUnit.MILLISECONDS));
-        assertFalse(changed1.await(300, TimeUnit.MILLISECONDS));
-        assertThat(db1.getCustomerDao().countCustomers(), is(1));
+        assertWithMessage("Observer invalidation")
+                .that(invalidated2.await(3, TimeUnit.SECONDS))
+                .isTrue();
     }
 
     @Test
@@ -194,22 +186,19 @@
         final SampleDatabase db2 = openDatabase(true); // Enabled only on one side
 
         final CountDownLatch invalidated1 = prepareTableObserver(db1);
-        final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
         final CountDownLatch invalidated2 = prepareTableObserver(db2);
-        final CountDownLatch changed2 = prepareLiveDataObserver(db2).first;
 
         db2.getCustomerDao().insert(CUSTOMER_1);
 
-        assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
-        assertTrue(changed2.await(3, TimeUnit.SECONDS));
+        assertWithMessage("Observer invalidation")
+                .that(invalidated1.await(200, TimeUnit.MILLISECONDS))
+                .isFalse();
 
-        assertFalse(invalidated1.await(300, TimeUnit.MILLISECONDS));
-        assertFalse(changed1.await(300, TimeUnit.MILLISECONDS));
-        assertThat(db1.getCustomerDao().countCustomers(), is(1));
+        assertWithMessage("Observer invalidation")
+                .that(invalidated2.await(3, TimeUnit.SECONDS))
+                .isTrue();
     }
 
-    // TODO(335890993): broken test
-    @Ignore
     @Test
     public void invalidationInAnotherInstance_closed() throws Exception {
         final SampleDatabase db1 = openDatabase(true);
@@ -217,179 +206,66 @@
         final SampleDatabase db3 = openDatabase(true);
 
         final CountDownLatch invalidated1 = prepareTableObserver(db1);
-        final Pair<CountDownLatch, CountDownLatch> changed1 = prepareLiveDataObserver(db1);
         final CountDownLatch invalidated2 = prepareTableObserver(db2);
-        final Pair<CountDownLatch, CountDownLatch> changed2 = prepareLiveDataObserver(db2);
         final CountDownLatch invalidated3 = prepareTableObserver(db3);
-        final Pair<CountDownLatch, CountDownLatch> changed3 = prepareLiveDataObserver(db3);
-
-        db2.getCustomerDao().insert(CUSTOMER_1);
-
-        assertTrue(invalidated1.await(3, TimeUnit.SECONDS));
-        assertTrue(changed1.first.await(3, TimeUnit.SECONDS));
-        assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
-        assertTrue(changed2.first.await(3, TimeUnit.SECONDS));
-        assertTrue(invalidated3.await(3, TimeUnit.SECONDS));
-        assertTrue(changed3.first.await(3, TimeUnit.SECONDS));
 
         db3.close();
-        db2.getCustomerDao().insert(CUSTOMER_2);
-
-        assertTrue(changed1.second.await(3, TimeUnit.SECONDS));
-        assertTrue(changed2.second.await(3, TimeUnit.SECONDS));
-        assertFalse(changed3.second.await(3, TimeUnit.SECONDS));
-    }
-
-    @Ignore // Flaky b/330519843
-    @Test
-    public void invalidationCausesNoLoop() throws Exception {
-        final SampleDatabase db1 = openDatabase(true);
-        final SampleDatabase db2 = openDatabase(true);
-
-        final CountDownLatch invalidated1 = prepareTableObserver(db1);
-        final CountDownLatch changed1 = prepareLiveDataObserver(db1).first;
-
         db2.getCustomerDao().insert(CUSTOMER_1);
-        mExecutorRule.drainTasks(300, TimeUnit.MILLISECONDS);
 
-        assertFalse(invalidated1.await(300, TimeUnit.MILLISECONDS));
-        assertFalse(changed1.await(300, TimeUnit.MILLISECONDS));
+        assertWithMessage("Observer invalidation")
+                .that(invalidated1.await(3, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Observer invalidation")
+                .that(invalidated2.await(3, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Observer invalidation")
+                .that(invalidated3.await(200, TimeUnit.MILLISECONDS))
+                .isFalse();
     }
 
     @Test
-    public void reopen() throws Exception {
-        final SampleDatabase db1 = openDatabase(true);
-        final Product product = new Product();
-        product.setId(1);
-        product.setName("A");
-        db1.getProductDao().insert(product);
-        db1.close();
-        final SampleDatabase db2 = openDatabase(true);
-        final CountDownLatch invalidated2 = prepareTableObserver(db2);
-        final CountDownLatch changed2 = prepareLiveDataObserver(db2).first;
-        db2.getCustomerDao().insert(CUSTOMER_1);
-        assertTrue(invalidated2.await(3, TimeUnit.SECONDS));
-        assertTrue(changed2.await(3, TimeUnit.SECONDS));
-    }
-
-    // TODO(186405912): broken test
-    @Ignore
-    @Test
     public void invalidatedByAnotherProcess() throws Exception {
         bindTestService();
+
         final SampleDatabase db = openDatabase(true);
-        assertThat(db.getCustomerDao().countCustomers(), is(0));
+        assertThat(db.getCustomerDao().countCustomers()).isEqualTo(0);
 
         final CountDownLatch invalidated = prepareTableObserver(db);
-        final CountDownLatch changed = prepareLiveDataObserver(db).first;
 
         mService.insertCustomer(CUSTOMER_1.getId(), CUSTOMER_1.getName(), CUSTOMER_1.getLastName());
 
-        assertTrue(invalidated.await(3, TimeUnit.SECONDS));
-        assertTrue(changed.await(3, TimeUnit.SECONDS));
+        assertWithMessage("Observer invalidation")
+                .that(invalidated.await(3, TimeUnit.SECONDS))
+                .isTrue();
     }
 
-    // TODO(186405912): broken test
-    @Ignore
     @Test
     public void invalidateAnotherProcess() throws Exception {
         bindTestService();
+
         final SampleDatabase db = openDatabase(true);
-        db.getCustomerDao().insert(CUSTOMER_1);
-        assertTrue(mService.waitForCustomer(CUSTOMER_1.getId(), CUSTOMER_1.getName(),
-                CUSTOMER_1.getLastName()));
-    }
 
-    // TODO: b/72877822 Better performance measurement
-    @Ignore
-    @Test
-    public void performance_oneByOne() {
-        final List<Customer> customers = generateCustomers(100);
-        final long[] elapsed = measureSeveralTimesEach(false, customers);
-        final String message = createMessage(elapsed);
-        assertThat(message, (double) elapsed[1], is(lessThan(elapsed[0] * 1.2)));
-    }
+        ArchTaskExecutor.getIOThreadExecutor().execute(
+                () -> {
+                    // This sleep is needed to wait for the IPC of waitForCustomer() to
+                    // reach the other process and to let the observer in the other process
+                    // subscribe to invalidation. If we insert before the other process registers
+                    // the observer then we'll miss the observer.
+                    try {
+                        Thread.sleep(200);
+                    } catch (InterruptedException e) {
+                        // no-op
+                    }
+                    db.getCustomerDao().insert(CUSTOMER_1);
+                }
+        );
 
-    // TODO: b/72877822 Better performance measurement
-    @Ignore
-    @Test
-    public void performance_bulk() {
-        final List<Customer> customers = generateCustomers(10000);
-        final long[] elapsed = measureSeveralTimesEach(true, customers);
-        final String message = createMessage(elapsed);
-        assertThat(message, (double) elapsed[1], is(lessThan(elapsed[0] * 1.2)));
-    }
-
-    private static String createMessage(long[] elapsed) {
-        return "Without multi-instance invalidation: " + elapsed[0] + " ms, "
-                + "with multi-instance invalidation: " + elapsed[1] + " ms.";
-    }
-
-    private List<Customer> generateCustomers(int count) {
-        final ArrayList<Customer> customers = new ArrayList<>();
-        for (int i = 0; i < count; i++) {
-            final Customer customer = new Customer();
-            int id = i + 1;
-            customer.setId(id);
-            customer.setName("Name" + id);
-            customer.setLastName("LastName" + id);
-            customers.add(customer);
-        }
-        return customers;
-    }
-
-    private long[] measureSeveralTimesEach(boolean bulk, List<Customer> customers) {
-        final int n = 10;
-        final long[] results1 = new long[n];
-        final long[] results2 = new long[n];
-        for (int i = 0; i < n; i++) {
-            results2[i] = measure(true, bulk, customers);
-            results1[i] = measure(false, bulk, customers);
-        }
-
-        // Median
-        Arrays.sort(results1);
-        Arrays.sort(results2);
-        final long result1 = results1[results1.length / 2];
-        final long result2 = results2[results2.length / 2];
-
-        return new long[]{result1, result2};
-    }
-
-
-    @SuppressWarnings("deprecation")
-    private long measure(boolean multiInstanceInvalidation, boolean bulk,
-            List<Customer> customers) {
-        final Context context = ApplicationProvider.getApplicationContext();
-        context.deleteDatabase(mDatabaseName);
-        final SampleDatabase db = openDatabase(multiInstanceInvalidation);
-        final CustomerDao dao = db.getCustomerDao();
-        final InvalidationTracker.Observer observer = new InvalidationTracker.Observer("Customer") {
-            @Override
-            public void onInvalidated(Set<String> tables) {
-                // This observer is only for creating triggers.
-            }
-        };
-        db.getInvalidationTracker().addObserver(observer);
-        final long start = SystemClock.currentThreadTimeMillis();
-        if (bulk) {
-            db.beginTransaction();
-        }
-        try {
-            for (int i = 0, size = customers.size(); i < size; i++) {
-                dao.insert(customers.get(i));
-            }
-            if (bulk) {
-                db.setTransactionSuccessful();
-            }
-        } finally {
-            if (bulk) {
-                db.endTransaction();
-            }
-        }
-        long elapsed = SystemClock.currentThreadTimeMillis() - start;
-        db.getInvalidationTracker().removeObserver(observer);
-        return elapsed;
+        boolean awaitResult = mService.waitForCustomer(
+                CUSTOMER_1.getId(), CUSTOMER_1.getName(), CUSTOMER_1.getLastName());
+        assertWithMessage(
+                "Observer invalidation in another process")
+                .that(awaitResult)
+                .isTrue();
     }
 
     private SampleDatabase openDatabase(boolean multiInstanceInvalidation) {
@@ -430,40 +306,12 @@
         db.getInvalidationTracker()
                 .addObserver(new InvalidationTracker.Observer("Customer", "Product") {
                     @Override
-                    public void onInvalidated(Set<String> tables) {
-                        assertThat(tables, hasSize(1));
-                        assertThat(tables, hasItem("Customer"));
+                    public void onInvalidated(@NonNull Set<String> tables) {
+                        assertThat(tables).hasSize(1);
+                        assertThat(tables).contains("Customer");
                         invalidated.countDown();
                     }
                 });
         return invalidated;
     }
-
-    private Pair<CountDownLatch, CountDownLatch> prepareLiveDataObserver(SampleDatabase db)
-            throws InterruptedException {
-        final CountDownLatch initialized = new CountDownLatch(1);
-        final CountDownLatch changedFirst = new CountDownLatch(1);
-        final CountDownLatch changedSecond = new CountDownLatch(1);
-        final Observer<List<Customer>> observer = customers -> {
-            if (customers == null || customers.isEmpty()) {
-                initialized.countDown();
-            } else if (changedFirst.getCount() > 0) {
-                assertThat(customers, hasSize(1));
-                assertThat(customers, hasItem(CUSTOMER_1));
-                changedFirst.countDown();
-            } else {
-                assertThat(customers, hasSize(2));
-                assertThat(customers, hasItem(CUSTOMER_1));
-                assertThat(customers, hasItem(CUSTOMER_2));
-                changedSecond.countDown();
-            }
-        };
-        final LiveData<List<Customer>> customers = db.getCustomerDao().all();
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
-                customers.observeForever(observer));
-        mObservers.put(customers, observer);
-        // Make sure that this observer is ready before inserting an item in another instance.
-        initialized.await(3, TimeUnit.SECONDS);
-        return new Pair<>(changedFirst, changedSecond);
-    }
 }
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java
index 38de612..c2b92b4 100644
--- a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/SampleDatabaseService.java
@@ -19,20 +19,18 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Process;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
 import androidx.room.ExperimentalRoomApi;
+import androidx.room.InvalidationTracker;
 import androidx.room.Room;
 import androidx.room.integration.testapp.database.Customer;
 import androidx.room.integration.testapp.database.SampleDatabase;
 
-import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -61,25 +59,23 @@
 
         @Override
         public boolean waitForCustomer(int id, String name, String lastName) {
-            final Customer customer = new Customer();
-            customer.setId(id);
-            customer.setName(name);
-            customer.setLastName(lastName);
             final CountDownLatch changed = new CountDownLatch(1);
-            final Observer<List<Customer>> observer = list -> {
-                if (list != null && list.size() >= 1 && list.contains(customer)) {
-                    changed.countDown();
+            final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
+                    Customer.class.getSimpleName()) {
+                @Override
+                public void onInvalidated(@NonNull Set<String> tables) {
+                    if (mDatabase.getCustomerDao().contains(id, name, lastName)) {
+                        changed.countDown();
+                    }
                 }
             };
-            final LiveData<List<Customer>> customers = mDatabase.getCustomerDao().all();
-            final Handler handler = new Handler(Looper.getMainLooper());
-            handler.post(() -> customers.observeForever(observer));
+            mDatabase.getInvalidationTracker().addObserver(observer);
             try {
                 return changed.await(3, TimeUnit.SECONDS);
             } catch (InterruptedException e) {
                 return false;
             } finally {
-                handler.post(() -> customers.removeObserver(observer));
+                mDatabase.getInvalidationTracker().removeObserver(observer);
             }
         }
     };
@@ -93,11 +89,12 @@
 
     /**
      * Creates the test service for the given database name
-     * @param context The context to creat the intent
+     * @param context The context to create the intent
      * @param databaseName The database name to be used
      * @return A new intent that can be used to connect to this service
      */
-    public static Intent intentFor(Context context, String databaseName) {
+    @NonNull
+    public static Intent intentFor(@NonNull Context context, @NonNull String databaseName) {
         Intent intent = new Intent(context, SampleDatabaseService.class);
         intent.putExtra(DATABASE_NAME_PARAM, databaseName);
         return intent;
@@ -109,7 +106,7 @@
     public IBinder onBind(Intent intent) {
         String databaseName = intent.getStringExtra(DATABASE_NAME_PARAM);
         if (databaseName == null) {
-            throw new IllegalArgumentException("must pass database name in the intent");
+            throw new IllegalArgumentException("Must pass database name in the intent");
         }
         if (mDatabase != null) {
             throw new IllegalStateException("Cannot re-use the same service for different tests");
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
index 7ab9523..14bc4691 100644
--- a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
@@ -54,4 +54,10 @@
      */
     @Query("SELECT * FROM customer")
     LiveData<List<Customer>> all();
+
+    /**
+     * @return True if customer is found
+     */
+    @Query("SELECT 1 FROM customer WHERE mId = :id AND mName = :name AND mLastName = :lastName")
+    boolean contains(int id, String name, String lastName);
 }
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
index 10605d2..a5d8e0f 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
@@ -19,14 +19,16 @@
 import android.app.ActivityManager
 import android.content.Context
 import android.os.Build
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule
 import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
 import androidx.room.support.AutoClosingRoomOpenHelper
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SdkSuppress
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlin.test.Test
-import org.junit.Before
+import org.junit.Rule
 
 class MultiInstanceInvalidationTest {
     @Entity data class SampleEntity(@PrimaryKey val pk: Int)
@@ -34,44 +36,52 @@
     @Database(entities = [SampleEntity::class], version = 1, exportSchema = false)
     abstract class SampleDatabase : RoomDatabase()
 
-    private lateinit var autoCloseDb: SampleDatabase
+    @get:Rule val countingTaskExecutorRule = CountingTaskExecutorRule()
 
-    @Suppress("DEPRECATION") // For `getRunningServices()`
     @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
-    fun invalidateInAnotherInstanceAutoCloser() {
-        val latch = CountDownLatch(1)
-        val context = ApplicationProvider.getApplicationContext<Context>()
-        val manager = context.getSystemService(ActivityManager::class.java)
-        val autoCloseHelper = autoCloseDb.openHelper as AutoClosingRoomOpenHelper
-        val autoCloser = autoCloseHelper.autoCloser
-        autoCloseHelper.writableDatabase
-        // Make sure the service is running.
-        assertThat(manager.getRunningServices(100)).isNotEmpty()
-
-        // Let Room call setAutoCloseCallback
-        val trackerCallback = autoCloser.onAutoCloseCallback
-        autoCloser.setAutoCloseCallback {
-            trackerCallback?.run()
-            // At this point in time InvalidationTracker's callback has run and unbind should have
-            // been invoked.
-            latch.countDown()
-        }
-        latch.await()
-
-        // Make sure the service is no longer running.
-        assertThat(manager.getRunningServices(100)).isEmpty()
-        autoCloseDb.close()
-    }
-
     @OptIn(ExperimentalRoomApi::class)
-    @Before
-    fun initDb() {
-        val context: Context = ApplicationProvider.getApplicationContext()
-        autoCloseDb =
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    @Suppress("DEPRECATION") // For getRunningServices()
+    fun invalidateInAnotherInstanceAutoCloser() {
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val autoCloseDb =
             Room.databaseBuilder(context, SampleDatabase::class.java, "MyDb")
                 .enableMultiInstanceInvalidation()
                 .setAutoCloseTimeout(200, TimeUnit.MILLISECONDS)
                 .build()
+        val manager = context.getSystemService(ActivityManager::class.java)
+        val autoCloseHelper = autoCloseDb.openHelper as AutoClosingRoomOpenHelper
+
+        // Force open the database causing the multi-instance invalidation service  to start
+        autoCloseHelper.writableDatabase
+
+        // Assert multi-instance invalidation service is running.
+        assertThat(manager.getRunningServices(100)).isNotEmpty()
+
+        // Remember the current auto close callback, it should be the one installed by the
+        // invalidation tracker
+        val trackerCallback = autoCloseHelper.autoCloser.onAutoCloseCallback!!
+
+        // Set a new callback, intercepting when DB is auto-closed
+        val latch = CountDownLatch(1)
+        autoCloseHelper.autoCloser.setAutoCloseCallback {
+            // Run the remember auto close callback
+            trackerCallback.run()
+            // At this point in time InvalidationTracker's callback has run and unbind should have
+            // been invoked.
+            latch.countDown()
+        }
+
+        assertWithMessage("Auto close callback latch await")
+            .that(latch.await(2, TimeUnit.SECONDS))
+            .isTrue()
+
+        countingTaskExecutorRule.drainTasks(2, TimeUnit.SECONDS)
+        assertWithMessage("Executor isIdle").that(countingTaskExecutorRule.isIdle).isTrue()
+
+        // Assert multi-instance invalidation service is no longer running.
+        assertThat(manager.getRunningServices(100)).isEmpty()
+
+        autoCloseDb.close()
     }
 }
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 e3d33a4..f4b7057 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
@@ -166,12 +166,11 @@
         val state = checkNotNull(multiInstanceClientInitState)
         multiInstanceInvalidationClient =
             MultiInstanceInvalidationClient(
-                context = state.context,
-                name = state.name,
-                serviceIntent = state.serviceIntent,
-                invalidationTracker = this,
-                executor = database.queryExecutor
-            )
+                    context = state.context,
+                    name = state.name,
+                    invalidationTracker = this,
+                )
+                .apply { start(state.serviceIntent) }
     }
 
     private fun stopMultiInstanceInvalidation() {
@@ -216,6 +215,12 @@
         implementation.addObserver(observer)
     }
 
+    /** An internal [addObserver] for remote observer only that skips trigger syncing. */
+    internal fun addRemoteObserver(observer: Observer): Unit {
+        check(observer.isRemote) { "isRemote was false of observer argument" }
+        implementation.addObserverOnly(observer)
+    }
+
     /**
      * Adds an observer but keeps a weak reference back to it.
      *
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt
index 491e677..9a023dc 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/MultiInstanceInvalidationClient.android.kt
@@ -23,8 +23,8 @@
 import android.os.RemoteException
 import android.util.Log
 import androidx.room.Room.LOG_TAG
-import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.launch
 
 /**
  * Handles all the communication from [RoomDatabase] and [InvalidationTracker] to
@@ -32,88 +32,80 @@
  *
  * @param context The Context to be used for binding [IMultiInstanceInvalidationService].
  * @param name The name of the database file.
- * @param serviceIntent The [Intent] used for binding [IMultiInstanceInvalidationService].
  * @param invalidationTracker The [InvalidationTracker]
- * @param executor The background executor.
  */
 internal class MultiInstanceInvalidationClient(
     context: Context,
     val name: String,
-    serviceIntent: Intent,
     val invalidationTracker: InvalidationTracker,
-    val executor: Executor
 ) {
     private val appContext = context.applicationContext
+    private val coroutineScope = invalidationTracker.database.getCoroutineScope()
+
+    private val stopped = AtomicBoolean(true)
 
     /** The client ID assigned by [MultiInstanceInvalidationService]. */
-    var clientId = 0
-    lateinit var observer: InvalidationTracker.Observer
-    var service: IMultiInstanceInvalidationService? = null
+    private var clientId = 0
+    private var invalidationService: IMultiInstanceInvalidationService? = null
 
-    val callback: IMultiInstanceInvalidationCallback =
+    /** All table observer to notify service of changes. */
+    private val observer =
+        object : InvalidationTracker.Observer(invalidationTracker.tableNames) {
+            override fun onInvalidated(tables: Set<String>) {
+                if (stopped.get()) {
+                    return
+                }
+
+                try {
+                    invalidationService?.broadcastInvalidation(clientId, tables.toTypedArray())
+                } catch (e: RemoteException) {
+                    Log.w(LOG_TAG, "Cannot broadcast invalidation", e)
+                }
+            }
+
+            override val isRemote: Boolean
+                get() = true
+        }
+
+    private val invalidationCallback: IMultiInstanceInvalidationCallback =
         object : IMultiInstanceInvalidationCallback.Stub() {
             override fun onInvalidation(tables: Array<out String>) {
-                executor.execute { invalidationTracker.notifyObserversByTableNames(*tables) }
+                coroutineScope.launch { invalidationTracker.notifyObserversByTableNames(*tables) }
             }
         }
 
-    val stopped = AtomicBoolean(false)
-
-    val serviceConnection: ServiceConnection =
+    private val serviceConnection: ServiceConnection =
         object : ServiceConnection {
             override fun onServiceConnected(name: ComponentName, service: IBinder) {
-                [email protected] =
-                    IMultiInstanceInvalidationService.Stub.asInterface(service)
-                executor.execute(setUpRunnable)
+                invalidationService = IMultiInstanceInvalidationService.Stub.asInterface(service)
+                registerCallback()
             }
 
             override fun onServiceDisconnected(name: ComponentName) {
-                executor.execute(removeObserverRunnable)
-                service = null
+                invalidationService = null
             }
         }
 
-    val setUpRunnable = Runnable {
+    private fun registerCallback() {
         try {
-            service?.let {
-                clientId = it.registerCallback(callback, name)
-                invalidationTracker.addObserver(observer)
-            }
+            invalidationService?.let { clientId = it.registerCallback(invalidationCallback, name) }
         } catch (e: RemoteException) {
             Log.w(LOG_TAG, "Cannot register multi-instance invalidation callback", e)
         }
     }
 
-    val removeObserverRunnable = Runnable { invalidationTracker.removeObserver(observer) }
-
-    init {
-        // Use all tables names for observer.
-        val tableNames = invalidationTracker.tableNames
-        observer =
-            object : InvalidationTracker.Observer(tableNames) {
-                override fun onInvalidated(tables: Set<String>) {
-                    if (stopped.get()) {
-                        return
-                    }
-
-                    try {
-                        service?.broadcastInvalidation(clientId, tables.toTypedArray())
-                    } catch (e: RemoteException) {
-                        Log.w(LOG_TAG, "Cannot broadcast invalidation", e)
-                    }
-                }
-
-                override val isRemote: Boolean
-                    get() = true
-            }
-        appContext.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
+    fun start(serviceIntent: Intent) {
+        if (stopped.compareAndSet(true, false)) {
+            appContext.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
+            invalidationTracker.addRemoteObserver(observer)
+        }
     }
 
     fun stop() {
         if (stopped.compareAndSet(false, true)) {
             invalidationTracker.removeObserver(observer)
             try {
-                service?.unregisterCallback(callback, clientId)
+                invalidationService?.unregisterCallback(invalidationCallback, clientId)
             } catch (e: RemoteException) {
                 Log.w(LOG_TAG, "Cannot unregister multi-instance invalidation callback", e)
             }
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
index 07d6ba4..16c09cb 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
@@ -201,8 +201,20 @@
         connection.execSQL(CREATE_TRACKING_TABLE_SQL)
     }
 
-    /** Add an observer and return true if it was actually added, or false if already added. */
+    /**
+     * Add an observer, sync triggers and return true if it was actually added, or false if already
+     * added.
+     */
     internal suspend fun addObserver(observer: Observer): Boolean {
+        val shouldSync = addObserverOnly(observer)
+        if (shouldSync) {
+            syncTriggers()
+        }
+        return shouldSync
+    }
+
+    /** Add an observer and return true if it was actually added, or false if already added. */
+    internal fun addObserverOnly(observer: Observer): Boolean {
         val (resolvedTableNames, tableIds) = validateTableNames(observer.tables)
         val wrapper =
             ObserverWrapper(
@@ -219,7 +231,15 @@
                     observerMap.put(observer, wrapper)
                 }
             }
-        val shouldSync = currentObserver == null && observedTableStates.onObserverAdded(tableIds)
+        return currentObserver == null && observedTableStates.onObserverAdded(tableIds)
+    }
+
+    /**
+     * Removes an observer, sync triggers and return true if it was actually removed, or false if it
+     * was not found.
+     */
+    internal suspend fun removeObserver(observer: Observer): Boolean {
+        val shouldSync = removeObserverOnly(observer)
         if (shouldSync) {
             syncTriggers()
         }
@@ -229,13 +249,9 @@
     /**
      * Removes an observer and return true if it was actually removed, or false if it was not found.
      */
-    internal suspend fun removeObserver(observer: Observer): Boolean {
+    private fun removeObserverOnly(observer: Observer): Boolean {
         val wrapper = observerMapLock.withLock { observerMap.remove(observer) }
-        val shouldSync = wrapper != null && observedTableStates.onObserverRemoved(wrapper.tableIds)
-        if (shouldSync) {
-            syncTriggers()
-        }
-        return shouldSync
+        return wrapper != null && observedTableStates.onObserverRemoved(wrapper.tableIds)
     }
 
     /** Resolves the list of tables and views into unique table names and ids. */
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
index e360241..65e89dd 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
@@ -47,7 +47,6 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -772,17 +771,14 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static boolean canDrawOverlays(Context context) {
             return Settings.canDrawOverlays(context);
         }
 
-        @DoNotInline
         static boolean shouldShowRequestPermissionRationale(Activity activity, String permission) {
             return activity.shouldShowRequestPermissionRationale(permission);
         }
 
-        @DoNotInline
         static void requestPermissions(Activity activity, String[] permissions, int requestCode) {
             activity.requestPermissions(permissions, requestCode);
         }
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java
index 80d6e33..096ebfd 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/systemrouting/source/AudioManagerSystemRoutesSource.java
@@ -22,7 +22,6 @@
 import android.media.AudioManager;
 import android.os.Build;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
@@ -115,7 +114,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static String getAddress(AudioDeviceInfo audioDeviceInfo) {
             return audioDeviceInfo.getAddress();
         }
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
index 4080f47..51b8acd 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
@@ -38,7 +38,6 @@
 import android.widget.Toast;
 
 import androidx.annotation.CallSuper;
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -397,24 +396,20 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static NotificationChannel createNotificationChannel(String notificationChannelId,
                 String name, int importance) {
             return new NotificationChannel(notificationChannelId, name, importance);
         }
 
-        @DoNotInline
         static void createNotificationChannel(NotificationManager notificationManager,
                 NotificationChannel channel) {
             notificationManager.createNotificationChannel(channel);
         }
 
-        @DoNotInline
         static void setDescription(NotificationChannel notificationChannel, String description) {
             notificationChannel.setDescription(description);
         }
 
-        @DoNotInline
         static NotificationManager getSystemServiceReturnsNotificationManager(Context context) {
             return context.getSystemService(NotificationManager.class);
         }
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java
index b10fabb..4db6b6b 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleMediaRouteProvider.java
@@ -31,7 +31,6 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -735,7 +734,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static boolean canDrawOverlays(Context context) {
             return Settings.canDrawOverlays(context);
         }
diff --git a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
index 4c1946c..0ac7512 100644
--- a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
+++ b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
@@ -26,7 +26,6 @@
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -339,7 +338,6 @@
                 // This class is not instantiable.
             }
 
-            @DoNotInline
             static String getKeystoreAlias(KeyGenParameterSpec keyGenParameterSpec) {
                 return keyGenParameterSpec.getKeystoreAlias();
             }
@@ -397,7 +395,6 @@
                     // This class is not instantiable.
                 }
 
-                @DoNotInline
                 static void setIsStrongBoxBacked(KeyGenParameterSpec.Builder builder) {
                     builder.setIsStrongBoxBacked(true);
                 }
@@ -409,7 +406,6 @@
                     // This class is not instantiable.
                 }
 
-                @DoNotInline
                 static void setUserAuthenticationParameters(KeyGenParameterSpec.Builder builder,
                         int timeout,
                         int type) {
@@ -426,12 +422,10 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static boolean isUserAuthenticationRequired(KeyGenParameterSpec keyGenParameterSpec) {
             return keyGenParameterSpec.isUserAuthenticationRequired();
         }
 
-        @DoNotInline
         static int getUserAuthenticationValidityDurationSeconds(
                 KeyGenParameterSpec keyGenParameterSpec) {
             return keyGenParameterSpec.getUserAuthenticationValidityDurationSeconds();
@@ -444,7 +438,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static boolean isStrongBoxBacked(KeyGenParameterSpec keyGenParameterSpec) {
             return keyGenParameterSpec.isStrongBoxBacked();
         }
diff --git a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java b/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
index 096366c..76c8677 100644
--- a/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
+++ b/security/security-identity-credential/src/main/java/androidx/security/identity/HardwareIdentityCredential.java
@@ -19,7 +19,6 @@
 import android.icu.util.Calendar;
 import android.os.Build;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -282,14 +281,12 @@
     @RequiresApi(Build.VERSION_CODES.S)
     private static class ApiImplS {
         @SuppressWarnings("deprecation")
-        @DoNotInline
         static void callSetAllowUsingExpiredKeys(
                 @NonNull android.security.identity.IdentityCredential credential,
                 boolean allowUsingExpiredKeys) {
             credential.setAllowUsingExpiredKeys(allowUsingExpiredKeys);
         }
 
-        @DoNotInline
         static void callStoreStaticAuthenticationData(
                 @NonNull android.security.identity.IdentityCredential credential,
                 @NonNull X509Certificate authenticationKey,
@@ -301,21 +298,18 @@
                     staticAuthData);
         }
 
-        @DoNotInline
         static @NonNull byte[] callProveOwnership(
                 @NonNull android.security.identity.IdentityCredential credential,
                 @NonNull byte[] challenge) {
             return credential.proveOwnership(challenge);
         }
 
-        @DoNotInline
         static @NonNull byte[] callDelete(
                 @NonNull android.security.identity.IdentityCredential credential,
                 @NonNull byte[] challenge) {
             return credential.delete(challenge);
         }
 
-        @DoNotInline
         static @NonNull byte[] callUpdate(
                 @NonNull android.security.identity.IdentityCredential credential,
                 @NonNull android.security.identity.PersonalizationData personalizationData) {
diff --git a/settings.gradle b/settings.gradle
index 3006362..00df100 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -623,6 +623,7 @@
 includeProject(":credentials:credentials-play-services-auth", [BuildType.MAIN])
 includeProject(":credentials:credentials-provider", [BuildType.MAIN])
 includeProject(":credentials:credentials-e2ee", [BuildType.MAIN])
+includeProject(":credentials:credentials-play-services-e2ee", [BuildType.MAIN])
 includeProject(":cursoradapter:cursoradapter", [BuildType.MAIN])
 includeProject(":customview:customview", [BuildType.MAIN])
 includeProject(":customview:customview-poolingcontainer", [BuildType.MAIN, BuildType.COMPOSE])
diff --git a/slice/slice-core/src/main/java/androidx/slice/Slice.java b/slice/slice-core/src/main/java/androidx/slice/Slice.java
index 93e5561..5cab35a 100644
--- a/slice/slice-core/src/main/java/androidx/slice/Slice.java
+++ b/slice/slice-core/src/main/java/androidx/slice/Slice.java
@@ -61,7 +61,6 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -653,7 +652,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static <T> T getSystemService(Context context, Class<T> serviceClass) {
             return context.getSystemService(serviceClass);
         }
@@ -666,7 +664,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static android.app.slice.Slice bindSlice(SliceManager sliceManager, Uri uri,
                 Set<android.app.slice.SliceSpec> supportedSpecs) {
             return sliceManager.bindSlice(uri, supportedSpecs);
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
index 9f327fc..e607f74 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
@@ -42,7 +42,6 @@
 import android.os.StrictMode;
 import android.util.Log;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -728,7 +727,6 @@
                 // This class is not instantiable.
             }
 
-            @DoNotInline
             static void close(ContentProviderClient contentProviderClient) {
                 contentProviderClient.close();
             }
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt
index d86497a..e999766 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.android.kt
@@ -28,7 +28,6 @@
 import android.os.CancellationSignal
 import android.text.TextUtils
 import android.util.Pair
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.sqlite.db.SimpleSQLiteQuery
 import androidx.sqlite.db.SupportSQLiteDatabase
@@ -324,7 +323,6 @@
 
     @RequiresApi(30)
     internal object Api30Impl {
-        @DoNotInline
         fun execPerConnectionSQL(
             sQLiteDatabase: SQLiteDatabase,
             sql: String,
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
index f4d86fa..951f4bb 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
@@ -23,7 +23,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.RequiresApi;
 import androidx.test.uiautomator.util.Traces;
 import androidx.test.uiautomator.util.Traces.Section;
@@ -216,7 +215,6 @@
         private Api24Impl() {
         }
 
-        @DoNotInline
         static int getDrawingOrder(AccessibilityNodeInfo accessibilityNodeInfo) {
             return accessibilityNodeInfo.getDrawingOrder();
         }
@@ -227,7 +225,6 @@
         private Api26Impl() {
         }
 
-        @DoNotInline
         static String getHintText(AccessibilityNodeInfo accessibilityNodeInfo) {
             CharSequence chars = accessibilityNodeInfo.getHintText();
             return chars != null ? chars.toString() : null;
@@ -239,7 +236,6 @@
         private Api30Impl() {
         }
 
-        @DoNotInline
         static int getDisplayId(AccessibilityNodeInfo accessibilityNodeInfo) {
             AccessibilityWindowInfo accessibilityWindowInfo = accessibilityNodeInfo.getWindow();
             return accessibilityWindowInfo == null ? Display.DEFAULT_DISPLAY :
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java
index 46ffa92..5a63da9 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/ByMatcher.java
@@ -22,7 +22,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.test.uiautomator.util.Traces;
@@ -365,7 +364,6 @@
             private Api26Impl() {
             }
 
-            @DoNotInline
             static String getHintText(AccessibilityNodeInfo accessibilityNodeInfo) {
                 CharSequence chars = accessibilityNodeInfo.getHintText();
                 return chars != null ? chars.toString() : null;
@@ -427,7 +425,6 @@
         private Api30Impl() {
         }
 
-        @DoNotInline
         static int getDisplayId(AccessibilityNodeInfo accessibilityNodeInfo) {
             AccessibilityWindowInfo accessibilityWindowInfo = accessibilityNodeInfo.getWindow();
             return accessibilityWindowInfo == null ? Display.DEFAULT_DISPLAY :
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
index a105be1..1c189a3 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
@@ -46,7 +46,6 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 
 import androidx.annotation.Discouraged;
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
@@ -1519,7 +1518,6 @@
         private Api24Impl() {
         }
 
-        @DoNotInline
         static UiAutomation getUiAutomationWithRetry(Instrumentation instrumentation, int flags) {
             UiAutomation uiAutomation = null;
             for (int i = 0; i < MAX_UIAUTOMATION_RETRY; i++) {
@@ -1541,7 +1539,6 @@
         private Api30Impl() {
         }
 
-        @DoNotInline
         static SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays(
                 UiAutomation uiAutomation) {
             return uiAutomation.getWindowsOnAllDisplays();
@@ -1553,7 +1550,6 @@
         private Api31Impl() {
         }
 
-        @DoNotInline
         static Context createWindowContext(Context context, Display display) {
             return context.createWindowContext(display,
                     WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, null);
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
index 2da3b2a..c50fb1f 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
@@ -33,7 +33,6 @@
 import android.widget.Checkable;
 import android.widget.TextView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.FloatRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -1032,7 +1031,6 @@
         private Api24Impl() {
         }
 
-        @DoNotInline
         static int getDrawingOrder(AccessibilityNodeInfo accessibilityNodeInfo) {
             return accessibilityNodeInfo.getDrawingOrder();
         }
@@ -1043,7 +1041,6 @@
         private Api26Impl() {
         }
 
-        @DoNotInline
         static String getHintText(AccessibilityNodeInfo accessibilityNodeInfo) {
             CharSequence chars = accessibilityNodeInfo.getHintText();
             return chars != null ? chars.toString() : null;
@@ -1055,7 +1052,6 @@
         private Api30Impl() {
         }
 
-        @DoNotInline
         static int getDisplayId(AccessibilityWindowInfo accessibilityWindowInfo) {
             return accessibilityWindowInfo.getDisplayId();
         }
diff --git a/testutils/testutils-appcompat/build.gradle b/testutils/testutils-appcompat/build.gradle
index eb47b7a..5c26d7f 100644
--- a/testutils/testutils-appcompat/build.gradle
+++ b/testutils/testutils-appcompat/build.gradle
@@ -32,7 +32,7 @@
 dependencies {
     api(project(":internal-testutils-runtime"))
     api(project(":appcompat:appcompat"))
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
 
     implementation(libs.testExtJunit)
     implementation(libs.testCore)
diff --git a/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt b/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt
index 865ca5f..a5dab67 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/testutils/AndroidFontScaleHelper.kt
@@ -19,7 +19,6 @@
 import android.app.Activity
 import android.os.Build
 import android.provider.Settings
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.test.core.app.ActivityScenario
 import androidx.test.platform.app.InstrumentationRegistry
@@ -70,7 +69,6 @@
     @RequiresApi(Build.VERSION_CODES.Q)
     private object Api29Impl {
         @JvmStatic
-        @DoNotInline
         fun <A : Activity> setSystemFontScale(
             fontScale: Float,
             activityScenario: ActivityScenario<A>
@@ -101,7 +99,6 @@
 
         /** Runs the given function as root, with all shell permissions granted. */
         @JvmStatic
-        @DoNotInline
         private fun invokeWithShellPermissions(runnable: () -> Unit) {
             val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
             try {
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
index 16f27ac..287c6c9 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
@@ -23,7 +23,6 @@
 import android.text.TextDirectionHeuristic
 import android.text.TextPaint
 import android.text.TextUtils.TruncateAt
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
 /** Factory Class for BoringLayout */
@@ -123,7 +122,6 @@
 private object BoringLayoutFactory33 {
 
     @JvmStatic
-    @DoNotInline
     fun isBoring(text: CharSequence, paint: TextPaint, textDir: TextDirectionHeuristic): Metrics? {
         return BoringLayout.isBoring(
             text,
@@ -135,7 +133,6 @@
     }
 
     @JvmStatic
-    @DoNotInline
     fun create(
         text: CharSequence,
         paint: TextPaint,
@@ -165,7 +162,6 @@
     }
 
     @JvmStatic
-    @DoNotInline
     fun isFallbackLineSpacingEnabled(layout: BoringLayout): Boolean {
         return layout.isFallbackLineSpacingEnabled
     }
@@ -173,7 +169,6 @@
 
 private object BoringLayoutFactoryDefault {
     @JvmStatic
-    @DoNotInline
     fun isBoring(text: CharSequence, paint: TextPaint, textDir: TextDirectionHeuristic): Metrics? {
         return if (!textDir.isRtl(text, 0, text.length)) {
             return BoringLayout.isBoring(text, paint, null /* metrics */)
@@ -183,7 +178,6 @@
     }
 
     @JvmStatic
-    @DoNotInline
     fun create(
         text: CharSequence,
         paint: TextPaint,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
index feda869..0928bb4b 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
@@ -18,8 +18,11 @@
 
 import android.text.BoringLayout
 import android.text.Layout
+import android.text.SpannableString
 import android.text.Spanned
 import android.text.TextPaint
+import android.text.style.CharacterStyle
+import android.text.style.MetricAffectingSpan
 import androidx.compose.ui.text.android.style.LetterSpacingSpanEm
 import androidx.compose.ui.text.android.style.LetterSpacingSpanPx
 import java.text.BreakIterator
@@ -79,15 +82,18 @@
                 var desiredWidth = (boringMetrics?.width ?: -1).toFloat()
 
                 // boring metrics doesn't cover RTL text so we fallback to different calculation
-                // when boring
-                // metrics can't be calculated
+                // when boring metrics can't be calculated
                 if (desiredWidth < 0) {
                     // b/233856978, apply `ceil` function here to be consistent with the boring
-                    // metrics
-                    // width calculation that does it under the hood, too
+                    // metrics width calculation that does it under the hood, too
                     desiredWidth =
                         ceil(
-                            Layout.getDesiredWidth(charSequence, 0, charSequence.length, textPaint)
+                            Layout.getDesiredWidth(
+                                stripNonMetricAffectingCharacterStyleSpans(charSequence),
+                                0,
+                                charSequence.length,
+                                textPaint
+                            )
                         )
                 }
                 if (shouldIncreaseMaxIntrinsic(desiredWidth, charSequence, textPaint)) {
@@ -142,7 +148,13 @@
     var minWidth = 0f
 
     longestWordCandidates.forEach { (start, end) ->
-        val width = Layout.getDesiredWidth(text, start, end, paint)
+        val width =
+            Layout.getDesiredWidth(
+                stripNonMetricAffectingCharacterStyleSpans(text),
+                start,
+                end,
+                paint
+            )
         minWidth = maxOf(minWidth, width)
     }
 
@@ -150,6 +162,27 @@
 }
 
 /**
+ * See [b/346918500#comment7](https://issuetracker.google.com/346918500#comment7).
+ *
+ * Remove all character styling spans for measuring intrinsic width. [CharacterStyle] spans may
+ * affect the intrinsic width, even though they aren't supposed to, resulting in a width that
+ * doesn't actually fit the text. This can cause the line to unexpectedly wrap, even if `maxLines`
+ * was set to `1` or `softWrap` was `false`.
+ *
+ * [MetricAffectingSpan] extends [CharacterStyle], but [MetricAffectingSpan]s are allowed to affect
+ * the width, so only remove spans that **do** extend [CharacterStyle] but **don't** extend
+ * [MetricAffectingSpan].
+ */
+private fun stripNonMetricAffectingCharacterStyleSpans(charSequence: CharSequence): CharSequence {
+    if (charSequence !is Spanned) return charSequence
+    val spans = charSequence.getSpans(0, charSequence.length, CharacterStyle::class.java)
+    if (spans.isNullOrEmpty()) return charSequence
+    return SpannableString(charSequence).apply {
+        spans.onEach { if (it !is MetricAffectingSpan) removeSpan(it) }
+    }
+}
+
+/**
  * b/173574230 on Android 11 and above, creating a StaticLayout when
  * - desiredWidth is an Integer,
  * - letterSpacing is set
@@ -157,7 +190,6 @@
  *
  * This function checks if those conditions are met.
  */
-@OptIn(InternalPlatformTextApi::class)
 private fun shouldIncreaseMaxIntrinsic(
     desiredWidth: Float,
     charSequence: CharSequence,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
index bc28ba3..cfbdc69 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
@@ -22,7 +22,6 @@
 import android.text.Spanned
 import android.text.TextPaint
 import android.text.style.MetricAffectingSpan
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import kotlin.math.max
@@ -101,7 +100,6 @@
 @RequiresApi(29)
 private object Paint29 {
     @JvmStatic
-    @DoNotInline
     fun getTextBounds(paint: Paint, text: CharSequence, start: Int, end: Int, rect: Rect) {
         paint.getTextBounds(text, start, end, rect)
     }
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
index 19699201e..256e746 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
@@ -24,7 +24,6 @@
 import android.text.TextPaint
 import android.text.TextUtils.TruncateAt
 import android.util.Log
-import androidx.annotation.DoNotInline
 import androidx.annotation.FloatRange
 import androidx.annotation.IntRange
 import androidx.annotation.RequiresApi
@@ -151,7 +150,7 @@
 
 private interface StaticLayoutFactoryImpl {
 
-    @DoNotInline // API level specific, do not inline to prevent ART class verification breakages
+    // API level specific, do not inline to prevent ART class verification breakages
     fun create(params: StaticLayoutParams): StaticLayout
 
     fun isFallbackLineSpacingEnabled(layout: StaticLayout, useFallbackLineSpacing: Boolean): Boolean
@@ -160,7 +159,6 @@
 @RequiresApi(23)
 private class StaticLayoutFactory23 : StaticLayoutFactoryImpl {
 
-    @DoNotInline
     override fun create(params: StaticLayoutParams): StaticLayout {
         return Builder.obtain(params.text, params.start, params.end, params.paint, params.width)
             .apply {
@@ -211,7 +209,6 @@
 @RequiresApi(26)
 private object StaticLayoutFactory26 {
     @JvmStatic
-    @DoNotInline
     fun setJustificationMode(builder: Builder, justificationMode: Int) {
         builder.setJustificationMode(justificationMode)
     }
@@ -220,7 +217,6 @@
 @RequiresApi(28)
 private object StaticLayoutFactory28 {
     @JvmStatic
-    @DoNotInline
     fun setUseLineSpacingFromFallbacks(builder: Builder, useFallbackLineSpacing: Boolean) {
         builder.setUseLineSpacingFromFallbacks(useFallbackLineSpacing)
     }
@@ -229,13 +225,11 @@
 @RequiresApi(33)
 private object StaticLayoutFactory33 {
     @JvmStatic
-    @DoNotInline
     fun isFallbackLineSpacingEnabled(layout: StaticLayout): Boolean {
         return layout.isFallbackLineSpacingEnabled
     }
 
     @JvmStatic
-    @DoNotInline
     fun setLineBreakConfig(builder: Builder, lineBreakStyle: Int, lineBreakWordStyle: Int) {
         val lineBreakConfig =
             LineBreakConfig.Builder()
@@ -283,7 +277,6 @@
         }
     }
 
-    @DoNotInline
     override fun create(params: StaticLayoutParams): StaticLayout {
         // On API 21 to 23, try to call the StaticLayoutConstructor which supports the
         // textDir and maxLines.
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
index cdc118e..917198b 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
@@ -33,7 +33,6 @@
 import android.graphics.fonts.Font
 import android.graphics.text.MeasuredText
 import android.os.Build
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
 /**
@@ -781,7 +780,6 @@
 @RequiresApi(Build.VERSION_CODES.M)
 private object CanvasCompatM {
 
-    @DoNotInline
     fun drawTextRun(
         canvas: Canvas,
         text: CharArray,
@@ -797,7 +795,6 @@
         canvas.drawTextRun(text, index, count, contextIndex, contextCount, x, y, isRtl, paint)
     }
 
-    @DoNotInline
     fun drawTextRun(
         canvas: Canvas,
         text: CharSequence,
@@ -817,27 +814,22 @@
 @RequiresApi(Build.VERSION_CODES.O)
 private object CanvasCompatO {
 
-    @DoNotInline
     fun clipOutRect(canvas: Canvas, rect: RectF): Boolean {
         return canvas.clipOutRect(rect)
     }
 
-    @DoNotInline
     fun clipOutRect(canvas: Canvas, rect: Rect): Boolean {
         return canvas.clipOutRect(rect)
     }
 
-    @DoNotInline
     fun clipOutRect(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float): Boolean {
         return canvas.clipOutRect(left, top, right, bottom)
     }
 
-    @DoNotInline
     fun clipOutRect(canvas: Canvas, left: Int, top: Int, right: Int, bottom: Int): Boolean {
         return canvas.clipOutRect(left, top, right, bottom)
     }
 
-    @DoNotInline
     fun clipOutPath(canvas: Canvas, path: Path): Boolean {
         return canvas.clipOutPath(path)
     }
@@ -846,32 +838,26 @@
 @RequiresApi(Build.VERSION_CODES.Q)
 private object CanvasCompatQ {
 
-    @DoNotInline
     fun enableZ(canvas: Canvas) {
         canvas.enableZ()
     }
 
-    @DoNotInline
     fun disableZ(canvas: Canvas) {
         canvas.disableZ()
     }
 
-    @DoNotInline
     fun drawColor(canvas: Canvas, color: Long) {
         canvas.drawColor(color)
     }
 
-    @DoNotInline
     fun drawColor(canvas: Canvas, color: Int, mode: BlendMode) {
         canvas.drawColor(color, mode)
     }
 
-    @DoNotInline
     fun drawColor(canvas: Canvas, color: Long, mode: BlendMode) {
         canvas.drawColor(color, mode)
     }
 
-    @DoNotInline
     fun drawDoubleRoundRect(
         canvas: Canvas,
         outer: RectF,
@@ -885,7 +871,6 @@
         canvas.drawDoubleRoundRect(outer, outerRx, outerRy, inner, innerRx, innerRy, paint)
     }
 
-    @DoNotInline
     fun drawDoubleRoundRect(
         canvas: Canvas,
         outer: RectF,
@@ -897,7 +882,6 @@
         canvas.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint)
     }
 
-    @DoNotInline
     fun drawTextRun(
         canvas: Canvas,
         text: MeasuredText,
@@ -913,7 +897,6 @@
         canvas.drawTextRun(text, start, end, contextStart, contextEnd, x, y, isRtl, paint)
     }
 
-    @DoNotInline
     fun drawRenderNode(canvas: Canvas, renderNode: RenderNode) {
         canvas.drawRenderNode(renderNode)
     }
@@ -922,17 +905,14 @@
 @RequiresApi(Build.VERSION_CODES.R)
 private object CanvasCompatR {
 
-    @DoNotInline
     fun quickReject(canvas: Canvas, rect: RectF): Boolean {
         return canvas.quickReject(rect)
     }
 
-    @DoNotInline
     fun quickReject(canvas: Canvas, path: Path): Boolean {
         return canvas.quickReject(path)
     }
 
-    @DoNotInline
     fun quickReject(canvas: Canvas, left: Float, top: Float, right: Float, bottom: Float): Boolean {
         return canvas.quickReject(left, top, right, bottom)
     }
@@ -940,17 +920,14 @@
 
 @RequiresApi(Build.VERSION_CODES.S)
 private object CanvasCompatS {
-    @DoNotInline
     fun drawPatch(canvas: Canvas, patch: NinePatch, dst: Rect, paint: Paint?) {
         canvas.drawPatch(patch, dst, paint)
     }
 
-    @DoNotInline
     fun drawPatch(canvas: Canvas, patch: NinePatch, dst: RectF, paint: Paint?) {
         canvas.drawPatch(patch, dst, paint)
     }
 
-    @DoNotInline
     fun drawGlyphs(
         canvas: Canvas,
         glyphIds: IntArray,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
index 1fe9dd3..06e3aef 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
@@ -32,7 +32,6 @@
 import android.text.TextDirectionHeuristics
 import android.text.TextPaint
 import android.text.TextUtils
-import androidx.annotation.DoNotInline
 import androidx.annotation.Px
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
@@ -1099,7 +1098,6 @@
 
 @RequiresApi(34)
 internal object AndroidLayoutApi34 {
-    @DoNotInline
     internal fun getRangeForRect(
         layout: TextLayout,
         rectF: RectF,
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt b/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt
index 878a6c7..cac8111 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/selection/SegmentFinder.android.kt
@@ -19,7 +19,6 @@
 import android.graphics.Paint
 import android.os.Build
 import android.text.TextPaint
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.text.android.selection.SegmentFinder.Companion.DONE
 import java.text.BreakIterator
@@ -222,7 +221,6 @@
 
 @RequiresApi(34)
 internal object Api34SegmentFinder {
-    @DoNotInline
     internal fun SegmentFinder.toAndroidSegmentFinder(): android.text.SegmentFinder {
         return object : android.text.SegmentFinder() {
             override fun previousStartBoundary(offset: Int): Int =
diff --git a/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java b/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java
index 14ba157..6c51b18 100644
--- a/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java
+++ b/tracing/tracing/src/main/java/androidx/tracing/TraceApi29Impl.java
@@ -16,7 +16,6 @@
 
 package androidx.tracing;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
@@ -38,7 +37,6 @@
     /**
      * Checks whether or not tracing is currently enabled.
      */
-    @DoNotInline
     public static boolean isEnabled() {
         return android.os.Trace.isEnabled();
     }
diff --git a/transition/transition/src/main/java/androidx/transition/CanvasUtils.java b/transition/transition/src/main/java/androidx/transition/CanvasUtils.java
index e9d7452..6c058c5 100644
--- a/transition/transition/src/main/java/androidx/transition/CanvasUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/CanvasUtils.java
@@ -20,7 +20,6 @@
 import android.graphics.Canvas;
 import android.os.Build;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
@@ -88,12 +87,10 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void enableZ(Canvas canvas) {
             canvas.enableZ();
         }
 
-        @DoNotInline
         static void disableZ(Canvas canvas) {
             canvas.disableZ();
         }
diff --git a/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java b/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java
index eccefad..95667f4 100644
--- a/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java
+++ b/transition/transition/src/main/java/androidx/transition/GhostViewHolder.java
@@ -23,7 +23,6 @@
 import android.view.ViewParent;
 import android.widget.FrameLayout;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
@@ -205,7 +204,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static float getZ(View view) {
             return view.getZ();
         }
diff --git a/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java b/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
index 453d015..58c435a 100644
--- a/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
@@ -22,7 +22,6 @@
 import android.os.Build;
 import android.widget.ImageView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -118,7 +117,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void animateTransform(ImageView imageView, Matrix matrix) {
             imageView.animateTransform(matrix);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java b/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
index ecf4989..2a4a6bd 100644
--- a/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
@@ -22,7 +22,6 @@
 import android.os.Build;
 import android.util.Property;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.RequiresApi;
 
 class ObjectAnimatorUtils {
@@ -42,7 +41,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property, Path path) {
             return ObjectAnimator.ofObject(target, property, null, path);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java b/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
index f8ccc90..913d56d 100644
--- a/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
@@ -22,7 +22,6 @@
 import android.os.Build;
 import android.util.Property;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.RequiresApi;
 
 class PropertyValuesHolderUtils {
@@ -52,7 +51,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static <V> PropertyValuesHolder ofObject(Property<?, V> property, Path path) {
             return PropertyValuesHolder.ofObject(property, null, path);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/Transition.java b/transition/transition/src/main/java/androidx/transition/Transition.java
index 745f2b0..460cd83 100644
--- a/transition/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/transition/src/main/java/androidx/transition/Transition.java
@@ -42,7 +42,6 @@
 import android.widget.ListView;
 import android.widget.Spinner;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.IdRes;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -2711,12 +2710,10 @@
 
     @RequiresApi(Build.VERSION_CODES.O)
     private static class Impl26 {
-        @DoNotInline
         static long getTotalDuration(Animator animator) {
             return animator.getTotalDuration();
         }
 
-        @DoNotInline
         static void setCurrentPlayTime(Animator animator, long playTimeMillis) {
             ((AnimatorSet) animator).setCurrentPlayTime(playTimeMillis);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/TransitionUtils.java b/transition/transition/src/main/java/androidx/transition/TransitionUtils.java
index 3e323a0d..f4359b2 100644
--- a/transition/transition/src/main/java/androidx/transition/TransitionUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/TransitionUtils.java
@@ -30,7 +30,6 @@
 import android.view.ViewGroupOverlay;
 import android.widget.ImageView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.RequiresApi;
 
 class TransitionUtils {
@@ -179,7 +178,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static Bitmap createBitmap(Picture source) {
             return Bitmap.createBitmap(source);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
index 66a310a..20b4000 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
@@ -20,7 +20,6 @@
 import android.os.Build;
 import android.view.ViewGroup;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
@@ -103,12 +102,10 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void suppressLayout(ViewGroup viewGroup, boolean suppress) {
             viewGroup.suppressLayout(suppress);
         }
 
-        @DoNotInline
         static int getChildDrawingOrder(ViewGroup viewGroup, int drawingPosition) {
             return viewGroup.getChildDrawingOrder(drawingPosition);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
index 448ba3a..f7c5b7d 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
@@ -22,7 +22,6 @@
 import android.view.View;
 import android.view.ViewParent;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -206,12 +205,10 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void setTransitionAlpha(View view, float alpha) {
             view.setTransitionAlpha(alpha);
         }
 
-        @DoNotInline
         static float getTransitionAlpha(View view) {
             return view.getTransitionAlpha();
         }
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java
index e4d7acd..3a4adeb 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi21.java
@@ -20,7 +20,6 @@
 import android.graphics.Matrix;
 import android.view.View;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -89,17 +88,14 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void transformMatrixToGlobal(View view, Matrix matrix) {
             view.transformMatrixToGlobal(matrix);
         }
 
-        @DoNotInline
         static void transformMatrixToLocal(View view, Matrix matrix) {
             view.transformMatrixToLocal(matrix);
         }
 
-        @DoNotInline
         static void setAnimationMatrix(View view, Matrix matrix) {
             view.setAnimationMatrix(matrix);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java
index b74893c..e0330a7 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi22.java
@@ -19,7 +19,6 @@
 import android.annotation.SuppressLint;
 import android.view.View;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
@@ -51,7 +50,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void setLeftTopRightBottom(View view, int left, int top, int right, int bottom) {
             view.setLeftTopRightBottom(left, top, right, bottom);
         }
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java
index b76944a..fc1d27b 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtilsApi23.java
@@ -20,7 +20,6 @@
 import android.os.Build;
 import android.view.View;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
@@ -57,7 +56,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void setTransitionVisibility(View view, int visibility) {
             view.setTransitionVisibility(visibility);
         }
diff --git a/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java b/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java
index 7c743a9..4a6e255 100644
--- a/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java
+++ b/vectordrawable/vectordrawable-animated/src/main/java/androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat.java
@@ -40,7 +40,6 @@
 import android.util.Log;
 import android.util.Xml;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -950,19 +949,16 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static boolean unregisterAnimationCallback(Object animatedVectorDrawable,
                 Object callback) {
             return ((AnimatedVectorDrawable) animatedVectorDrawable).unregisterAnimationCallback(
                     (Animatable2.AnimationCallback) callback);
         }
 
-        @DoNotInline
         static void clearAnimationCallbacks(Object animatedVectorDrawable) {
             ((AnimatedVectorDrawable) animatedVectorDrawable).clearAnimationCallbacks();
         }
 
-        @DoNotInline
         static void registerAnimationCallback(Object animatedVectorDrawable, Object callback) {
             ((AnimatedVectorDrawable) animatedVectorDrawable).registerAnimationCallback(
                     (Animatable2.AnimationCallback) callback);
diff --git a/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java b/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java
index 83db0db..b796da3 100644
--- a/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java
+++ b/versionedparcelable/versionedparcelable/src/main/java/androidx/versionedparcelable/VersionedParcel.java
@@ -29,7 +29,6 @@
 import android.util.SizeF;
 import android.util.SparseBooleanArray;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -1686,7 +1685,6 @@
 
     @RequiresApi(21)
     private static final class Api21Impl {
-        @DoNotInline
         static void writeSize(@NonNull VersionedParcel self, @Nullable Size val) {
             self.writeBoolean(val != null);
             if (val != null) {
@@ -1695,7 +1693,6 @@
             }
         }
 
-        @DoNotInline
         static void writeSizeF(@NonNull VersionedParcel self, @Nullable SizeF val) {
             self.writeBoolean(val != null);
             if (val != null) {
@@ -1704,7 +1701,6 @@
             }
         }
 
-        @DoNotInline
         @Nullable
         static Size readSize(@NonNull VersionedParcel self) {
             if (self.readBoolean()) {
@@ -1715,7 +1711,6 @@
             return null;
         }
 
-        @DoNotInline
         @Nullable
         static SizeF readSizeF(@NonNull VersionedParcel self) {
             if (self.readBoolean()) {
diff --git a/viewpager2/viewpager2/build.gradle b/viewpager2/viewpager2/build.gradle
index 56499ac..abddf8a 100644
--- a/viewpager2/viewpager2/build.gradle
+++ b/viewpager2/viewpager2/build.gradle
@@ -31,7 +31,7 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     implementation("androidx.core:core:1.3.2")
     api("androidx.fragment:fragment:1.1.0")
     api("androidx.recyclerview:recyclerview:1.3.1")
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
new file mode 100644
index 0000000..cd8d533
--- /dev/null
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.demos
+
+import android.view.HapticFeedbackConstants
+import android.view.View
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Text
+
+@Composable
+fun HapticsDemos() {
+    val haptics = HapticFeedbackProvider(LocalView.current)
+
+    // Show a Button to trigger each haptic constant when clicked:
+    // https://developer.android.com/reference/android/view/HapticFeedbackConstants
+    val hapticConstants =
+        listOf(
+            Pair(HapticFeedbackConstants.CLOCK_TICK, "Clock Tick"),
+            Pair(HapticFeedbackConstants.CONFIRM, "Confirm"),
+            Pair(HapticFeedbackConstants.CONTEXT_CLICK, "Context Click"),
+            Pair(HapticFeedbackConstants.DRAG_START, "Drag Start"),
+            // NB HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING has been deprecated, so omit it
+            Pair(HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, "Flag Ignore"),
+            Pair(HapticFeedbackConstants.GESTURE_END, "Gesture End"),
+            Pair(HapticFeedbackConstants.GESTURE_START, "Gesture Start"),
+            Pair(HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE, "Gesture Threshold Activate"),
+            Pair(
+                HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE,
+                "Gesture Threshold Deactivate"
+            ),
+            Pair(HapticFeedbackConstants.KEYBOARD_PRESS, "Keyboard Press"),
+            Pair(HapticFeedbackConstants.KEYBOARD_RELEASE, "Keyboard Release"),
+            Pair(HapticFeedbackConstants.KEYBOARD_TAP, "Keyboard Tap"),
+            Pair(HapticFeedbackConstants.LONG_PRESS, "Long Press"),
+            Pair(HapticFeedbackConstants.NO_HAPTICS, "No haptics"),
+            Pair(HapticFeedbackConstants.REJECT, "Reject"),
+            Pair(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK, "Segment Frequent Tick"),
+            Pair(HapticFeedbackConstants.SEGMENT_TICK, "Segment Tick"),
+            Pair(HapticFeedbackConstants.TEXT_HANDLE_MOVE, "Text Handle Move"),
+            Pair(HapticFeedbackConstants.TOGGLE_OFF, "Toggle Off"),
+            Pair(HapticFeedbackConstants.TOGGLE_ON, "Toggle On"),
+            Pair(HapticFeedbackConstants.VIRTUAL_KEY, "Virtual Key"),
+            Pair(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE, "Virtual Key Release"),
+        )
+
+    ScalingLazyDemo(contentPadding = PaddingValues(horizontal = 20.dp)) {
+        items(hapticConstants.size) { index ->
+            val (constant, name) = hapticConstants[index]
+            HapticsDemo(haptics, constant, name)
+        }
+    }
+}
+
+@Composable
+private fun HapticsDemo(
+    hapticFeedbackProvider: HapticFeedbackProvider,
+    feedbackConstant: Int,
+    demoName: String
+) {
+    Button(
+        onClick = { hapticFeedbackProvider.performHapticFeedback(feedbackConstant) },
+        label = {
+            Text(demoName, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
+        },
+        modifier = Modifier.fillMaxWidth()
+    )
+}
+
+private class HapticFeedbackProvider(private val view: View) {
+    fun performHapticFeedback(feedbackConstant: Int) {
+        view.let { view -> view.performHapticFeedback(feedbackConstant) }
+    }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
index e2f16b1..3d39cb3 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Material3Demos.kt
@@ -16,6 +16,7 @@
 
 package androidx.wear.compose.material3.demos
 
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -26,13 +27,17 @@
 import androidx.wear.compose.material3.ScreenScaffold
 
 @Composable
-fun ScalingLazyDemo(content: ScalingLazyListScope.() -> Unit) {
+fun ScalingLazyDemo(
+    contentPadding: PaddingValues = PaddingValues(),
+    content: ScalingLazyListScope.() -> Unit
+) {
     val scrollState = rememberScalingLazyListState()
     ScreenScaffold(scrollState = scrollState) {
         ScalingLazyColumn(
             state = scrollState,
             modifier = Modifier.fillMaxSize(),
             horizontalAlignment = Alignment.CenterHorizontally,
+            contentPadding = contentPadding,
             content = content
         )
     }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index b7e97ab..7c13585 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -43,6 +43,7 @@
         listOf(
             ComposableDemo("Scaffold") { ScaffoldSample() },
             Material3DemoCategory("ScrollAway", ScrollAwayDemos),
+            ComposableDemo("Haptics") { Centralize { HapticsDemos() } },
             Material3DemoCategory(
                 "Button",
                 listOf(
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 5a629cc..e43fb98 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,8 +25,8 @@
     defaultConfig {
         applicationId "androidx.wear.compose.integration.demos"
         minSdk 25
-        versionCode 31
-        versionName "1.31"
+        versionCode 32
+        versionName "1.32"
     }
 
     buildTypes {
diff --git a/wear/protolayout/protolayout-expression-pipeline/build.gradle b/wear/protolayout/protolayout-expression-pipeline/build.gradle
index d82751b..0a5898e 100644
--- a/wear/protolayout/protolayout-expression-pipeline/build.gradle
+++ b/wear/protolayout/protolayout-expression-pipeline/build.gradle
@@ -35,7 +35,7 @@
     implementation("androidx.collection:collection:1.2.0")
     implementation("androidx.core:core:1.7.0")
     implementation("androidx.concurrent:concurrent-futures:1.1.0")
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation(project(":wear:protolayout:protolayout-proto"))
     implementation(project(":wear:protolayout:protolayout-expression"))
 
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java
index a1b629b..36152d5 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/NumberFormatter.java
@@ -29,7 +29,6 @@
 import android.os.Build.VERSION_CODES;
 import android.util.Log;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
@@ -123,19 +122,16 @@
     @RequiresApi(VERSION_CODES.R)
     private static class Api30Impl {
         @NonNull
-        @DoNotInline
         static String callFormatToString(LocalizedNumberFormatter mFmt, int value) {
             return mFmt.format(value).toString();
         }
 
         @NonNull
-        @DoNotInline
         static String callFormatToString(LocalizedNumberFormatter mFmt, float value) {
             return mFmt.format(value).toString();
         }
 
         @NonNull
-        @DoNotInline
         static LocalizedNumberFormatter buildLocalizedNumberFormatter(
                 int minIntegerDigits,
                 int minFractionDigits,
diff --git a/wear/protolayout/protolayout-expression/build.gradle b/wear/protolayout/protolayout-expression/build.gradle
index 4b3638f..e21ab55 100644
--- a/wear/protolayout/protolayout-expression/build.gradle
+++ b/wear/protolayout/protolayout-expression/build.gradle
@@ -32,7 +32,7 @@
     annotationProcessor(libs.nullaway)
     api("androidx.annotation:annotation:1.2.0")
 
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation("androidx.collection:collection:1.2.0")
     implementation(project(":wear:protolayout:protolayout-proto"))
 
diff --git a/wear/protolayout/protolayout-material-core/build.gradle b/wear/protolayout/protolayout-material-core/build.gradle
index dcf081a..ca1740a 100644
--- a/wear/protolayout/protolayout-material-core/build.gradle
+++ b/wear/protolayout/protolayout-material-core/build.gradle
@@ -26,6 +26,7 @@
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
+    id("kotlin-android")
 }
 
 android {
@@ -55,6 +56,7 @@
     testImplementation(libs.testRunner)
     testImplementation(libs.testRules)
     testImplementation(libs.truth)
+    testImplementation("androidx.core:core-ktx:1.13.1")
 }
 
 androidx {
diff --git a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverter.java b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverter.java
new file mode 100644
index 0000000..98351ea
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverter.java
@@ -0,0 +1,155 @@
+/*
+ * 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.protolayout.materialcore.fontscaling;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.Arrays;
+
+/**
+ * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
+ * "dp" dimension according to a non-linear curve.
+ *
+ * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more
+ * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen.
+ *
+ * <p>The thinking here is that large fonts are already big enough to read, but we still want to
+ * scale them slightly to preserve the visual hierarchy when compared to smaller fonts.
+ */
+// This is copied from
+// https://cs.android.com/android/_/android/platform/frameworks/base/+/2a4e99a798cc69944f64d54b81aee987fbea45d6:core/java/android/content/res/FontScaleConverter.java
+// TODO: b/342359552 - Use Android Platform api instead when it becomes public.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FontScaleConverter {
+
+    final float[] mFromSpValues;
+    final float[] mToDpValues;
+
+    /**
+     * Creates a lookup table for the given conversions.
+     *
+     * <p>Any "sp" value not in the lookup table will be derived via linear interpolation.
+     *
+     * <p>The arrays must be sorted ascending and monotonically increasing.
+     *
+     * @param fromSp array of dimensions in SP
+     * @param toDp array of dimensions in DP that correspond to an SP value in fromSp
+     * @throws IllegalArgumentException if the array lengths don't match or are empty
+     */
+    FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) {
+        if (fromSp.length != toDp.length || fromSp.length == 0) {
+            throw new IllegalArgumentException("Array lengths must match and be nonzero");
+        }
+
+        mFromSpValues = fromSp;
+        mToDpValues = toDp;
+    }
+
+    /** Convert a dimension in "dp" back to "sp" using the lookup table. */
+    public float convertDpToSp(float dp) {
+        return lookupAndInterpolate(dp, mToDpValues, mFromSpValues);
+    }
+
+    /** Convert a dimension in "sp" to "dp" using the lookup table. */
+    public float convertSpToDp(float sp) {
+        return lookupAndInterpolate(sp, mFromSpValues, mToDpValues);
+    }
+
+    private static float lookupAndInterpolate(
+            float sourceValue, float[] sourceValues, float[] targetValues) {
+        final float sourceValuePositive = Math.abs(sourceValue);
+        // TODO(b/247861374): find a match at a higher index?
+        final float sign = Math.signum(sourceValue);
+        // We search for exact matches only, even if it's just a little off. The interpolation will
+        // handle any non-exact matches.
+        final int index = Arrays.binarySearch(sourceValues, sourceValuePositive);
+        if (index >= 0) {
+            // exact match, return the matching dp
+            return sign * targetValues[index];
+        } else {
+            // must be a value in between index and index + 1: interpolate.
+            final int lowerIndex = -(index + 1) - 1;
+
+            final float startSp;
+            final float endSp;
+            final float startDp;
+            final float endDp;
+
+            if (lowerIndex >= sourceValues.length - 1) {
+                // It's past our lookup table. Determine the last elements' scaling factor and use.
+                startSp = sourceValues[sourceValues.length - 1];
+                startDp = targetValues[sourceValues.length - 1];
+
+                if (startSp == 0) {
+                    return 0;
+                }
+
+                final float scalingFactor = startDp / startSp;
+                return sourceValue * scalingFactor;
+            } else if (lowerIndex == -1) {
+                // It's smaller than the smallest value in our table. Interpolate from 0.
+                startSp = 0;
+                startDp = 0;
+                endSp = sourceValues[0];
+                endDp = targetValues[0];
+            } else {
+                startSp = sourceValues[lowerIndex];
+                endSp = sourceValues[lowerIndex + 1];
+                startDp = targetValues[lowerIndex];
+                endDp = targetValues[lowerIndex + 1];
+            }
+
+            return sign
+                    * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, sourceValuePositive);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null) {
+            return false;
+        }
+        if (!(o instanceof FontScaleConverter)) {
+            return false;
+        }
+        FontScaleConverter that = (FontScaleConverter) o;
+        return Arrays.equals(mFromSpValues, that.mFromSpValues)
+                && Arrays.equals(mToDpValues, that.mToDpValues);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Arrays.hashCode(mFromSpValues);
+        result = 31 * result + Arrays.hashCode(mToDpValues);
+        return result;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "FontScaleConverter{"
+                + "fromSpValues="
+                + Arrays.toString(mFromSpValues)
+                + ", toDpValues="
+                + Arrays.toString(mToDpValues)
+                + '}';
+    }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactory.java b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactory.java
new file mode 100644
index 0000000..5b8b3f7
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactory.java
@@ -0,0 +1,181 @@
+/*
+ * 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.protolayout.materialcore.fontscaling;
+
+import android.content.res.Configuration;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+
+/** Stores lookup tables for creating {@link FontScaleConverter}s at various scales. */
+// This is copied from
+// https://cs.android.com/android/_/android/platform/frameworks/base/+/2a4e99a798cc69944f64d54b81aee987fbea45d6:core/java/android/content/res/FontScaleConverterFactory.java
+// TODO: b/342359552 - Use Android Platform api instead when it becomes public.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FontScaleConverterFactory {
+    private static final float SCALE_KEY_MULTIPLIER = 100f;
+
+    @VisibleForTesting
+    static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>();
+
+    @SuppressWarnings("NonFinalStaticField")
+    private static float sMinScaleBeforeCurvesApplied = 1.05f;
+
+    static {
+        // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and
+        // manually tweaked for optimum readability.
+        put(
+                /* scaleKey= */ 1.15f,
+                new FontScaleConverter(
+                        /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+                        /* toDp= */ new float[] {
+                            9.2f, 11.5f, 13.8f, 16.4f, 19.8f, 21.8f, 25.2f, 30f, 100
+                        }));
+
+        put(
+                /* scaleKey= */ 1.3f,
+                new FontScaleConverter(
+                        /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+                        /* toDp= */ new float[] {
+                            10.4f, 13f, 15.6f, 18.8f, 21.6f, 23.6f, 26.4f, 30f, 100
+                        }));
+
+        put(
+                /* scaleKey= */ 1.5f,
+                new FontScaleConverter(
+                        /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+                        /* toDp= */ new float[] {12f, 15f, 18f, 22f, 24f, 26f, 28f, 30f, 100}));
+
+        put(
+                /* scaleKey= */ 1.8f,
+                new FontScaleConverter(
+                        /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+                        /* toDp= */ new float[] {
+                            14.4f, 18f, 21.6f, 24.4f, 27.6f, 30.8f, 32.8f, 34.8f, 100
+                        }));
+
+        put(
+                /* scaleKey= */ 2f,
+                new FontScaleConverter(
+                        /* fromSp= */ new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+                        /* toDp= */ new float[] {16f, 20f, 24f, 26f, 30f, 34f, 36f, 38f, 100}));
+
+        sMinScaleBeforeCurvesApplied = getScaleFromKey(LOOKUP_TABLES.keyAt(0)) - 0.02f;
+        if (sMinScaleBeforeCurvesApplied <= 1.0f) {
+            throw new IllegalStateException(
+                    "You should only apply non-linear scaling to font scales > 1");
+        }
+    }
+
+    private FontScaleConverterFactory() {}
+
+    /**
+     * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+     * if the scaling would follow a linear curve or for no scaling.
+     *
+     * <p>Example usage: <code>
+     * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
+     */
+    public static boolean isNonLinearFontScalingActive(float fontScale) {
+        return fontScale >= sMinScaleBeforeCurvesApplied;
+    }
+
+    /**
+     * Finds a matching FontScaleConverter for the given fontScale factor.
+     *
+     * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+     * @return a converter for the given scale, or null if non-linear scaling should not be used.
+     */
+    @Nullable
+    public static FontScaleConverter forScale(float fontScale) {
+        if (!isNonLinearFontScalingActive(fontScale)) {
+            return null;
+        }
+
+        FontScaleConverter lookupTable = get(fontScale);
+        if (lookupTable != null) {
+            return lookupTable;
+        }
+
+        // Didn't find an exact match: interpolate between two existing tables
+        final int index = LOOKUP_TABLES.indexOfKey(getKey(fontScale));
+        if (index >= 0) {
+            // This should never happen, should have been covered by get() above.
+            return LOOKUP_TABLES.valueAt(index);
+        }
+        // Didn't find an exact match: interpolate between two existing tables
+        final int lowerIndex = -(index + 1) - 1;
+        final int higherIndex = lowerIndex + 1;
+        if (lowerIndex < 0 || higherIndex >= LOOKUP_TABLES.size()) {
+            // We have gone beyond our bounds and have nothing to interpolate between. Just give
+            // them a straight linear table instead.
+            // This works because when FontScaleConverter encounters a size beyond its bounds, it
+            // calculates a linear fontScale factor using the ratio of the last element pair.
+            return new FontScaleConverter(new float[] {1f}, new float[] {fontScale});
+        } else {
+            float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex));
+            float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex));
+            float interpolationPoint =
+                    MathUtils.constrainedMap(
+                            /* rangeMin= */ 0f,
+                            /* rangeMax= */ 1f,
+                            startScale,
+                            endScale,
+                            fontScale);
+            return createInterpolatedTableBetween(
+                    LOOKUP_TABLES.valueAt(lowerIndex),
+                    LOOKUP_TABLES.valueAt(higherIndex),
+                    interpolationPoint);
+        }
+    }
+
+    @NonNull
+    private static FontScaleConverter createInterpolatedTableBetween(
+            FontScaleConverter start, FontScaleConverter end, float interpolationPoint) {
+        float[] commonSpSizes = new float[] {8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100f};
+        float[] dpInterpolated = new float[commonSpSizes.length];
+
+        for (int i = 0; i < commonSpSizes.length; i++) {
+            float sp = commonSpSizes[i];
+            float startDp = start.convertSpToDp(sp);
+            float endDp = end.convertSpToDp(sp);
+            dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint);
+        }
+
+        return new FontScaleConverter(commonSpSizes, dpInterpolated);
+    }
+
+    private static int getKey(float fontScale) {
+        return (int) (fontScale * SCALE_KEY_MULTIPLIER);
+    }
+
+    private static float getScaleFromKey(int key) {
+        return (float) key / SCALE_KEY_MULTIPLIER;
+    }
+
+    private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
+        LOOKUP_TABLES.put(getKey(scaleKey), fontScaleConverter);
+    }
+
+    @Nullable
+    private static FontScaleConverter get(float scaleKey) {
+        return LOOKUP_TABLES.get(getKey(scaleKey));
+    }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/MathUtils.java b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/MathUtils.java
new file mode 100644
index 0000000..5773d03
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/fontscaling/MathUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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.protolayout.materialcore.fontscaling;
+
+import static java.lang.Math.min;
+
+/** A class that contains utility methods related to numbers. */
+final class MathUtils {
+    private MathUtils() {}
+
+    /**
+     * Returns the linear interpolation of {@code amount} between {@code start} and {@code stop}.
+     */
+    static float lerp(float start, float stop, float amount) {
+        return start + (stop - start) * amount;
+    }
+
+    /**
+     * Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link
+     * #lerp}{@code (a, b, s)}
+     *
+     * <p>If {@code a == b}, then this function will return 0.
+     */
+    static float lerpInv(float a, float b, float value) {
+        return a != b ? ((value - a) / (b - a)) : 0.0f;
+    }
+
+    /** Returns the single argument constrained between [0.0, 1.0]. */
+    static float saturate(float value) {
+        return value < 0.0f ? 0.0f : min(1.0f, value);
+    }
+
+    /** Returns the saturated (constrained between [0, 1]) result of {@link #lerpInv}. */
+    static float lerpInvSat(float a, float b, float value) {
+        return saturate(lerpInv(a, b, value));
+    }
+
+    /**
+     * Calculates a value in [rangeMin, rangeMax] that maps value in [valueMin, valueMax] to
+     * returnVal in [rangeMin, rangeMax].
+     *
+     * <p>Always returns a constrained value in the range [rangeMin, rangeMax], even if value is
+     * outside [valueMin, valueMax].
+     *
+     * <p>Eg: constrainedMap(0f, 100f, 0f, 1f, 0.5f) = 50f constrainedMap(20f, 200f, 10f, 20f, 20f)
+     * = 200f constrainedMap(20f, 200f, 10f, 20f, 50f) = 200f constrainedMap(10f, 50f, 10f, 20f, 5f)
+     * = 10f
+     *
+     * @param rangeMin minimum of the range that should be returned.
+     * @param rangeMax maximum of the range that should be returned.
+     * @param valueMin minimum of range to map {@code value} to.
+     * @param valueMax maximum of range to map {@code value} to.
+     * @param value to map to the range [{@code valueMin}, {@code valueMax}]. Note, can be outside
+     *     this range, resulting in a clamped value.
+     * @return the mapped value, constrained to [{@code rangeMin}, {@code rangeMax}.
+     */
+    static float constrainedMap(
+            float rangeMin, float rangeMax, float valueMin, float valueMax, float value) {
+        return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value));
+    }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactoryTest.kt b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactoryTest.kt
new file mode 100644
index 0000000..841825b
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterFactoryTest.kt
@@ -0,0 +1,232 @@
+/*
+ * 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.protolayout.materialcore.fontscaling
+
+import androidx.core.util.forEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.math.ceil
+import kotlin.math.floor
+import kotlin.random.Random.Default.nextFloat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterFactoryTest {
+
+    @Test
+    fun scale200IsTwiceAtSmallSizes() {
+        val table = FontScaleConverterFactory.forScale(2F)!!
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @LargeTest
+    @Test
+    fun missingLookupTablePastEnd_returnsLinear() {
+        val table = FontScaleConverterFactory.forScale(3F)!!
+        generateSequenceOfFractions(-10000f..10000f, step = 0.01f).map {
+            assertThat(table.convertSpToDp(it)).isWithin(CONVERSION_TOLERANCE).of(it * 3f)
+        }
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(3f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(24f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(15f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+        assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(150f)
+        assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(300f)
+    }
+
+    @SmallTest
+    fun missingLookupTable110_returnsInterpolated() {
+        val table = FontScaleConverterFactory.forScale(1.1F)!!
+
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.1f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.1f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(11f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.1f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+        assertThat(table.convertSpToDp(50F)).isLessThan(50f * 1.1f)
+        assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.1f)
+    }
+
+    @Test
+    fun missingLookupTable199_returnsInterpolated() {
+        val table = FontScaleConverterFactory.forScale(1.9999F)!!
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun missingLookupTable160_returnsInterpolated() {
+        val table = FontScaleConverterFactory.forScale(1.6F)!!
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f * 1.6F)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.6F)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f * 1.6F)
+        assertThat(table.convertSpToDp(20F)).isLessThan(20f * 1.6F)
+        assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.6F)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.6F)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @SmallTest
+    fun missingLookupTableNegativeReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(-1F)).isNull()
+    }
+
+    @SmallTest
+    fun unnecessaryFontScalesReturnsNull() {
+        assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
+        assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
+        assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
+    }
+
+    @SmallTest
+    fun tablesMatchAndAreMonotonicallyIncreasing() {
+        FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable ->
+            assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size)
+            assertThat(lookupTable.mToDpValues).isNotEmpty()
+
+            assertThat(lookupTable.mFromSpValues.asList()).isInStrictOrder()
+            assertThat(lookupTable.mToDpValues.asList()).isInStrictOrder()
+
+            assertThat(lookupTable.mFromSpValues.asList()).containsNoDuplicates()
+            assertThat(lookupTable.mToDpValues.asList()).containsNoDuplicates()
+        }
+    }
+
+    @SmallTest
+    fun testIsNonLinearFontScalingActive() {
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f)).isTrue()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.5f)).isTrue()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(2f)).isTrue()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(3f)).isTrue()
+    }
+
+    @LargeTest
+    @Test
+    fun allFeasibleScalesAndConversionsDoNotCrash() {
+        generateSequenceOfFractions(-10f..10f, step = 0.1f)
+            .fuzzFractions()
+            .mapNotNull { FontScaleConverterFactory.forScale(it) }
+            .flatMap { table ->
+                generateSequenceOfFractions(-2000f..2000f, step = 0.1f).fuzzFractions().map {
+                    Pair(table, it)
+                }
+            }
+            .forEach { (table, sp) ->
+                try {
+                    // Truth is slow because it creates a bunch of
+                    // objects. Don't use it unless we need to.
+                    if (!table.convertSpToDp(sp).isFinite()) {
+                        assertWithMessage("convertSpToDp(%s) on table: %s", sp, table)
+                            .that(table.convertSpToDp(sp))
+                            .isFinite()
+                    }
+                } catch (e: Exception) {
+                    throw AssertionError("Exception during convertSpToDp($sp) on table: $table", e)
+                }
+            }
+    }
+
+    @Test
+    fun testGenerateSequenceOfFractions() {
+        val fractions = generateSequenceOfFractions(-1000f..1000f, step = 0.1f).toList()
+        fractions.forEach {
+            assertThat(it).isAtLeast(-1000f)
+            assertThat(it).isAtMost(1000f)
+        }
+
+        assertThat(fractions).isInStrictOrder()
+        assertThat(fractions).hasSize(1000 * 2 * 10 + 1) // Don't forget the 0 in the middle!
+
+        assertThat(fractions).contains(100f)
+        assertThat(fractions).contains(500.1f)
+        assertThat(fractions).contains(500.2f)
+        assertThat(fractions).contains(0.2f)
+        assertThat(fractions).contains(0f)
+        assertThat(fractions).contains(-10f)
+        assertThat(fractions).contains(-10f)
+        assertThat(fractions).contains(-10.3f)
+
+        assertThat(fractions).doesNotContain(-10.31f)
+        assertThat(fractions).doesNotContain(0.35f)
+        assertThat(fractions).doesNotContain(0.31f)
+        assertThat(fractions).doesNotContain(-.35f)
+    }
+
+    @Test
+    fun testFuzzFractions() {
+        val numFuzzedFractions = 6
+        val fractions =
+            generateSequenceOfFractions(-1000f..1000f, step = 0.1f).fuzzFractions().toList()
+        fractions.forEach {
+            assertThat(it).isAtLeast(-1000f)
+            assertThat(it).isLessThan(1001f)
+        }
+
+        val numGeneratedFractions = 1000 * 2 * 10 + 1 // Don't forget the 0 in the middle!
+        assertThat(fractions).hasSize(numGeneratedFractions * numFuzzedFractions)
+
+        assertThat(fractions).contains(100f)
+        assertThat(fractions).contains(500.1f)
+        assertThat(fractions).contains(500.2f)
+        assertThat(fractions).contains(0.2f)
+        assertThat(fractions).contains(0f)
+        assertThat(fractions).contains(-10f)
+        assertThat(fractions).contains(-10f)
+        assertThat(fractions).contains(-10.3f)
+    }
+
+    companion object {
+        private const val CONVERSION_TOLERANCE = 0.05f
+    }
+}
+
+fun generateSequenceOfFractions(
+    range: ClosedFloatingPointRange<Float>,
+    step: Float
+): Sequence<Float> {
+    val multiplier = 1f / step
+    val start = floor(range.start * multiplier).toInt()
+    val endInclusive = ceil(range.endInclusive * multiplier).toInt()
+    return generateSequence(start) { it + 1 }
+        .takeWhile { it <= endInclusive }
+        .map { it.toFloat() / multiplier }
+}
+
+private fun Sequence<Float>.fuzzFractions(): Sequence<Float> {
+    return flatMap { i ->
+        listOf(i, i + 0.01f, i + 0.054f, i + 0.099f, i + nextFloat(), i + nextFloat())
+    }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterTest.kt b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterTest.kt
new file mode 100644
index 0000000..7dc342a
--- /dev/null
+++ b/wear/protolayout/protolayout-material-core/src/test/java/androidx/wear/protolayout/materialcore/fontscaling/FontScaleConverterTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.protolayout.materialcore.fontscaling
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FontScaleConverterTest {
+
+    @Test
+    fun straightInterpolation() {
+        val table = createTable(8f to 8f, 10f to 10f, 20f to 20f)
+        verifyConversionBothWays(table, 1f, 1F)
+        verifyConversionBothWays(table, 8f, 8F)
+        verifyConversionBothWays(table, 10f, 10F)
+        verifyConversionBothWays(table, 30f, 30F)
+        verifyConversionBothWays(table, 20f, 20F)
+        verifyConversionBothWays(table, 5f, 5F)
+        verifyConversionBothWays(table, 0f, 0F)
+    }
+
+    @Test
+    fun interpolate200Percent() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        verifyConversionBothWays(table, 2f, 1F)
+        verifyConversionBothWays(table, 16f, 8F)
+        verifyConversionBothWays(table, 20f, 10F)
+        verifyConversionBothWays(table, 60f, 30F)
+        verifyConversionBothWays(table, 40f, 20F)
+        verifyConversionBothWays(table, 10f, 5F)
+        verifyConversionBothWays(table, 0f, 0F)
+    }
+
+    @Test
+    fun interpolate150Percent() {
+        val table = createTable(2f to 3f, 10f to 15f, 20f to 30f, 100f to 150f)
+        verifyConversionBothWays(table, 3f, 2F)
+        verifyConversionBothWays(table, 1.5f, 1F)
+        verifyConversionBothWays(table, 12f, 8F)
+        verifyConversionBothWays(table, 15f, 10F)
+        verifyConversionBothWays(table, 30f, 20F)
+        verifyConversionBothWays(table, 75f, 50F)
+        verifyConversionBothWays(table, 7.5f, 5F)
+        verifyConversionBothWays(table, 0f, 0F)
+    }
+
+    @Test
+    fun pastEndsUsesLastScalingFactor() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        verifyConversionBothWays(table, 200f, 100F)
+        verifyConversionBothWays(table, 62f, 31F)
+        verifyConversionBothWays(table, 2000f, 1000F)
+        verifyConversionBothWays(table, 4000f, 2000F)
+        verifyConversionBothWays(table, 20000f, 10000F)
+    }
+
+    @Test
+    fun negativeSpIsNegativeDp() {
+        val table = createTable(8f to 16f, 10f to 20f, 30f to 60f)
+        verifyConversionBothWays(table, -2f, -1F)
+        verifyConversionBothWays(table, -16f, -8F)
+        verifyConversionBothWays(table, -20f, -10F)
+        verifyConversionBothWays(table, -60f, -30F)
+        verifyConversionBothWays(table, -40f, -20F)
+        verifyConversionBothWays(table, -10f, -5F)
+        verifyConversionBothWays(table, 0f, -0F)
+    }
+
+    private fun createTable(vararg pairs: Pair<Float, Float>) =
+        FontScaleConverter(
+            pairs.map { it.first }.toFloatArray(),
+            pairs.map { it.second }.toFloatArray()
+        )
+
+    private fun verifyConversionBothWays(
+        table: FontScaleConverter,
+        expectedDp: Float,
+        spToConvert: Float
+    ) {
+        assertWithMessage("convertSpToDp")
+            .that(table.convertSpToDp(spToConvert))
+            .isWithin(CONVERSION_TOLERANCE)
+            .of(expectedDp)
+
+        assertWithMessage("inverse: convertDpToSp")
+            .that(table.convertDpToSp(expectedDp))
+            .isWithin(CONVERSION_TOLERANCE)
+            .of(spToConvert)
+    }
+
+    companion object {
+        private const val CONVERSION_TOLERANCE = 0.05f
+    }
+}
diff --git a/wear/protolayout/protolayout-material/build.gradle b/wear/protolayout/protolayout-material/build.gradle
index be951af..a979b44 100644
--- a/wear/protolayout/protolayout-material/build.gradle
+++ b/wear/protolayout/protolayout-material/build.gradle
@@ -36,7 +36,7 @@
 
     implementation(project(":wear:protolayout:protolayout-material-core"))
     implementation(project(":wear:protolayout:protolayout-proto"))
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
 
     lintChecks(project(":wear:protolayout:protolayout-lint"))
     lintPublish(project(":wear:protolayout:protolayout-lint"))
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java
index 0299154..7dd4feb 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenTest.java
@@ -67,21 +67,10 @@
 
     @Parameterized.Parameters(name = "{0}")
     public static Collection<Object[]> data() {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
         float scale = displayMetrics.density;
 
-        InstrumentationRegistry.getInstrumentation()
-                .getContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-        InstrumentationRegistry.getInstrumentation()
-                .getTargetContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-
         DeviceParameters deviceParameters =
                 new DeviceParameters.Builder()
                         .setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java
index 61734b9..ef36a3d 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/MaterialGoldenXLTest.java
@@ -16,11 +16,12 @@
 
 package androidx.wear.protolayout.material;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_HEIGHT;
 import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_WIDTH;
 import static androidx.wear.protolayout.material.RunnerUtils.convertToTestParameters;
+import static androidx.wear.protolayout.material.RunnerUtils.getFontScale;
 import static androidx.wear.protolayout.material.RunnerUtils.runSingleScreenshotTest;
+import static androidx.wear.protolayout.material.RunnerUtils.setFontScale;
 import static androidx.wear.protolayout.material.RunnerUtils.waitForNotificationToDisappears;
 import static androidx.wear.protolayout.material.TestCasesGenerator.XXXL_SCALE_SUFFIX;
 import static androidx.wear.protolayout.material.TestCasesGenerator.generateTestCases;
@@ -38,6 +39,8 @@
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
 import androidx.wear.protolayout.material.RunnerUtils.TestCase;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,15 +53,10 @@
 @RunWith(Parameterized.class)
 @LargeTest
 public class MaterialGoldenXLTest {
-    /* We set DisplayMetrics in the data() method for creating test cases. However, when running all
-    tests together, first all parametrization (data()) methods are called, and then individual
-    tests, causing that actual DisplayMetrics will be different. So we need to restore it before
-    each test. */
-    private static final DisplayMetrics DISPLAY_METRICS_FOR_TEST = new DisplayMetrics();
-    private static final DisplayMetrics OLD_DISPLAY_METRICS = new DisplayMetrics();
-
     private static final float FONT_SCALE_XXXL = 1.24f;
 
+    private static float originalFontScale;
+
     private final TestCase mTestCase;
     private final String mExpected;
 
@@ -76,27 +74,18 @@
         return (int) ((px - 0.5f) / scale);
     }
 
-    @SuppressWarnings("deprecation")
     @Parameterized.Parameters(name = "{0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> data() throws Exception {
+        // These "parameters" methods are called before any parameterized test (from any class)
+        // executes. We set and later reset the font here to have the correct context during test
+        // generation. We later set and reset the font for the actual test in BeforeClass/AfterClass
+        // methods.
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        DisplayMetrics currentDisplayMetrics = new DisplayMetrics();
+        originalFontScale =
+                getFontScale(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        setFontScale(context, FONT_SCALE_XXXL);
+
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
-        currentDisplayMetrics.setTo(displayMetrics);
-        displayMetrics.scaledDensity *= FONT_SCALE_XXXL;
-
-        InstrumentationRegistry.getInstrumentation()
-                .getContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-        InstrumentationRegistry.getInstrumentation()
-                .getTargetContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-
-        DISPLAY_METRICS_FOR_TEST.setTo(displayMetrics);
 
         float scale = displayMetrics.density;
         DeviceParameters deviceParameters =
@@ -104,6 +93,7 @@
                         .setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
                         .setScreenHeightDp(pxToDp(SCREEN_HEIGHT, scale))
                         .setScreenDensity(displayMetrics.density)
+                        .setFontScale(context.getResources().getConfiguration().fontScale)
                         // Not important for components.
                         .setScreenShape(DeviceParametersBuilders.SCREEN_SHAPE_RECT)
                         .build();
@@ -126,31 +116,22 @@
                         /* isForLtr= */ true));
 
         // Restore state before this method, so other test have correct context.
-        InstrumentationRegistry.getInstrumentation()
-                .getContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(currentDisplayMetrics);
-        InstrumentationRegistry.getInstrumentation()
-                .getTargetContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(currentDisplayMetrics);
-
+        setFontScale(context, originalFontScale);
         waitForNotificationToDisappears();
 
         return testCaseList;
     }
 
-    @Parameterized.BeforeParam
-    public static void restoreBefore() {
-        OLD_DISPLAY_METRICS.setTo(getApplicationContext().getResources().getDisplayMetrics());
-        getApplicationContext().getResources().getDisplayMetrics().setTo(DISPLAY_METRICS_FOR_TEST);
+    @Before
+    public void setUp() {
+        setFontScale(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), FONT_SCALE_XXXL);
     }
 
-    @Parameterized.AfterParam
-    public static void restoreAfter() {
-        getApplicationContext().getResources().getDisplayMetrics().setTo(OLD_DISPLAY_METRICS);
+    @After
+    public void tearDown() {
+        setFontScale(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), originalFontScale);
     }
 
     @Test
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java
index 0fe42d3..dd901e5 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/RunnerUtils.java
@@ -17,8 +17,9 @@
 package androidx.wear.protolayout.material;
 
 import android.annotation.SuppressLint;
-import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -70,53 +71,39 @@
         startIntent.putExtra("layout", layoutPayload);
         startIntent.putExtra(GoldenTestActivity.USE_RTL_DIRECTION, isRtlDirection);
 
-        ActivityScenario<GoldenTestActivity> scenario = ActivityScenario.launch(startIntent);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        try (ActivityScenario<GoldenTestActivity> scenario = ActivityScenario.launch(startIntent)) {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
-        try {
-            // Wait 1s after launching the activity. This allows for the old white layout in the
-            // bootstrap activity to fully go away before proceeding.
-            Thread.sleep(1000);
-        } catch (Exception ex) {
-            if (ex instanceof InterruptedException) {
-                Thread.currentThread().interrupt();
+            try {
+                // Wait 1s after launching the activity. This allows for the old white layout in the
+                // bootstrap activity to fully go away before proceeding.
+                Thread.sleep(100);
+            } catch (Exception ex) {
+                if (ex instanceof InterruptedException) {
+                    Thread.currentThread().interrupt();
+                }
+                Log.e("MaterialGoldenTest", "Error sleeping", ex);
             }
-            Log.e("MaterialGoldenTest", "Error sleeping", ex);
-        }
 
-        DisplayMetrics displayMetrics =
-                InstrumentationRegistry.getInstrumentation()
-                        .getTargetContext()
-                        .getResources()
-                        .getDisplayMetrics();
+            DisplayMetrics displayMetrics =
+                    InstrumentationRegistry.getInstrumentation()
+                            .getTargetContext()
+                            .getResources()
+                            .getDisplayMetrics();
 
-        // RTL will put the View on the right side.
-        int screenWidthStart = isRtlDirection ? displayMetrics.widthPixels - SCREEN_WIDTH : 0;
+            // RTL will put the View on the right side.
+            int screenWidthStart = isRtlDirection ? displayMetrics.widthPixels - SCREEN_WIDTH : 0;
 
-        Bitmap bitmap =
-                Bitmap.createBitmap(
-                        InstrumentationRegistry.getInstrumentation()
-                                .getUiAutomation()
-                                .takeScreenshot(),
-                        screenWidthStart,
-                        0,
-                        SCREEN_WIDTH,
-                        SCREEN_HEIGHT);
-        rule.assertBitmapAgainstGolden(bitmap, expected, new MSSIMMatcher());
-
-        // There's a weird bug (related to b/159805732) where, when calling .close() on
-        // ActivityScenario or calling finish() and immediately exiting the test, the test can hang
-        // on a white screen for 45s. Closing the activity here and waiting for 1s seems to fix
-        // this.
-        scenario.onActivity(Activity::finish);
-
-        try {
-            Thread.sleep(1000);
-        } catch (Exception ex) {
-            if (ex instanceof InterruptedException) {
-                Thread.currentThread().interrupt();
-            }
-            Log.e("MaterialGoldenTest", "Error sleeping", ex);
+            Bitmap bitmap =
+                    Bitmap.createBitmap(
+                            InstrumentationRegistry.getInstrumentation()
+                                    .getUiAutomation()
+                                    .takeScreenshot(),
+                            screenWidthStart,
+                            0,
+                            SCREEN_WIDTH,
+                            SCREEN_HEIGHT);
+            rule.assertBitmapAgainstGolden(bitmap, expected, new MSSIMMatcher());
         }
     }
 
@@ -153,4 +140,17 @@
             this.isForLtr = isForLtr;
         }
     }
+
+    public static float getFontScale(Context context) {
+        return context.getResources().getConfiguration().fontScale;
+    }
+
+    @SuppressWarnings("deprecation")
+    public static void setFontScale(Context context, float fontScale) {
+        Configuration newConfiguration =
+                new Configuration(context.getResources().getConfiguration());
+        newConfiguration.fontScale = fontScale;
+        context.getResources()
+                .updateConfiguration(newConfiguration, context.getResources().getDisplayMetrics());
+    }
 }
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java
index c613323..9685084e 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenTest.java
@@ -65,21 +65,10 @@
 
     @Parameterized.Parameters(name = "{0}")
     public static Collection<Object[]> data() {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
         float scale = displayMetrics.density;
 
-        InstrumentationRegistry.getInstrumentation()
-                .getContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-        InstrumentationRegistry.getInstrumentation()
-                .getTargetContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-
         DeviceParameters deviceParameters =
                 new DeviceParameters.Builder()
                         .setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java
index 3c49e92..dc82303 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/LayoutsGoldenXLTest.java
@@ -16,11 +16,12 @@
 
 package androidx.wear.protolayout.material.layouts;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_HEIGHT;
 import static androidx.wear.protolayout.material.RunnerUtils.SCREEN_WIDTH;
 import static androidx.wear.protolayout.material.RunnerUtils.convertToTestParameters;
+import static androidx.wear.protolayout.material.RunnerUtils.getFontScale;
 import static androidx.wear.protolayout.material.RunnerUtils.runSingleScreenshotTest;
+import static androidx.wear.protolayout.material.RunnerUtils.setFontScale;
 import static androidx.wear.protolayout.material.RunnerUtils.waitForNotificationToDisappears;
 import static androidx.wear.protolayout.material.layouts.TestCasesGenerator.XXXL_SCALE_SUFFIX;
 import static androidx.wear.protolayout.material.layouts.TestCasesGenerator.generateTestCases;
@@ -36,6 +37,8 @@
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
 import androidx.wear.protolayout.material.RunnerUtils.TestCase;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,15 +50,10 @@
 @RunWith(Parameterized.class)
 @LargeTest
 public class LayoutsGoldenXLTest {
-    /* We set DisplayMetrics in the data() method for creating test cases. However, when running all
-    tests together, first all parametrization (data()) methods are called, and then individual
-    tests, causing that actual DisplayMetrics will be different. So we need to restore it before
-    each test. */
-    private static final DisplayMetrics DISPLAY_METRICS_FOR_TEST = new DisplayMetrics();
-    private static final DisplayMetrics OLD_DISPLAY_METRICS = new DisplayMetrics();
-
     private static final float FONT_SCALE_XXXL = 1.24f;
 
+    private static float originalFontScale;
+
     private final TestCase mTestCase;
     private final String mExpected;
 
@@ -73,27 +71,18 @@
         return (int) ((px - 0.5f) / scale);
     }
 
-    @SuppressWarnings("deprecation")
     @Parameterized.Parameters(name = "{0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> data() throws Exception {
+        // These "parameters" methods are called before any parameterized test (from any class)
+        // executes. We set and later reset the font here to have the correct context during test
+        // generation. We later set and reset the font for the actual test in BeforeClass/AfterClass
+        // methods.
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        DisplayMetrics currentDisplayMetrics = new DisplayMetrics();
+        originalFontScale =
+                getFontScale(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        setFontScale(context, FONT_SCALE_XXXL);
+
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
-        currentDisplayMetrics.setTo(displayMetrics);
-        displayMetrics.scaledDensity *= FONT_SCALE_XXXL;
-
-        InstrumentationRegistry.getInstrumentation()
-                .getContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-        InstrumentationRegistry.getInstrumentation()
-                .getTargetContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(displayMetrics);
-
-        DISPLAY_METRICS_FOR_TEST.setTo(displayMetrics);
 
         float scale = displayMetrics.density;
         DeviceParameters deviceParameters =
@@ -101,6 +90,7 @@
                         .setScreenWidthDp(pxToDp(SCREEN_WIDTH, scale))
                         .setScreenHeightDp(pxToDp(SCREEN_HEIGHT, scale))
                         .setScreenDensity(displayMetrics.density)
+                        .setFontScale(context.getResources().getConfiguration().fontScale)
                         // TODO(b/231543947): Add test cases for round screen.
                         .setScreenShape(DeviceParametersBuilders.SCREEN_SHAPE_RECT)
                         .build();
@@ -111,40 +101,23 @@
                         /* isForRtl= */ true,
                         /* isForLtr= */ true);
 
-        // Restore state before this method, so other test have correct context. This is needed here
-        // too, besides in restoreBefore and restoreAfter as the test cases builder uses the context
-        // to apply font scaling, so we need that display metrics passed in. However, after
-        // generating cases we need to restore the state as other data() methods in this package can
-        // work correctly with the default state, as when the tests are run, first all data() static
-        // methods are called, and then parameterized test cases.
-        InstrumentationRegistry.getInstrumentation()
-                .getContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(currentDisplayMetrics);
-        InstrumentationRegistry.getInstrumentation()
-                .getTargetContext()
-                .getResources()
-                .getDisplayMetrics()
-                .setTo(currentDisplayMetrics);
+        // Restore state before this method, so other test have correct context.
+        setFontScale(context, originalFontScale);
         waitForNotificationToDisappears();
 
         return testCaseList;
     }
 
-    @Parameterized.BeforeParam
-    public static void restoreBefore() {
-        // Set the state as it was in data() method when we generated test cases. This was
-        // overridden by other static data() methods, so we need to restore it.
-        OLD_DISPLAY_METRICS.setTo(getApplicationContext().getResources().getDisplayMetrics());
-        getApplicationContext().getResources().getDisplayMetrics().setTo(DISPLAY_METRICS_FOR_TEST);
+    @Before
+    public void setUp() {
+        setFontScale(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), FONT_SCALE_XXXL);
     }
 
-    @Parameterized.AfterParam
-    public static void restoreAfter() {
-        // Restore the state to default, so the other tests and emulator have the correct starter
-        // state.
-        getApplicationContext().getResources().getDisplayMetrics().setTo(OLD_DISPLAY_METRICS);
+    @After
+    public void tearDown() {
+        setFontScale(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), originalFontScale);
     }
 
     @Test
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java
index 10c457c..8717539d 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/test/GoldenTestActivity.java
@@ -45,12 +45,14 @@
 import com.google.common.util.concurrent.MoreExecutors;
 
 import java.util.Locale;
+import java.util.concurrent.ExecutionException;
 
 @SuppressWarnings("deprecation")
 public class GoldenTestActivity extends Activity {
 
     /** Extra to be put in the intent if test should use RTL direction on parent View. */
     public static final String USE_RTL_DIRECTION = "using_rtl";
+
     private static final String ICON_ID = "icon";
     private static final String ICON_ID_SMALL = "icon_small";
     private static final String AVATAR = "avatar_image";
@@ -81,7 +83,15 @@
                                 .setIsViewFullyVisible(true)
                                 .build());
 
-        instance.renderAndAttach(checkNotNull(layout).toProto(), resources.toProto(), root);
+        try {
+            instance.renderAndAttach(checkNotNull(layout).toProto(), resources.toProto(), root)
+                    .get();
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException(e);
+        }
 
         View firstChild = root.getChildAt(0);
 
@@ -136,7 +146,7 @@
         Locale.setDefault(locale);
         Configuration config = new Configuration();
         config.setLocale(locale);
-        context.getResources().updateConfiguration(
-                config, context.getResources().getDisplayMetrics());
+        context.getResources()
+                .updateConfiguration(config, context.getResources().getDisplayMetrics());
     }
 }
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
index 9e1613e..da9219f 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Typography.java
@@ -16,6 +16,9 @@
 
 package androidx.wear.protolayout.material;
 
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
 import static androidx.annotation.Dimension.DP;
 import static androidx.annotation.Dimension.SP;
 import static androidx.wear.protolayout.DimensionBuilders.sp;
@@ -28,7 +31,6 @@
 
 import android.annotation.SuppressLint;
 import android.content.Context;
-import android.util.DisplayMetrics;
 
 import androidx.annotation.Dimension;
 import androidx.annotation.IntDef;
@@ -40,6 +42,8 @@
 import androidx.wear.protolayout.LayoutElementBuilders.FontStyle;
 import androidx.wear.protolayout.LayoutElementBuilders.FontVariant;
 import androidx.wear.protolayout.LayoutElementBuilders.FontWeight;
+import androidx.wear.protolayout.materialcore.fontscaling.FontScaleConverter;
+import androidx.wear.protolayout.materialcore.fontscaling.FontScaleConverterFactory;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -120,6 +124,9 @@
         TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION2, 16f);
         TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION3, 14f);
     }
+
+    private Typography() {}
+
     /**
      * Returns the {@link FontStyle.Builder} for the given FontStyle code with the recommended size,
      * weight and letter spacing. Font will be scalable.
@@ -130,8 +137,6 @@
         return getFontStyleBuilder(fontStyleCode, context, true);
     }
 
-    private Typography() {}
-
     /**
      * Returns the {@link FontStyle.Builder} for the given Typography code with the recommended
      * size, weight and letter spacing, with the option to make this font not scalable.
@@ -183,17 +188,29 @@
         return sp(checkNotNull(TYPOGRAPHY_TO_LINE_HEIGHT_SP.get(typography)).intValue());
     }
 
-    @NonNull
-    @SuppressLint("ResourceType")
-    @SuppressWarnings("deprecation") // scaledDensity, b/335215227
-    // This is a helper function to make the font not scalable. It should interpret in value as DP
-    // and convert it to SP which is needed to be passed in as a font size. However, we will pass an
-    // SP object to it, because the default style is defined in it, but for the case when the font
-    // size on device in 1, so the DP is equal to SP.
-    private static SpProp dpToSp(@NonNull Context context, @Dimension(unit = DP) float valueDp) {
-        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
-        float scaledSp = (valueDp / metrics.scaledDensity) * metrics.density;
-        return sp(scaledSp);
+    /**
+     * This is a helper function to make the font not scalable. It should interpret in value as DP
+     * and convert it to SP which is needed to be passed in as a font size. However, we will pass an
+     * SP object to it, because the default style is defined in it, but for the case when the font
+     * size on device is 1, so the DP is equal to SP.
+     */
+    @Dimension(unit = SP)
+    private static float dpToSp(float fontScale, @Dimension(unit = DP) float valueDp) {
+        FontScaleConverter converter =
+                (SDK_INT >= UPSIDE_DOWN_CAKE)
+                        ? FontScaleConverterFactory.forScale(fontScale)
+                        : null;
+
+        if (converter == null) {
+            return dpToSpLinear(fontScale, valueDp);
+        }
+
+        return converter.convertDpToSp(valueDp);
+    }
+
+    @Dimension(unit = SP)
+    private static float dpToSpLinear(float fontScale, @Dimension(unit = DP) float valueDp) {
+        return valueDp / fontScale;
     }
 
     // The @Dimension(unit = SP) on sp() is seemingly being ignored, so lint complains that we're
@@ -206,8 +223,9 @@
             float letterSpacing,
             boolean isScalable,
             @NonNull Context context) {
+        float fontScale = context.getResources().getConfiguration().fontScale;
         return new FontStyle.Builder()
-                .setSize(isScalable ? DimensionBuilders.sp(size) : dpToSp(context, size))
+                .setSize(DimensionBuilders.sp(isScalable ? size : dpToSp(fontScale, size)))
                 .setLetterSpacing(DimensionBuilders.em(letterSpacing))
                 .setVariant(variant)
                 .setWeight(weight);
diff --git a/wear/protolayout/protolayout/build.gradle b/wear/protolayout/protolayout/build.gradle
index 5d37b2ad..8b883b5 100644
--- a/wear/protolayout/protolayout/build.gradle
+++ b/wear/protolayout/protolayout/build.gradle
@@ -33,7 +33,7 @@
     api("androidx.annotation:annotation:1.2.0")
     api(project(":wear:protolayout:protolayout-expression"))
 
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation(project(":wear:protolayout:protolayout-proto"))
 
     lintChecks(project(":wear:protolayout:protolayout-lint"))
diff --git a/wear/tiles/tiles/build.gradle b/wear/tiles/tiles/build.gradle
index 8b85e94..9d64b88 100644
--- a/wear/tiles/tiles/build.gradle
+++ b/wear/tiles/tiles/build.gradle
@@ -36,7 +36,7 @@
 
     compileOnly files("../../wear_sdk/wear-sdk.jar")
 
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     implementation("androidx.concurrent:concurrent-futures:1.1.0")
     implementation(project(":wear:tiles:tiles-proto"))
 
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
index 36a655c..ab30109 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
@@ -28,7 +28,6 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -684,7 +683,6 @@
 
     @RequiresApi(34)
     private static class Api34Impl {
-        @DoNotInline
         @NonNull
         static ListenableFuture<List<ActiveTileIdentifier>> getActiveTilesAsync(
                 @NonNull TilesManager tilesManager, @NonNull Executor executor) {
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt
index 18e1f0f..395f381 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/utility/TraceEvent.kt
@@ -18,7 +18,6 @@
 
 import android.os.Build
 import android.os.Trace
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import java.io.Closeable
@@ -57,13 +56,11 @@
     @RequiresApi(Build.VERSION_CODES.Q)
     private object Api29Impl {
         @JvmStatic
-        @DoNotInline
         fun callBeginAsyncSection(traceName: String, traceId: Int) {
             Trace.beginAsyncSection(traceName, traceId)
         }
 
         @JvmStatic
-        @DoNotInline
         fun callEndAsyncSection(traceName: String, traceId: Int) {
             Trace.endAsyncSection(traceName, traceId)
         }
diff --git a/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt
index d79b16d..9719a8c 100644
--- a/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt
+++ b/wear/wear-remote-interactions/src/main/java/androidx/wear/remote/interactions/RemoteInteractionsUtil.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.os.Build
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
 internal object RemoteInteractionsUtil {
@@ -30,7 +29,6 @@
     @RequiresApi(Build.VERSION_CODES.N)
     private object Api24Impl {
         @JvmStatic
-        @DoNotInline
         fun hasSystemFeature(context: Context) =
             context.packageManager.hasSystemFeature(SYSTEM_FEATURE_WATCH)
     }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java
index cb66c5b..d2caca4 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForM.java
@@ -25,7 +25,6 @@
 import android.webkit.WebSettings;
 import android.webkit.WebView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -46,7 +45,6 @@
     /**
      * @see WebMessagePort#postMessage(WebMessage)
      */
-    @DoNotInline
     public static void postMessage(@NonNull WebMessagePort webMessagePort,
             @NonNull WebMessage webMessage) {
         webMessagePort.postMessage(webMessage);
@@ -55,7 +53,6 @@
     /**
      * @see WebMessagePort#close()
      */
-    @DoNotInline
     public static void close(@NonNull WebMessagePort webMessagePort) {
         webMessagePort.close();
     }
@@ -64,7 +61,6 @@
      * Wraps the passed callback in the framework callback type to isolate new types in this class.
      * @see WebMessagePort#setWebMessageCallback(WebMessagePort.WebMessageCallback)
      */
-    @DoNotInline
     public static void setWebMessageCallback(@NonNull WebMessagePort frameworksImpl,
             @NonNull WebMessagePortCompat.WebMessageCallbackCompat callback) {
         frameworksImpl.setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
@@ -81,7 +77,6 @@
      * {@link WebMessagePort#setWebMessageCallback(WebMessagePort.WebMessageCallback, Handler)}
      * Wraps the passed callback in the framework callback type to isolate new types in this class.
      */
-    @DoNotInline
     public static void setWebMessageCallback(@NonNull WebMessagePort frameworksImpl,
             @NonNull WebMessagePortCompat.WebMessageCallbackCompat callback,
             @Nullable Handler handler) {
@@ -97,7 +92,6 @@
     /**
      * @see WebMessage#WebMessage(String, WebMessagePort[])}  WebMessage
      */
-    @DoNotInline
     @NonNull
     public static WebMessage createWebMessage(@NonNull WebMessageCompat message) {
         return new WebMessage(message.getData(),
@@ -107,7 +101,6 @@
     /**
      * @see WebMessageCompat#WebMessageCompat(String, WebMessagePortCompat[])
      */
-    @DoNotInline
     @NonNull
     public static WebMessageCompat createWebMessageCompat(@NonNull WebMessage webMessage) {
         return new WebMessageCompat(webMessage.getData(),
@@ -117,7 +110,6 @@
     /**
      * @see WebResourceError#getErrorCode()
      */
-    @DoNotInline
     public static int getErrorCode(@NonNull WebResourceError webResourceError) {
         return webResourceError.getErrorCode();
     }
@@ -126,7 +118,6 @@
     /**
      * @see WebResourceError#getDescription()
      */
-    @DoNotInline
     @NonNull
     public static CharSequence getDescription(@NonNull WebResourceError webResourceError) {
         return webResourceError.getDescription();
@@ -135,7 +126,6 @@
     /**
      * @see WebSettings#setOffscreenPreRaster(boolean)
      */
-    @DoNotInline
     public static void setOffscreenPreRaster(@NonNull WebSettings webSettings, boolean b) {
         webSettings.setOffscreenPreRaster(b);
     }
@@ -143,7 +133,6 @@
     /**
      * @see WebSettings#getOffscreenPreRaster()
      */
-    @DoNotInline
     public static boolean getOffscreenPreRaster(@NonNull WebSettings webSettings) {
         return webSettings.getOffscreenPreRaster();
     }
@@ -152,7 +141,6 @@
      * Wraps the passed callback in the framework callback type to isolate new types in this class.
      * @see WebView#postVisualStateCallback(long, WebView.VisualStateCallback)
      */
-    @DoNotInline
     public static void postVisualStateCallback(@NonNull WebView webView, long requestId,
             final @NonNull WebViewCompat.VisualStateCallback callback) {
         webView.postVisualStateCallback(requestId, new WebView.VisualStateCallback() {
@@ -166,7 +154,6 @@
     /**
      * @see WebView#postWebMessage(WebMessage, Uri)
      */
-    @DoNotInline
     public static void postWebMessage(@NonNull WebView webView, @NonNull WebMessage message,
             @NonNull Uri targetOrigin) {
         webView.postWebMessage(message, targetOrigin);
@@ -175,7 +162,6 @@
     /**
      * @see WebView#createWebMessageChannel()
      */
-    @DoNotInline
     @NonNull
     public static WebMessagePort[] createWebMessageChannel(@NonNull WebView webView) {
         return webView.createWebMessageChannel();
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java
index 76260d7..a8c61b2 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForN.java
@@ -24,7 +24,6 @@
 import android.webkit.WebResourceRequest;
 import android.webkit.WebSettings;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -45,7 +44,6 @@
     /**
      * @see Context#getDataDir()
      */
-    @DoNotInline
     @NonNull
     public static File getDataDir(@NonNull Context context) {
         return context.getDataDir();
@@ -54,7 +52,6 @@
     /**
      * @see ServiceWorkerController#getInstance()
      */
-    @DoNotInline
     @NonNull
     public static ServiceWorkerController getServiceWorkerControllerInstance() {
         return ServiceWorkerController.getInstance();
@@ -63,7 +60,6 @@
     /**
      * @see ServiceWorkerController#getServiceWorkerWebSettings()
      */
-    @DoNotInline
     @NonNull
     public static ServiceWorkerWebSettings getServiceWorkerWebSettings(
             @NonNull ServiceWorkerController serviceWorkerController) {
@@ -73,7 +69,6 @@
     /**
      * @see ServiceWorkerController#getServiceWorkerWebSettings()
      */
-    @DoNotInline
     @NonNull
     public static ServiceWorkerWebSettingsImpl getServiceWorkerWebSettingsImpl(
             @NonNull ServiceWorkerController serviceWorkerController) {
@@ -84,7 +79,6 @@
     /**
      * @see ServiceWorkerController#setServiceWorkerClient(ServiceWorkerClient)
      */
-    @DoNotInline
     public static void setServiceWorkerClient(
             @NonNull ServiceWorkerController serviceWorkerController,
             @Nullable ServiceWorkerClient serviceWorkerClient) {
@@ -94,7 +88,6 @@
     /**
      * @see ServiceWorkerController#setServiceWorkerClient(ServiceWorkerClient)
      */
-    @DoNotInline
     public static void setServiceWorkerClientCompat(
             @NonNull ServiceWorkerController serviceWorkerController,
             @NonNull ServiceWorkerClientCompat serviceWorkerClientCompat) {
@@ -105,7 +98,6 @@
     /**
      * @see ServiceWorkerWebSettings#setCacheMode(int)
      */
-    @DoNotInline
     public static void setCacheMode(@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings,
             int cacheMode) {
         serviceWorkerWebSettings.setCacheMode(cacheMode);
@@ -114,7 +106,6 @@
     /**
      * @see ServiceWorkerWebSettings#getCacheMode()
      */
-    @DoNotInline
     public static int getCacheMode(@NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
         return serviceWorkerWebSettings.getCacheMode();
     }
@@ -122,7 +113,6 @@
     /**
      * @see ServiceWorkerWebSettings#setAllowContentAccess(boolean)
      */
-    @DoNotInline
     public static void setAllowContentAccess(
             @NonNull ServiceWorkerWebSettings serviceWorkerWebSettings,
             boolean allowContentAccess) {
@@ -132,7 +122,6 @@
     /**
      * @see ServiceWorkerWebSettings#getAllowContentAccess()
      */
-    @DoNotInline
     public static boolean getAllowContentAccess(
             @NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
         return serviceWorkerWebSettings.getAllowContentAccess();
@@ -141,7 +130,6 @@
     /**
      * @see ServiceWorkerWebSettings#setAllowFileAccess(boolean)
      */
-    @DoNotInline
     public static void setAllowFileAccess(
             @NonNull ServiceWorkerWebSettings serviceWorkerWebSettings, boolean allowFileAccess) {
         serviceWorkerWebSettings.setAllowFileAccess(allowFileAccess);
@@ -150,7 +138,6 @@
     /**
      * @see ServiceWorkerWebSettings#getAllowFileAccess()
      */
-    @DoNotInline
     public static boolean getAllowFileAccess(
             @NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
         return serviceWorkerWebSettings.getAllowFileAccess();
@@ -159,7 +146,6 @@
     /**
      * @see ServiceWorkerWebSettings#setBlockNetworkLoads(boolean)
      */
-    @DoNotInline
     public static void setBlockNetworkLoads(
             @NonNull ServiceWorkerWebSettings serviceWorkerWebSettings, boolean blockNetworkLoads) {
         serviceWorkerWebSettings.setBlockNetworkLoads(blockNetworkLoads);
@@ -168,7 +154,6 @@
     /**
      * @see ServiceWorkerWebSettings#getBlockNetworkLoads()
      */
-    @DoNotInline
     public static boolean getBlockNetworkLoads(
             @NonNull ServiceWorkerWebSettings serviceWorkerWebSettings) {
         return serviceWorkerWebSettings.getBlockNetworkLoads();
@@ -177,7 +162,6 @@
     /**
      * @see WebResourceRequest#isRedirect()
      */
-    @DoNotInline
     public static boolean isRedirect(@NonNull WebResourceRequest webResourceRequest) {
         return webResourceRequest.isRedirect();
     }
@@ -185,7 +169,6 @@
     /**
      * @see WebSettings#setDisabledActionModeMenuItems(int)
      */
-    @DoNotInline
     public static void setDisabledActionModeMenuItems(@NonNull WebSettings webSettings, int i) {
         webSettings.setDisabledActionModeMenuItems(i);
     }
@@ -193,7 +176,6 @@
     /**
      * @see WebSettings#getDisabledActionModeMenuItems()
      */
-    @DoNotInline
     public static int getDisabledActionModeMenuItems(@NonNull WebSettings webSettings) {
         return webSettings.getDisabledActionModeMenuItems();
     }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java
index b4efe99..5c0ce09 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForO.java
@@ -23,7 +23,6 @@
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -41,7 +40,6 @@
     /**
      * @see WebSettings#setSafeBrowsingEnabled(boolean)
      */
-    @DoNotInline
     public static void setSafeBrowsingEnabled(@NonNull WebSettings webSettings, boolean b) {
         webSettings.setSafeBrowsingEnabled(b);
     }
@@ -49,7 +47,6 @@
     /**
      * @see WebSettings#getSafeBrowsingEnabled()
      */
-    @DoNotInline
     public static boolean getSafeBrowsingEnabled(@NonNull WebSettings webSettings) {
         return webSettings.getSafeBrowsingEnabled();
     }
@@ -57,7 +54,6 @@
     /**
      * @see WebView#getWebViewClient()
      */
-    @DoNotInline
     @Nullable
     public static WebViewClient getWebViewClient(@NonNull WebView webView) {
         return webView.getWebViewClient();
@@ -66,7 +62,6 @@
     /**
      * @see WebView#getWebChromeClient()
      */
-    @DoNotInline
     @Nullable
     public static WebChromeClient getWebChromeClient(@NonNull WebView webView) {
         return webView.getWebChromeClient();
@@ -75,7 +70,6 @@
     /**
      * @see WebView#getCurrentWebViewPackage()
      */
-    @DoNotInline
     @NonNull
     public static PackageInfo getCurrentWebViewPackage() {
         return WebView.getCurrentWebViewPackage();
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java
index 0630f2f..85ab81a 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForOMR1.java
@@ -23,7 +23,6 @@
 import android.webkit.ValueCallback;
 import android.webkit.WebView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -44,7 +43,6 @@
     /**
      * @see SafeBrowsingResponse#showInterstitial(boolean)
      */
-    @DoNotInline
     public static void showInterstitial(@NonNull SafeBrowsingResponse safeBrowsingResponse,
             boolean showInterstitial) {
         safeBrowsingResponse.showInterstitial(showInterstitial);
@@ -53,7 +51,6 @@
     /**
      * @see SafeBrowsingResponse#proceed(boolean)
      */
-    @DoNotInline
     public static void proceed(@NonNull SafeBrowsingResponse frameworksImpl, boolean proceed) {
         frameworksImpl.proceed(proceed);
     }
@@ -61,7 +58,6 @@
     /**
      * @see SafeBrowsingResponse#backToSafety(boolean)
      */
-    @DoNotInline
     public static void backToSafety(@NonNull SafeBrowsingResponse safeBrowsingResponse,
             boolean backToSafety) {
         safeBrowsingResponse.backToSafety(backToSafety);
@@ -70,7 +66,6 @@
     /**
      * @see WebView#startSafeBrowsing(Context, ValueCallback)
      */
-    @DoNotInline
     public static void startSafeBrowsing(@NonNull Context context,
             @Nullable ValueCallback<Boolean> callback) {
         WebView.startSafeBrowsing(context, callback);
@@ -79,7 +74,6 @@
     /**
      * @see WebView#setSafeBrowsingWhitelist(List, ValueCallback)
      */
-    @DoNotInline
     public static void setSafeBrowsingWhitelist(@NonNull List<String> hosts,
             @Nullable ValueCallback<Boolean> callback) {
         WebView.setSafeBrowsingWhitelist(hosts, callback);
@@ -88,7 +82,6 @@
     /**
      * @see WebView#getSafeBrowsingPrivacyPolicyUrl()
      */
-    @DoNotInline
     @NonNull
     public static Uri getSafeBrowsingPrivacyPolicyUrl() {
         return WebView.getSafeBrowsingPrivacyPolicyUrl();
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java
index a495e22..b9d7643 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForP.java
@@ -21,7 +21,6 @@
 import android.webkit.TracingController;
 import android.webkit.WebView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -43,7 +42,6 @@
     /**
      * @see TracingController#getInstance()
      */
-    @DoNotInline
     @NonNull
     public static TracingController getTracingControllerInstance() {
         return TracingController.getInstance();
@@ -52,7 +50,6 @@
     /**
      * @see TracingController#isTracing()
      */
-    @DoNotInline
     public static boolean isTracing(@NonNull TracingController tracingController) {
         return tracingController.isTracing();
     }
@@ -62,7 +59,6 @@
      * isolate new types in this class.
      * @see TracingController#start(android.webkit.TracingConfig)
      */
-    @DoNotInline
     public static void start(@NonNull TracingController tracingController,
             @NonNull TracingConfig tracingConfig) {
         android.webkit.TracingConfig config =
@@ -77,7 +73,6 @@
     /**
      * @see TracingController#stop(OutputStream, Executor)
      */
-    @DoNotInline
     public static boolean stop(@NonNull TracingController tracingController,
             @Nullable OutputStream os, @NonNull Executor ex) {
         return tracingController.stop(os, ex);
@@ -86,7 +81,6 @@
     /**
      * @see WebView#getWebViewClassLoader()
      */
-    @DoNotInline
     @NonNull
     public static ClassLoader getWebViewClassLoader() {
         return WebView.getWebViewClassLoader();
@@ -95,7 +89,6 @@
     /**
      * @see WebView#getWebViewLooper()
      */
-    @DoNotInline
     @NonNull
     public static Looper getWebViewLooper(@NonNull WebView webView) {
         return webView.getWebViewLooper();
@@ -105,7 +98,6 @@
     /**
      * @see WebView#setDataDirectorySuffix(String)
      */
-    @DoNotInline
     public static void setDataDirectorySuffix(@NonNull String suffix) {
         WebView.setDataDirectorySuffix(suffix);
     }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java
index 4e93fca..5917b04 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForQ.java
@@ -21,7 +21,6 @@
 import android.webkit.WebView;
 import android.webkit.WebViewRenderProcess;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -43,7 +42,6 @@
      * @see WebSettings#setForceDark(int)
      * @deprecated in 31
      */
-    @DoNotInline
     @Deprecated
     public static void setForceDark(@NonNull WebSettings webSettings, int forceDark) {
         webSettings.setForceDark(forceDark);
@@ -53,7 +51,6 @@
      * @see WebSettings#getForceDark()
      * @deprecated in 31
      */
-    @DoNotInline
     @Deprecated
     public static int getForceDark(@NonNull WebSettings webSettings) {
         return webSettings.getForceDark();
@@ -62,7 +59,6 @@
     /**
      * @see WebView#getWebViewRenderProcess()
      */
-    @DoNotInline
     @Nullable
     public static WebViewRenderProcess getWebViewRenderProcess(@NonNull WebView webView) {
         return webView.getWebViewRenderProcess();
@@ -71,7 +67,6 @@
     /**
      * @see WebViewRenderProcess#terminate()
      */
-    @DoNotInline
     public static boolean terminate(@NonNull WebViewRenderProcess webViewRenderProcess) {
         return webViewRenderProcess.terminate();
     }
@@ -80,7 +75,6 @@
      * @see WebView#setWebViewRenderProcessClient(Executor,
      *          android.webkit.WebViewRenderProcessClient)
      */
-    @DoNotInline
     public static void setWebViewRenderProcessClient(@NonNull WebView webView,
             @NonNull Executor executor,
             @Nullable WebViewRenderProcessClient client) {
@@ -92,7 +86,6 @@
     /**
      * @see WebView#setWebViewRenderProcessClient(android.webkit.WebViewRenderProcessClient)
      */
-    @DoNotInline
     public static void setWebViewRenderProcessClient(@NonNull WebView webView,
             @Nullable WebViewRenderProcessClient client) {
         WebViewRenderProcessClientFrameworkAdapter clientAdapter =
@@ -103,7 +96,6 @@
     /**
      * @see WebView#getWebViewRenderProcessClient()
      */
-    @DoNotInline
     @Nullable
     public static android.webkit.WebViewRenderProcessClient getWebViewRenderProcessClient(
             @NonNull WebView webView) {
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java
index ff59197..1ad8075 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ApiHelperForTiramisu.java
@@ -21,7 +21,6 @@
 import android.content.pm.ServiceInfo;
 import android.os.Build;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.RequiresApi;
 
 /**
@@ -34,12 +33,10 @@
     private ApiHelperForTiramisu() {
     }
 
-    @DoNotInline
     static PackageManager.ComponentInfoFlags of(long value) {
         return PackageManager.ComponentInfoFlags.of(value);
     }
 
-    @DoNotInline
     static ServiceInfo getServiceInfo(PackageManager packageManager, ComponentName component,
             PackageManager.ComponentInfoFlags flags)
             throws PackageManager.NameNotFoundException {
diff --git a/window/extensions/extensions/build.gradle b/window/extensions/extensions/build.gradle
index ac53d85..4ad8662 100644
--- a/window/extensions/extensions/build.gradle
+++ b/window/extensions/extensions/build.gradle
@@ -31,7 +31,7 @@
 
 dependencies {
     implementation("androidx.annotation:annotation:1.6.0")
-    implementation("androidx.annotation:annotation-experimental:1.4.0")
+    implementation("androidx.annotation:annotation-experimental:1.4.1")
     compileOnly("androidx.window.extensions.core:core:1.0.0")
 
     testImplementation(libs.robolectric)
diff --git a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
index 8b57faf..11e0f41 100644
--- a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
@@ -23,7 +23,6 @@
 import android.os.Build
 import android.os.IBinder
 import android.util.Log
-import androidx.annotation.DoNotInline
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
@@ -385,7 +384,6 @@
 
     @RequiresApi(31)
     private object Api31Impl {
-        @DoNotInline
         fun isSplitPropertyEnabled(context: Context): SplitController.SplitSupportStatus {
             val property =
                 try {
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
index 13cfa6b..adc8732 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
@@ -20,7 +20,6 @@
 import android.graphics.Rect
 import android.os.Build
 import android.view.WindowMetrics
-import androidx.annotation.DoNotInline
 import androidx.annotation.IntRange
 import androidx.annotation.RequiresApi
 import androidx.core.util.Preconditions
@@ -277,7 +276,6 @@
 
     @RequiresApi(30)
     internal object Api30Impl {
-        @DoNotInline
         fun getBounds(windowMetrics: WindowMetrics): Rect {
             return windowMetrics.bounds
         }
@@ -285,7 +283,6 @@
 
     @RequiresApi(34)
     internal object Api34Impl {
-        @DoNotInline
         fun getDensity(windowMetrics: WindowMetrics, context: Context): Float {
             // TODO(b/265089843) remove the try catch after U is finalized.
             return try {
diff --git a/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
index 3f3217b..36fc60b 100644
--- a/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
+++ b/window/window/src/main/java/androidx/window/layout/util/ContextCompatHelper.kt
@@ -23,7 +23,6 @@
 import android.inputmethodservice.InputMethodService
 import android.os.Build
 import android.view.WindowManager
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.UiContext
 import androidx.core.view.WindowInsetsCompat
@@ -90,10 +89,7 @@
      * Computes the [WindowInsetsCompat] for platforms above [Build.VERSION_CODES.R], inclusive.
      *
      * @see androidx.window.layout.WindowMetrics.getWindowInsets
-     * @DoNotInline required for implementation-specific class method to prevent it from being
-     *   inlined.
      */
-    @DoNotInline
     fun currentWindowInsets(@UiContext context: Context): WindowInsetsCompat {
         val platformInsets =
             context.getSystemService(WindowManager::class.java).currentWindowMetrics.windowInsets
diff --git a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java
index 410e540..4180a34 100644
--- a/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java
+++ b/work/work-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableRuntimeExtras.java
@@ -26,7 +26,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
@@ -156,7 +155,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static Parcelable castToParcelable(Network network) {
             return network;
         }
diff --git a/work/work-runtime/build.gradle b/work/work-runtime/build.gradle
index 1545264..004fffe 100644
--- a/work/work-runtime/build.gradle
+++ b/work/work-runtime/build.gradle
@@ -66,7 +66,7 @@
     implementation("androidx.core:core:1.12.0")
     implementation("androidx.room:room-ktx:2.6.1")
     implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
-    api("androidx.annotation:annotation-experimental:1.4.0")
+    api("androidx.annotation:annotation-experimental:1.4.1")
     api(libs.guavaListenableFuture)
     api("androidx.lifecycle:lifecycle-livedata:2.6.2")
     api("androidx.startup:startup-runtime:1.1.1")
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt
index c6965c9..319eefd 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabasePathHelper.kt
@@ -17,7 +17,6 @@
 
 import android.content.Context
 import android.os.Build
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.work.Logger
@@ -116,7 +115,6 @@
 
 @RequiresApi(21)
 internal object Api21Impl {
-    @DoNotInline
     fun getNoBackupFilesDir(context: Context): File {
         return context.noBackupFilesDir
     }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 14201d2..2e8c1b7 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -37,7 +37,6 @@
 import android.content.Intent;
 import android.os.Build;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -716,7 +715,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static boolean isDeviceProtectedStorage(Context context) {
             return context.isDeviceProtectedStorage();
         }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
index dc44577..5e5159b 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
@@ -45,7 +45,6 @@
 import android.os.Build;
 import android.os.PersistableBundle;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -245,12 +244,10 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static Uri[] getTriggeredContentUris(JobParameters jobParameters) {
             return jobParameters.getTriggeredContentUris();
         }
 
-        @DoNotInline
         static String[] getTriggeredContentAuthorities(JobParameters jobParameters) {
             return jobParameters.getTriggeredContentAuthorities();
         }
@@ -262,7 +259,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static Network getNetwork(JobParameters jobParameters) {
             return jobParameters.getNetwork();
         }
@@ -274,7 +270,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static int getStopReason(JobParameters jobParameters) {
             return stopReason(jobParameters.getStopReason());
         }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
index 62efc86..9b1ac79 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundService.java
@@ -25,7 +25,6 @@
 import android.content.Intent;
 import android.os.Build;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -155,7 +154,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void startForeground(Service service, int id, Notification notification,
                 int foregroundServiceType) {
             service.startForeground(id, notification, foregroundServiceType);
@@ -168,7 +166,6 @@
             // This class is not instantiable.
         }
 
-        @DoNotInline
         static void startForeground(Service service, int id, Notification notification,
                 int foregroundServiceType) {
             try {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt
index 0ff8bbc..9f53082 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/DurationApi26.kt
@@ -18,11 +18,9 @@
 
 package androidx.work.impl.utils
 
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import java.time.Duration
 
-@DoNotInline
 internal fun Duration.toMillisCompat(): Long {
     return this.toMillis()
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt
index 7c8dbaf..e8afb1b 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi21.kt
@@ -22,16 +22,12 @@
 import android.net.ConnectivityManager.NetworkCallback
 import android.net.Network
 import android.net.NetworkCapabilities
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
-@DoNotInline
 internal fun ConnectivityManager.unregisterNetworkCallbackCompat(networkCallback: NetworkCallback) =
     unregisterNetworkCallback(networkCallback)
 
-@DoNotInline
 internal fun ConnectivityManager.getNetworkCapabilitiesCompat(network: Network?) =
     getNetworkCapabilities(network)
 
-@DoNotInline
 internal fun NetworkCapabilities.hasCapabilityCompat(capability: Int) = hasCapability(capability)
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt
index bf57873..6b0bee95 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi23.kt
@@ -19,7 +19,6 @@
 package androidx.work.impl.utils
 
 import android.net.ConnectivityManager
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
-@DoNotInline fun ConnectivityManager.getActiveNetworkCompat() = activeNetwork
+fun ConnectivityManager.getActiveNetworkCompat() = activeNetwork
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt
index 8e27226..3a23709 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkApi24.kt
@@ -19,10 +19,8 @@
 package androidx.work.impl.utils
 
 import android.net.ConnectivityManager
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
-@DoNotInline
 fun ConnectivityManager.registerDefaultNetworkCallbackCompat(
     networkCallback: ConnectivityManager.NetworkCallback
 ) = registerDefaultNetworkCallback(networkCallback)
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt
index e3af8a5..d928d8b 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/NetworkRequestCompat.kt
@@ -19,7 +19,6 @@
 import android.net.NetworkCapabilities
 import android.net.NetworkRequest
 import android.os.Build
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
 internal data class NetworkRequestCompat(val wrapped: Any? = null) {
@@ -92,16 +91,13 @@
 
 @RequiresApi(28)
 object NetworkRequest28 {
-    @DoNotInline
     internal fun hasCapability(request: NetworkRequest, capability: Int) =
         request.hasCapability(capability)
 
-    @DoNotInline
     internal fun hasTransport(request: NetworkRequest, transport: Int) =
         request.hasTransport(transport)
 
     @JvmStatic
-    @DoNotInline
     fun createNetworkRequest(capabilities: IntArray, transports: IntArray): NetworkRequest {
         val networkRequest = NetworkRequest.Builder()
         capabilities.forEach { networkRequest.addCapability(it) }
@@ -109,7 +105,6 @@
         return networkRequest.build()
     }
 
-    @DoNotInline
     internal fun createNetworkRequestCompat(
         capabilities: IntArray,
         transports: IntArray
@@ -120,12 +115,12 @@
 
 @RequiresApi(31)
 private object NetworkRequest31 {
-    @DoNotInline fun capabilities(request: NetworkRequest) = request.capabilities
+    fun capabilities(request: NetworkRequest) = request.capabilities
 
-    @DoNotInline fun transportTypes(request: NetworkRequest) = request.transportTypes
+    fun transportTypes(request: NetworkRequest) = request.transportTypes
 }
 
 @RequiresApi(30)
 internal object NetworkRequest30 {
-    @DoNotInline fun getNetworkSpecifier(request: NetworkRequest) = request.networkSpecifier
+    fun getNetworkSpecifier(request: NetworkRequest) = request.networkSpecifier
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt b/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
index 952cf90..d987b19 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/ProcessUtils.kt
@@ -23,7 +23,6 @@
 import android.content.Context
 import android.os.Build
 import android.os.Process
-import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.work.Configuration
 import androidx.work.Logger
@@ -68,7 +67,6 @@
 
 @RequiresApi(28)
 private object Api28Impl {
-    @get:DoNotInline
     val processName: String
         get() = Application.getProcessName()
 }