Merge "Bumping compose-material3 to 1.2.0-beta01." into androidx-main
diff --git a/appcompat/appcompat-resources/api/restricted_current.txt b/appcompat/appcompat-resources/api/restricted_current.txt
index 6a66d3a..1278c5d 100644
--- a/appcompat/appcompat-resources/api/restricted_current.txt
+++ b/appcompat/appcompat-resources/api/restricted_current.txt
@@ -58,7 +58,7 @@
 package androidx.appcompat.widget {
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DrawableUtils {
-    method public static boolean canSafelyMutateDrawable(android.graphics.drawable.Drawable);
+    method @Deprecated public static boolean canSafelyMutateDrawable(android.graphics.drawable.Drawable);
     method public static android.graphics.Rect getOpticalBounds(android.graphics.drawable.Drawable);
     method public static android.graphics.PorterDuff.Mode! parseTintMode(int, android.graphics.PorterDuff.Mode!);
     field public static final android.graphics.Rect! INSETS_NONE;
diff --git a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/DrawableUtils.java b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/DrawableUtils.java
index 9349881..04e9541 100644
--- a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/DrawableUtils.java
+++ b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/DrawableUtils.java
@@ -24,20 +24,13 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableContainer;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ScaleDrawable;
 import android.os.Build;
 
 import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
-import androidx.appcompat.graphics.drawable.DrawableWrapperCompat;
 import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.core.graphics.drawable.WrappedDrawable;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -69,13 +62,9 @@
                     insets.right,
                     insets.bottom
             );
-        } else if (Build.VERSION.SDK_INT >= 18) {
+        } else {
             return Api18Impl.getOpticalInsets(DrawableCompat.unwrap(drawable));
         }
-
-        // If we reach here, either we're running on a device pre-v18, the Drawable didn't have
-        // any optical insets, or a reflection issue, so we'll just return an empty rect.
-        return INSETS_NONE;
     }
 
     /**
@@ -101,43 +90,11 @@
     /**
      * Some drawable implementations have problems with mutation. This method returns false if
      * there is a known issue in the given drawable's implementation.
+     *
+     * @deprecated it is always true.
      */
+    @Deprecated
     public static boolean canSafelyMutateDrawable(@NonNull Drawable drawable) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            // We'll never return false on API level >= 17, stop early.
-            return true;
-        }
-
-        if (Build.VERSION.SDK_INT < 15 && drawable instanceof InsetDrawable) {
-            return false;
-        } else if (Build.VERSION.SDK_INT < 15 && drawable instanceof GradientDrawable) {
-            // GradientDrawable has a bug pre-ICS which results in mutate() resulting
-            // in loss of color
-            return false;
-        } else if (Build.VERSION.SDK_INT < 17 && drawable instanceof LayerDrawable) {
-            return false;
-        }
-
-        if (drawable instanceof DrawableContainer) {
-            // If we have a DrawableContainer, let's traverse its child array
-            final Drawable.ConstantState state = drawable.getConstantState();
-            if (state instanceof DrawableContainer.DrawableContainerState) {
-                final DrawableContainer.DrawableContainerState containerState =
-                        (DrawableContainer.DrawableContainerState) state;
-                for (final Drawable child : containerState.getChildren()) {
-                    if (!canSafelyMutateDrawable(child)) {
-                        return false;
-                    }
-                }
-            }
-        } else if (drawable instanceof WrappedDrawable) {
-            return canSafelyMutateDrawable(((WrappedDrawable) drawable).getWrappedDrawable());
-        } else if (drawable instanceof DrawableWrapperCompat) {
-            return canSafelyMutateDrawable(((DrawableWrapperCompat) drawable).getDrawable());
-        } else if (drawable instanceof ScaleDrawable) {
-            return canSafelyMutateDrawable(((ScaleDrawable) drawable).getDrawable());
-        }
-
         return true;
     }
 
@@ -179,8 +136,7 @@
         }
     }
 
-    // Only accessible on SDK_INT >= 18 and < 29.
-    @RequiresApi(18)
+    // Only accessible on SDK_INT < 29.
     static class Api18Impl {
         private static final boolean sReflectionSuccessful;
         private static final Method sGetOpticalInsets;
diff --git a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/ResourceManagerInternal.java b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/ResourceManagerInternal.java
index 916e4ac..d64a144e 100644
--- a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/ResourceManagerInternal.java
+++ b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/ResourceManagerInternal.java
@@ -203,9 +203,7 @@
         final ColorStateList tintList = getTintList(context, resId);
         if (tintList != null) {
             // First mutate the Drawable, then wrap it and set the tint list
-            if (DrawableUtils.canSafelyMutateDrawable(drawable)) {
-                drawable = drawable.mutate();
-            }
+            drawable = drawable.mutate();
             drawable = DrawableCompat.wrap(drawable);
             DrawableCompat.setTintList(drawable, tintList);
 
@@ -438,13 +436,10 @@
     static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {
         int[] drawableState = drawable.getState();
 
-        boolean mutated = false;
-        if (DrawableUtils.canSafelyMutateDrawable(drawable)) {
-            mutated = drawable.mutate() == drawable;
-            if (!mutated) {
-                Log.d(TAG, "Mutated drawable is not the same instance as the input.");
-                return;
-            }
+        boolean mutated = drawable.mutate() == drawable;
+        if (!mutated) {
+            Log.d(TAG, "Mutated drawable is not the same instance as the input.");
+            return;
         }
 
         // Workaround for b/232275112 where LayerDrawable loses its state on mutate().
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateActivity.java
index 35ee8d3..a25e6a5 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateActivity.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesLateOnCreateActivity.java
@@ -68,10 +68,8 @@
         Configuration conf = context.getResources().getConfiguration();
         if (Build.VERSION.SDK_INT >= 24) {
             conf.setLocales(LocaleList.forLanguageTags(locales.toLanguageTags()));
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            conf.setLocale(locales.get(0));
         } else {
-            conf.locale = locales.get(0);
+            conf.setLocale(locales.get(0));
         }
         // updateConfiguration is required to make the configuration change stick.
         // updateConfiguration must be called before any use of the actual Resources.
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt
index bab064a..80561e1 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt
@@ -18,7 +18,6 @@
 
 import android.content.res.Configuration
 import android.content.res.Resources
-import android.os.Build
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
 import androidx.appcompat.app.NightModeCustomAttachBaseContextActivity.CUSTOM_FONT_SCALE
 import androidx.appcompat.app.NightModeCustomAttachBaseContextActivity.CUSTOM_LOCALE
@@ -115,10 +114,6 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters
-        fun data() = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        fun data() = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationActivity.java
index 5c9e24e..9e0e311 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationActivity.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationActivity.java
@@ -59,11 +59,7 @@
         if (locale != null) {
             // Configuration.setLocale is added after 17 and Configuration.locale is deprecated
             // after 24
-            if (Build.VERSION.SDK_INT >= 17) {
-                config.setLocale(locale);
-            } else {
-                config.locale = locale;
-            }
+            config.setLocale(locale);
         }
         return config;
     }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt
index fea48d4..e502df0 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt
@@ -16,7 +16,6 @@
 
 package androidx.appcompat.app
 
-import android.os.Build
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
 import androidx.appcompat.app.NightModeCustomApplyOverrideConfigurationActivity.CUSTOM_FONT_SCALE
 import androidx.appcompat.app.NightModeCustomApplyOverrideConfigurationActivity.CUSTOM_LOCALE
@@ -83,10 +82,6 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters
-        fun data() = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        fun data() = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomAttachBaseContextTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomAttachBaseContextTestCase.kt
index da4f6d1..62d0906 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomAttachBaseContextTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomAttachBaseContextTestCase.kt
@@ -16,7 +16,6 @@
 
 package androidx.appcompat.app
 
-import android.os.Build
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
 import androidx.appcompat.app.NightModeCustomAttachBaseContextActivity.CUSTOM_FONT_SCALE
 import androidx.appcompat.app.NightModeCustomAttachBaseContextActivity.CUSTOM_LOCALE
@@ -78,10 +77,6 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters
-        fun data() = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        fun data() = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt
index 29d06b1..075ff08 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModePreventOverrideConfigTestCase.kt
@@ -17,7 +17,6 @@
 package androidx.appcompat.app
 
 import android.content.res.Configuration
-import android.os.Build
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
 import androidx.appcompat.testutils.NightModeActivityTestRule
 import androidx.appcompat.testutils.NightModeUtils.NightSetMode
@@ -67,10 +66,6 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters
-        fun data() = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        fun data() = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt
index 523d88d..c698bba 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateDoesNotRecreateActivityTestCase.kt
@@ -17,7 +17,6 @@
 package androidx.appcompat.app
 
 import android.content.res.Configuration
-import android.os.Build
 import androidx.appcompat.Orientation
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
@@ -109,10 +108,6 @@
     public companion object {
         @JvmStatic
         @Parameterized.Parameters
-        public fun data(): List<NightSetMode> = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        public fun data(): List<NightSetMode> = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt
index f5f1e53..962dfd5 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRotateRecreatesActivityWithConfigTestCase.kt
@@ -19,7 +19,6 @@
 import android.app.Activity
 import android.app.Instrumentation
 import android.content.res.Configuration
-import android.os.Build
 import androidx.appcompat.Orientation
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
@@ -132,10 +131,6 @@
     public companion object {
         @JvmStatic
         @Parameterized.Parameters
-        public fun data(): List<NightSetMode> = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        public fun data(): List<NightSetMode> = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
index 1e3451f..51fc6d3a 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.location.LocationManager
-import android.os.Build
 import android.webkit.WebView
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
@@ -282,10 +281,6 @@
 
         @Parameterized.Parameters
         @JvmStatic
-        fun data() = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        fun data() = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.kt
index fe35b47..f0ebe47 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.kt
@@ -17,7 +17,6 @@
 package androidx.appcompat.app
 
 import android.content.res.Configuration
-import android.os.Build
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
 import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
 import androidx.appcompat.testutils.NightModeUtils.NightSetMode
@@ -161,10 +160,6 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters
-        fun data() = if (Build.VERSION.SDK_INT >= 17) {
-            listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
-        } else {
-            listOf(NightSetMode.DEFAULT)
-        }
+        fun data() = listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
     }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
index 1fb9286..a666cda 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
@@ -20,7 +20,6 @@
 import android.content.Context
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
-import android.os.Build
 import android.util.Log
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.app.AppCompatDelegate
@@ -189,12 +188,7 @@
         setMode: NightSetMode
     ) = when (setMode) {
         NightSetMode.DEFAULT -> AppCompatDelegate.setDefaultNightMode(nightMode)
-        NightSetMode.LOCAL ->
-            if (Build.VERSION.SDK_INT >= 17) {
-                activity!!.delegate.localNightMode = nightMode
-            } else {
-                throw Exception("Local night mode is not supported on SDK_INT < 17")
-            }
+        NightSetMode.LOCAL -> activity!!.delegate.localNightMode = nightMode
     }
 
     @NightMode
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java
index 525e43a..8502458 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java
@@ -431,10 +431,8 @@
             mActivityTestRule.runOnUiThread(new Runnable() {
                 @Override
                 public void run() {
-                    if (Build.VERSION.SDK_INT >= 17) {
-                        autoSizeView.setCompoundDrawablesRelative(
-                                drawable, drawable, drawable, drawable);
-                    }
+                    autoSizeView.setCompoundDrawablesRelative(
+                            drawable, drawable, drawable, drawable);
                 }
             });
             mInstrumentation.waitForIdleSync();
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index 4976f99..28d90ef 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -475,7 +475,7 @@
             // Workaround for incorrect default fontScale on earlier SDKs.
             overrideConfig.fontScale = 0f;
             Configuration referenceConfig =
-                    Api17Impl.createConfigurationContext(baseContext, overrideConfig)
+                    baseContext.createConfigurationContext(overrideConfig)
                             .getResources().getConfiguration();
             // Revert the uiMode change so that the diff doesn't include uiMode.
             Configuration baseConfig = baseContext.getResources().getConfiguration();
@@ -2687,11 +2687,9 @@
     void setConfigurationLocales(Configuration conf, @NonNull LocaleListCompat locales) {
         if (Build.VERSION.SDK_INT >= 24) {
             Api24Impl.setLocales(conf, locales);
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            Api17Impl.setLocale(conf, locales.get(0));
-            Api17Impl.setLayoutDirection(conf, locales.get(0));
         } else {
-            conf.locale = locales.get(0);
+            conf.setLocale(locales.get(0));
+            conf.setLayoutDirection(locales.get(0));
         }
     }
 
@@ -2795,9 +2793,7 @@
         }
         if (newLocales != null && !currentLocales.equals(newLocales)) {
             configChanges |= ActivityInfo.CONFIG_LOCALE;
-            if (Build.VERSION.SDK_INT >= 17) {
-                configChanges |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
-            }
+            configChanges |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
         }
 
         if (DEBUG) {
@@ -2836,9 +2832,8 @@
             // layout direction after recreating in Android S.
             if (Build.VERSION.SDK_INT >= 31
                     && (configChanges & ActivityInfo.CONFIG_LAYOUT_DIRECTION) != 0) {
-                Api17Impl.setLayoutDirection(
-                        ((Activity) mHost).getWindow().getDecorView(),
-                        Api17Impl.getLayoutDirection(overrideConfig));
+                View view = ((Activity) mHost).getWindow().getDecorView();
+                view.setLayoutDirection(overrideConfig.getLayoutDirection());
             }
             ActivityCompat.recreate((Activity) mHost);
             handled = true;
@@ -3908,8 +3903,8 @@
             delta.smallestScreenWidthDp = change.smallestScreenWidthDp;
         }
 
-        if (Build.VERSION.SDK_INT >= 17) {
-            Api17Impl.generateConfigDelta_densityDpi(base, change, delta);
+        if (base.densityDpi != change.densityDpi) {
+            delta.densityDpi = change.densityDpi;
         }
 
         // Assets sequence and window configuration are not supported.
@@ -3917,44 +3912,6 @@
         return delta;
     }
 
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() { }
-
-        static void generateConfigDelta_densityDpi(@NonNull Configuration base,
-                @NonNull Configuration change, @NonNull Configuration delta) {
-            if (base.densityDpi != change.densityDpi) {
-                delta.densityDpi = change.densityDpi;
-            }
-        }
-
-        @DoNotInline
-        static Context createConfigurationContext(@NonNull Context context,
-                @NonNull Configuration overrideConfiguration) {
-            return context.createConfigurationContext(overrideConfiguration);
-        }
-
-        @DoNotInline
-        static void setLayoutDirection(Configuration configuration, Locale loc) {
-            configuration.setLayoutDirection(loc);
-        }
-
-        @DoNotInline
-        static void setLayoutDirection(View view, int layoutDirection) {
-            view.setLayoutDirection(layoutDirection);
-        }
-
-        @DoNotInline
-        static void setLocale(Configuration configuration, Locale loc) {
-            configuration.setLocale(loc);
-        }
-
-        @DoNotInline
-        static int getLayoutDirection(Configuration configuration) {
-            return configuration.getLayoutDirection();
-        }
-    }
-
     @RequiresApi(21)
     static class Api21Impl {
         private Api21Impl() { }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java
index a8936f8..317cd76 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java
@@ -419,7 +419,7 @@
 
     private void backportAccessibilityAttributes(@NonNull Context context, @NonNull View view,
             @NonNull AttributeSet attrs) {
-        if (Build.VERSION.SDK_INT < 19 || Build.VERSION.SDK_INT > 28) {
+        if (Build.VERSION.SDK_INT > 28) {
             return;
         }
 
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuItemImpl.java b/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuItemImpl.java
index 6d20471..dbdc39a 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuItemImpl.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuItemImpl.java
@@ -25,7 +25,6 @@
 import android.content.res.Resources;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.util.Log;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.KeyEvent;
@@ -471,17 +470,7 @@
 
     @Override
     public CharSequence getTitleCondensed() {
-        final CharSequence ctitle = mTitleCondensed != null ? mTitleCondensed : mTitle;
-
-        if (Build.VERSION.SDK_INT < 18 && ctitle != null && !(ctitle instanceof String)) {
-            // For devices pre-JB-MR2, where we have a non-String CharSequence, we need to
-            // convert this to a String so that EventLog.writeEvent() does not throw an exception
-            // in Activity.onMenuItemSelected()
-            return ctitle.toString();
-        } else {
-            // Else, we just return the condensed title
-            return ctitle;
-        }
+        return mTitleCondensed != null ? mTitleCondensed : mTitle;
     }
 
     @Override
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuPopupHelper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuPopupHelper.java
index 69c498c..aa98768 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuPopupHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/view/menu/MenuPopupHelper.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.Build;
 import android.view.Display;
 import android.view.Gravity;
 import android.view.View;
@@ -32,10 +31,8 @@
 import android.widget.PopupWindow.OnDismissListener;
 
 import androidx.annotation.AttrRes;
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.StyleRes;
 import androidx.appcompat.R;
@@ -233,11 +230,7 @@
         final Display display = windowManager.getDefaultDisplay();
         final Point displaySize = new Point();
 
-        if (Build.VERSION.SDK_INT >= 17) {
-            Api17Impl.getRealSize(display, displaySize);
-        } else {
-            display.getSize(displaySize);
-        }
+        display.getRealSize(displaySize);
 
         final int smallestWidth = Math.min(displaySize.x, displaySize.y);
         final int minSmallestWidthCascading = mContext.getResources().getDimensionPixelSize(
@@ -351,16 +344,4 @@
     public ListView getListView() {
         return getPopup().getListView();
     }
-
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static void getRealSize(Display display, Point outSize) {
-            display.getRealSize(outSize);
-        }
-    }
 }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java
index 82d92e2..c7d4f79 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCheckBox.java
@@ -117,14 +117,6 @@
         setButtonDrawable(AppCompatResources.getDrawable(getContext(), resId));
     }
 
-    @Override
-    public int getCompoundPaddingLeft() {
-        final int value = super.getCompoundPaddingLeft();
-        return mCompoundButtonHelper != null
-                ? mCompoundButtonHelper.getCompoundPaddingLeft(value)
-                : value;
-    }
-
     /**
      * This should be accessed from {@link androidx.core.widget.CompoundButtonCompat}
      */
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java
index 3513f72..bf9b410 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatCompoundButtonHelper.java
@@ -20,7 +20,6 @@
 import android.content.res.Resources;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.widget.CompoundButton;
 
@@ -144,15 +143,4 @@
         }
     }
 
-    int getCompoundPaddingLeft(int superValue) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            // Before JB-MR1 the button drawable wasn't taken into account for padding. We'll
-            // workaround that here
-            Drawable buttonDrawable = CompoundButtonCompat.getButtonDrawable(mView);
-            if (buttonDrawable != null) {
-                superValue += buttonDrawable.getIntrinsicWidth();
-            }
-        }
-        return superValue;
-    }
 }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatDrawableManager.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatDrawableManager.java
index 6bd8f11..479200e 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatDrawableManager.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatDrawableManager.java
@@ -309,9 +309,7 @@
                 }
 
                 private void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {
-                    if (DrawableUtils.canSafelyMutateDrawable(d)) {
-                        d = d.mutate();
-                    }
+                    d = d.mutate();
                     d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE
                             : mode));
                 }
@@ -423,9 +421,7 @@
                     }
 
                     if (colorAttrSet) {
-                        if (DrawableUtils.canSafelyMutateDrawable(drawable)) {
-                            drawable = drawable.mutate();
-                        }
+                        drawable = drawable.mutate();
 
                         final int color = getThemeAttrColor(context, colorAttr);
                         drawable.setColorFilter(getPorterDuffColorFilter(color, tintMode));
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java
index 693f95f..dee338b 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatRadioButton.java
@@ -115,14 +115,6 @@
         setButtonDrawable(AppCompatResources.getDrawable(getContext(), resId));
     }
 
-    @Override
-    public int getCompoundPaddingLeft() {
-        final int value = super.getCompoundPaddingLeft();
-        return mCompoundButtonHelper != null
-                ? mCompoundButtonHelper.getCompoundPaddingLeft(value)
-                : value;
-    }
-
     /**
      * This should be accessed from {@link androidx.core.widget.CompoundButtonCompat}
      */
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 4eb3371..8408ef6 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextHelper.java
@@ -548,12 +548,10 @@
             applyCompoundDrawableTint(compoundDrawables[2], mDrawableRightTint);
             applyCompoundDrawableTint(compoundDrawables[3], mDrawableBottomTint);
         }
-        if (Build.VERSION.SDK_INT >= 17) {
-            if (mDrawableStartTint != null || mDrawableEndTint != null) {
-                final Drawable[] compoundDrawables = Api17Impl.getCompoundDrawablesRelative(mView);
-                applyCompoundDrawableTint(compoundDrawables[0], mDrawableStartTint);
-                applyCompoundDrawableTint(compoundDrawables[2], mDrawableEndTint);
-            }
+        if (mDrawableStartTint != null || mDrawableEndTint != null) {
+            final Drawable[] compoundDrawables = Api17Impl.getCompoundDrawablesRelative(mView);
+            applyCompoundDrawableTint(compoundDrawables[0], mDrawableStartTint);
+            applyCompoundDrawableTint(compoundDrawables[2], mDrawableEndTint);
         }
     }
 
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
index 446b6d8..684696a 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
@@ -34,7 +34,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
-import android.view.View;
 import android.widget.TextView;
 
 import androidx.annotation.DoNotInline;
@@ -47,7 +46,6 @@
 import androidx.core.view.ViewCompat;
 import androidx.core.widget.TextViewCompat;
 
-import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -75,11 +73,6 @@
     @SuppressLint("BanConcurrentHashMap")
     private static java.util.concurrent.ConcurrentHashMap<String, Method>
             sTextViewMethodByNameCache = new java.util.concurrent.ConcurrentHashMap<>();
-    // Cache of TextView fields used via reflection; the key is the field name and the value is
-    // the field itself or null if it can not be found.
-    @SuppressLint("BanConcurrentHashMap")
-    private static java.util.concurrent.ConcurrentHashMap<String, Field> sTextViewFieldByNameCache =
-            new java.util.concurrent.ConcurrentHashMap<>();
     // Use this to specify that any of the auto-size configuration int values have not been set.
     static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
     // Ported from TextView#VERY_WIDE. Represents a maximum width in pixels the TextView takes when
@@ -647,14 +640,12 @@
         setRawTextSize(TypedValue.applyDimension(unit, size, res.getDisplayMetrics()));
     }
 
+    @SuppressLint("BanUncheckedReflection")
     private void setRawTextSize(float size) {
         if (size != mTextView.getPaint().getTextSize()) {
             mTextView.getPaint().setTextSize(size);
 
-            boolean isInLayout = false;
-            if (Build.VERSION.SDK_INT >= 18) {
-                isInLayout = Api18Impl.isInLayout(mTextView);
-            }
+            boolean isInLayout = mTextView.isInLayout();
 
             if (mTextView.getLayout() != null) {
                 // Do not auto-size right after setting the text size.
@@ -731,11 +722,18 @@
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             return Api23Impl.createStaticLayoutForMeasuring(
                     text, alignment, availableWidth, maxLines, mTextView, mTempTextPaint, mImpl);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            return Api16Impl.createStaticLayoutForMeasuring(
-                    text, alignment, availableWidth, mTextView, mTempTextPaint);
         } else {
-            return createStaticLayoutForMeasuringPre16(text, alignment, availableWidth);
+            final float lineSpacingMultiplier = mTextView.getLineSpacingMultiplier();
+            final float lineSpacingAdd = mTextView.getLineSpacingExtra();
+            final boolean includePad = mTextView.getIncludeFontPadding();
+
+            // The layout could not be constructed using the builder so fall back to the
+            // most broad constructor.
+            return new StaticLayout(text, mTempTextPaint, availableWidth,
+                    alignment,
+                    lineSpacingMultiplier,
+                    lineSpacingAdd,
+                    includePad);
         }
     }
 
@@ -749,7 +747,7 @@
             }
         }
 
-        final int maxLines = Build.VERSION.SDK_INT >= 16 ? Api16Impl.getMaxLines(mTextView) : -1;
+        final int maxLines = mTextView.getMaxLines();
         initTempTextPaint(suggestedSizeInPx);
 
         // Needs reflection call due to being private.
@@ -771,25 +769,7 @@
         return true;
     }
 
-
-    private StaticLayout createStaticLayoutForMeasuringPre16(CharSequence text,
-            Layout.Alignment alignment, int availableWidth) {
-        // The default values have been inlined with the StaticLayout defaults.
-
-        final float lineSpacingMultiplier = accessAndReturnWithDefault(mTextView,
-                "mSpacingMult", 1.0f);
-        final float lineSpacingAdd = accessAndReturnWithDefault(mTextView,
-                "mSpacingAdd", 0.0f);
-        final boolean includePad = accessAndReturnWithDefault(mTextView,
-                "mIncludePad", true);
-
-        return new StaticLayout(text, mTempTextPaint, availableWidth,
-                alignment,
-                lineSpacingMultiplier,
-                lineSpacingAdd,
-                includePad);
-    }
-
+    @SuppressLint("BanUncheckedReflection")
     @SuppressWarnings("unchecked")
     // This is marked package-protected so that it doesn't require a synthetic accessor
     // when being used from the Impl inner classes
@@ -814,22 +794,6 @@
         return result;
     }
 
-    @SuppressWarnings("unchecked")
-    private static <T> T accessAndReturnWithDefault(@NonNull Object object,
-            @NonNull final String fieldName, @NonNull final T defaultValue) {
-        try {
-            final Field field = getTextViewField(fieldName);
-            if (field == null) {
-                return defaultValue;
-            }
-
-            return (T) field.get(object);
-        }  catch (IllegalAccessException e) {
-            Log.w(TAG, "Failed to access TextView#" + fieldName + " member", e);
-            return defaultValue;
-        }
-    }
-
     @Nullable
     private static Method getTextViewMethod(@NonNull final String methodName) {
         try {
@@ -850,25 +814,6 @@
         }
     }
 
-    @Nullable
-    private static Field getTextViewField(@NonNull final String fieldName) {
-        try {
-            Field field = sTextViewFieldByNameCache.get(fieldName);
-            if (field == null) {
-                field = TextView.class.getDeclaredField(fieldName);
-                if (field != null) {
-                    field.setAccessible(true);
-                    sTextViewFieldByNameCache.put(fieldName, field);
-                }
-            }
-
-            return field;
-        } catch (NoSuchFieldException e) {
-            Log.w(TAG, "Failed to access TextView#" + fieldName + " member", e);
-            return null;
-        }
-    }
-
     /**
      * @return {@code true} if this widget supports auto-sizing text and has been configured to
      * auto-size.
@@ -928,50 +873,4 @@
             return layoutBuilder.build();
         }
     }
-
-    @RequiresApi(18)
-    private static final class Api18Impl {
-        private Api18Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static boolean isInLayout(@NonNull View view) {
-            return view.isInLayout();
-        }
-    }
-
-    @RequiresApi(16)
-    private static final class Api16Impl {
-        private Api16Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static int getMaxLines(@NonNull TextView textView) {
-            return textView.getMaxLines();
-        }
-
-        @DoNotInline
-        @NonNull
-        static StaticLayout createStaticLayoutForMeasuring(
-                @NonNull CharSequence text,
-                @NonNull Layout.Alignment alignment,
-                int availableWidth,
-                @NonNull TextView textView,
-                @NonNull TextPaint tempTextPaint
-        ) {
-            final float lineSpacingMultiplier = textView.getLineSpacingMultiplier();
-            final float lineSpacingAdd = textView.getLineSpacingExtra();
-            final boolean includePad = textView.getIncludeFontPadding();
-
-            // The layout could not be constructed using the builder so fall back to the
-            // most broad constructor.
-            return new StaticLayout(text, tempTextPaint, availableWidth,
-                    alignment,
-                    lineSpacingMultiplier,
-                    lineSpacingAdd,
-                    includePad);
-        }
-    }
 }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
index c764cb6..24ca26b 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
@@ -48,11 +48,9 @@
 import android.widget.Switch;
 import android.widget.TextView;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.FloatRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.appcompat.R;
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.appcompat.text.AllCapsTransformationMethod;
@@ -1139,9 +1137,7 @@
         final float targetPosition = newCheckedState ? 1 : 0;
         mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition);
         mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION);
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api18Impl.setAutoCancel(mPositionAnimator, true);
-        }
+        mPositionAnimator.setAutoCancel(true);
         mPositionAnimator.start();
     }
 
@@ -1692,16 +1688,4 @@
             }
         }
     }
-
-    @RequiresApi(18)
-    static class Api18Impl {
-        private Api18Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static void setAutoCancel(ObjectAnimator objectAnimator, boolean cancel) {
-            objectAnimator.setAutoCancel(cancel);
-        }
-    }
 }
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
index b48c66c..9198f6a 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
@@ -559,9 +559,7 @@
 
     @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            super.onRtlPropertiesChanged(layoutDirection);
-        }
+        super.onRtlPropertiesChanged(layoutDirection);
 
         ensureContentInsets();
         mContentInsets.setDirection(layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL);
diff --git a/arch/core/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java b/arch/core/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
index b164eb5..99433a0 100644
--- a/arch/core/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
+++ b/arch/core/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
@@ -82,7 +82,7 @@
     private static Handler createAsync(@NonNull Looper looper) {
         if (Build.VERSION.SDK_INT >= 28) {
             return Api28Impl.createAsync(looper);
-        } else if (Build.VERSION.SDK_INT >= 17) {
+        } else {
             try {
                 // This constructor was added as private in JB MR1:
                 // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-mr1-release/core/java/android/os/Handler.java
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index 2f2b284..40b06ab 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -20,6 +20,7 @@
 import androidx.baselineprofile.gradle.utils.TASK_NAME_SUFFIX
 import androidx.baselineprofile.gradle.utils.maybeRegister
 import java.io.File
+import kotlin.io.path.Path
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
 import org.gradle.api.Project
@@ -262,7 +263,7 @@
                         logger.warn(
                             """
                             A baseline profile was generated for the variant `${variantName.get()}`:
-                            file:///$absolutePath
+                            ${Path(absolutePath).toUri()}
                         """.trimIndent()
                         )
                     }
@@ -312,7 +313,7 @@
                         logger.warn(
                             """
                             A startup profile was generated for the variant `${variantName.get()}`:
-                            file:///$absolutePath
+                            ${Path(absolutePath).toUri()}
                         """.trimIndent()
                         )
                     }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index aa4cf94..0fec0ff 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -35,6 +35,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import java.io.File
+import kotlin.io.path.Path
 import org.junit.Assume.assumeTrue
 import org.junit.Rule
 import org.junit.Test
@@ -68,6 +69,8 @@
         "src/$variantName/$EXPECTED_PROFILE_FOLDER/startup-prof.txt"
     )
 
+    private fun File.toUri() = Path(canonicalPath).toUri()
+
     private fun mergedArtProfile(variantName: String): File {
         // Task name folder in path was first observed in the update to AGP 8.3.0-alpha10.
         // Before that, the folder was omitted in path.
@@ -111,7 +114,7 @@
         gradleRunner.build("generateBaselineProfile") {
             val notFound = it.lines().requireInOrder(
                 "A baseline profile was generated for the variant `release`:",
-                "file:///${baselineProfileFile("main").canonicalPath}"
+                "${baselineProfileFile("main").toUri()}"
             )
             assertThat(notFound).isEmpty()
         }
@@ -154,9 +157,9 @@
         gradleRunner.build("generateBaselineProfile") {
             val notFound = it.lines().requireInOrder(
                 "A baseline profile was generated for the variant `release`:",
-                "file:///${baselineProfileFile("release").canonicalPath}",
+                "${baselineProfileFile("release").toUri()}",
                 "A startup profile was generated for the variant `release`:",
-                "file:///${startupProfileFile("release").canonicalPath}"
+                "${startupProfileFile("release").toUri()}"
             )
             assertThat(notFound).isEmpty()
         }
@@ -237,9 +240,9 @@
 
                 val notFound = it.lines().requireInOrder(
                     "A baseline profile was generated for the variant `$variantName`:",
-                    "file:///${baselineProfileFile(variantName).canonicalPath}",
+                    "${baselineProfileFile(variantName).toUri()}",
                     "A startup profile was generated for the variant `$variantName`:",
-                    "file:///${startupProfileFile(variantName).canonicalPath}"
+                    "${startupProfileFile(variantName).toUri()}"
                 )
 
                 assertWithMessage(
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
index 441f9ff..e888c7b 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
@@ -48,7 +48,7 @@
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameters
 
-private const val tracingPerfettoVersion = "1.0.0-beta03" // TODO(224510255): get by 'reflection'
+private const val tracingPerfettoVersion = "1.0.0" // TODO(224510255): get by 'reflection'
 private const val minSupportedSdk = Build.VERSION_CODES.R // TODO(234351579): Support API < 30
 
 @RunWith(Parameterized::class)
diff --git a/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt b/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt
index 7b55cde..8e04606 100644
--- a/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt
+++ b/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt
@@ -43,6 +43,7 @@
     fun standardBaselineProfile() = baselineRule.collect(
         packageName = PACKAGE_NAME,
         includeInStartupProfile = false,
+        maxIterations = 1,
         profileBlock = {
             startActivityAndWait(Intent(ACTION))
             device.waitForIdle()
@@ -53,6 +54,7 @@
     fun startupBaselineProfile() = baselineRule.collect(
         packageName = PACKAGE_NAME,
         includeInStartupProfile = true,
+        maxIterations = 1,
         profileBlock = {
             startActivityAndWait(Intent(ACTION))
             device.waitForIdle()
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/ScanResultTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/ScanResultTest.kt
index 9236324..df5d8b1 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/ScanResultTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/ScanResultTest.kt
@@ -17,11 +17,13 @@
 package androidx.bluetooth
 
 import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice as FwkBluetoothDevice
 import android.bluetooth.BluetoothManager
 import android.bluetooth.le.ScanResult as FwkScanResult
 import android.content.Context
 import android.os.Build
 import android.os.ParcelUuid
+import androidx.bluetooth.utils.addressType
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SdkSuppress
 import androidx.test.rule.GrantPermissionRule
@@ -39,6 +41,7 @@
  */
 @RunWith(JUnit4::class)
 class ScanResultTest {
+
     @Rule
     @JvmField
     val permissionRule: GrantPermissionRule = if (Build.VERSION.SDK_INT >= 31) {
@@ -53,6 +56,7 @@
     private val bluetoothManager: BluetoothManager? =
         context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
     private val bluetoothAdapter: BluetoothAdapter? = bluetoothManager?.adapter
+    private val bluetoothLe = BluetoothLe(context)
 
     @Before
     fun setUp() {
@@ -90,8 +94,13 @@
         assertThat(BluetoothDevice(fwkBluetoothDevice).bondState)
             .isEqualTo(scanResult.device.bondState)
         assertThat(address).isEqualTo(scanResult.deviceAddress.address)
-        assertThat(BluetoothAddress.ADDRESS_TYPE_UNKNOWN)
-            .isEqualTo(scanResult.deviceAddress.addressType)
+        val expectedAddressType = if (Build.VERSION.SDK_INT >= 34) {
+            BluetoothAddress.ADDRESS_TYPE_PUBLIC
+        } else {
+            BluetoothAddress.ADDRESS_TYPE_UNKNOWN
+        }
+        assertThat(scanResult.deviceAddress.addressType)
+            .isEqualTo(expectedAddressType)
         assertThat(true).isEqualTo(scanResult.isConnectable())
         assertThat(timeStampNanos).isEqualTo(scanResult.timestampNanos)
         assertThat(scanResult.getManufacturerSpecificData(1)).isNull()
@@ -129,4 +138,100 @@
         assertThat(scanResult.device).isEqualTo(scanResult.device)
         assertThat(scanResult.deviceAddress).isEqualTo(scanResult.deviceAddress)
     }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkScanResultAddressTypeRandomStatic() {
+        val address = "F0:43:A8:23:10:11"
+        val fwkBluetoothDevice = bluetoothAdapter!!
+            .getRemoteLeDevice(address, FwkBluetoothDevice.ADDRESS_TYPE_RANDOM)
+        val rssi = 34
+        val periodicAdvertisingInterval = 8
+        val timeStampNanos: Long = 1
+
+        val fwkScanResult = FwkScanResult(
+            fwkBluetoothDevice,
+            1,
+            0,
+            0,
+            0,
+            0,
+            rssi,
+            periodicAdvertisingInterval,
+            null,
+            timeStampNanos
+        )
+
+        val bluetoothAddress = BluetoothAddress(
+            fwkScanResult.device.address,
+            fwkScanResult.device.addressType()
+        )
+
+        assertThat(bluetoothAddress.addressType)
+            .isEqualTo(BluetoothAddress.ADDRESS_TYPE_RANDOM_STATIC)
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkScanResultAddressTypeRandomResolvable() {
+        val address = "40:01:02:03:04:05"
+        val fwkBluetoothDevice = bluetoothAdapter!!
+            .getRemoteLeDevice(address, FwkBluetoothDevice.ADDRESS_TYPE_RANDOM)
+        val rssi = 34
+        val periodicAdvertisingInterval = 8
+        val timeStampNanos: Long = 1
+
+        val fwkScanResult = FwkScanResult(
+            fwkBluetoothDevice,
+            1,
+            0,
+            0,
+            0,
+            0,
+            rssi,
+            periodicAdvertisingInterval,
+            null,
+            timeStampNanos
+        )
+
+        val bluetoothAddress = BluetoothAddress(
+            fwkScanResult.device.address,
+            fwkScanResult.device.addressType()
+        )
+
+        assertThat(bluetoothAddress.addressType)
+            .isEqualTo(BluetoothAddress.ADDRESS_TYPE_RANDOM_RESOLVABLE)
+    }
+
+    @SdkSuppress(minSdkVersion = 34)
+    @Test
+    fun frameworkScanResultAddressTypeRandomNonResolvable() {
+        val address = "00:01:02:03:04:05"
+        val fwkBluetoothDevice = bluetoothAdapter!!
+            .getRemoteLeDevice(address, FwkBluetoothDevice.ADDRESS_TYPE_RANDOM)
+        val rssi = 34
+        val periodicAdvertisingInterval = 8
+        val timeStampNanos: Long = 1
+
+        val fwkScanResult = FwkScanResult(
+            fwkBluetoothDevice,
+            1,
+            0,
+            0,
+            0,
+            0,
+            rssi,
+            periodicAdvertisingInterval,
+            null,
+            timeStampNanos
+        )
+
+        val bluetoothAddress = BluetoothAddress(
+            fwkScanResult.device.address,
+            fwkScanResult.device.addressType()
+        )
+
+        assertThat(bluetoothAddress.addressType)
+            .isEqualTo(BluetoothAddress.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE)
+    }
 }
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
index 7846be5..c104d69 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
@@ -44,15 +44,6 @@
 
         /** Address type is unknown. */
         const val ADDRESS_TYPE_UNKNOWN: Int = 0xFFFF
-
-        /** Address type random static bits value */
-        internal const val TYPE_RANDOM_STATIC_BITS_VALUE: Int = 3
-
-        /** Address type random resolvable bits value */
-        internal const val TYPE_RANDOM_RESOLVABLE_BITS_VALUE: Int = 1
-
-        /** Address type random non resolvable bits value */
-        internal const val TYPE_RANDOM_NON_RESOLVABLE_BITS_VALUE: Int = 0
     }
 
     @Target(
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanResult.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanResult.kt
index b9af5f7..2e3cf987 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanResult.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanResult.kt
@@ -22,6 +22,7 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
+import androidx.bluetooth.utils.addressType
 import java.util.UUID
 
 /**
@@ -74,12 +75,10 @@
     /** Remote Bluetooth device found. */
     val device: BluetoothDevice = BluetoothDevice(fwkScanResult.device)
 
-    // TODO(kihongs) Find a way to get address type from framework scan result
     /** Bluetooth address for the remote device found. */
-
     val deviceAddress: BluetoothAddress = BluetoothAddress(
         fwkScanResult.device.address,
-        BluetoothAddress.ADDRESS_TYPE_UNKNOWN
+        fwkScanResult.device.addressType()
     )
 
     /** Device timestamp when the advertisement was last seen. */
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/FwkBluetoothDevice.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/FwkBluetoothDevice.kt
new file mode 100644
index 0000000..3e39c67
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/FwkBluetoothDevice.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.bluetooth.utils
+
+import android.bluetooth.BluetoothDevice as FwkBluetoothDevice
+import android.os.Build
+import android.os.Parcel
+import androidx.annotation.RequiresApi
+import androidx.bluetooth.BluetoothAddress
+
+/** Address type random static bits value */
+private const val ADDRESS_TYPE_RANDOM_STATIC_BITS_VALUE: Int = 3
+
+/** Address type random resolvable bits value */
+private const val ADDRESS_TYPE_RANDOM_RESOLVABLE_BITS_VALUE: Int = 1
+
+/** Address type random non resolvable bits value */
+private const val ADDRESS_TYPE_RANDOM_NON_RESOLVABLE_BITS_VALUE: Int = 0
+
+// mAddressType is added to the parcel in API 34
+internal fun FwkBluetoothDevice.addressType(): @BluetoothAddress.AddressType Int {
+    return if (Build.VERSION.SDK_INT >= 34) {
+        return addressType34()
+    } else {
+        BluetoothAddress.ADDRESS_TYPE_UNKNOWN
+    }
+}
+
+@RequiresApi(34)
+private fun FwkBluetoothDevice.addressType34(): @BluetoothAddress.AddressType Int {
+    val parcel = Parcel.obtain()
+    writeToParcel(parcel, 0)
+    parcel.setDataPosition(0)
+    parcel.readString() // Skip address
+    val mAddressType = parcel.readInt()
+    parcel.recycle()
+
+    return when (mAddressType) {
+        FwkBluetoothDevice.ADDRESS_TYPE_PUBLIC -> BluetoothAddress.ADDRESS_TYPE_PUBLIC
+        FwkBluetoothDevice.ADDRESS_TYPE_RANDOM ->
+            when (address.substring(0, 1).toInt(16).shr(2)) {
+                ADDRESS_TYPE_RANDOM_STATIC_BITS_VALUE ->
+                    BluetoothAddress.ADDRESS_TYPE_RANDOM_STATIC
+
+                ADDRESS_TYPE_RANDOM_RESOLVABLE_BITS_VALUE ->
+                    BluetoothAddress.ADDRESS_TYPE_RANDOM_RESOLVABLE
+
+                ADDRESS_TYPE_RANDOM_NON_RESOLVABLE_BITS_VALUE ->
+                    BluetoothAddress.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE
+
+                else -> BluetoothAddress.ADDRESS_TYPE_UNKNOWN
+            }
+
+        FwkBluetoothDevice.ADDRESS_TYPE_UNKNOWN -> BluetoothAddress.ADDRESS_TYPE_UNKNOWN
+        else -> BluetoothAddress.ADDRESS_TYPE_UNKNOWN
+    }
+}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Utils.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Utils.kt
index 6dac023..ed4f1dd 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Utils.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Utils.kt
@@ -17,11 +17,7 @@
 package androidx.bluetooth.utils
 
 import android.bluetooth.BluetoothDevice as FwkBluetoothDevice
-import android.os.Build
-import android.os.Parcel
-import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
-import androidx.bluetooth.BluetoothAddress
 import java.security.MessageDigest
 import java.util.UUID
 import kotlin.experimental.and
@@ -32,19 +28,7 @@
     packageName: String,
     fwkDevice: FwkBluetoothDevice
 ): UUID {
-    return if (Build.VERSION.SDK_INT >= 34) {
-        deviceId(packageName, fwkDevice.address, fwkDevice.addressType())
-    } else {
-        deviceId(packageName, fwkDevice.address, BluetoothAddress.ADDRESS_TYPE_UNKNOWN)
-    }
-}
-
-private fun deviceId(
-    packageName: String,
-    address: String,
-    @BluetoothAddress.AddressType addressType: Int
-): UUID {
-    val name = packageName + address + addressType
+    val name = packageName + fwkDevice.address + fwkDevice.addressType()
     val md = MessageDigest.getInstance("SHA-1")
     md.update(name.toByteArray())
     val hash = md.digest()
@@ -68,30 +52,3 @@
 
     return UUID(msb, lsb)
 }
-
-// mAddressType is added to the parcel in API 34
-@RequiresApi(34)
-private fun FwkBluetoothDevice.addressType(): @BluetoothAddress.AddressType Int {
-    val parcel = Parcel.obtain()
-    writeToParcel(parcel, 0)
-    parcel.setDataPosition(0)
-    parcel.readString() // Skip address
-    val mAddressType = parcel.readInt()
-    parcel.recycle()
-
-    return when (mAddressType) {
-        FwkBluetoothDevice.ADDRESS_TYPE_PUBLIC -> BluetoothAddress.ADDRESS_TYPE_PUBLIC
-        FwkBluetoothDevice.ADDRESS_TYPE_RANDOM ->
-            when (address.substring(0, 0).toInt(16).shr(2)) {
-                BluetoothAddress.TYPE_RANDOM_STATIC_BITS_VALUE ->
-                    BluetoothAddress.ADDRESS_TYPE_RANDOM_STATIC
-                BluetoothAddress.TYPE_RANDOM_RESOLVABLE_BITS_VALUE ->
-                    BluetoothAddress.ADDRESS_TYPE_RANDOM_RESOLVABLE
-                BluetoothAddress.TYPE_RANDOM_NON_RESOLVABLE_BITS_VALUE ->
-                    BluetoothAddress.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE
-                else -> BluetoothAddress.ADDRESS_TYPE_UNKNOWN
-            }
-        FwkBluetoothDevice.ADDRESS_TYPE_UNKNOWN -> BluetoothAddress.ADDRESS_TYPE_UNKNOWN
-        else -> BluetoothAddress.ADDRESS_TYPE_UNKNOWN
-    }
-}
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SdkResourceGenerator.kt b/buildSrc/public/src/main/kotlin/androidx/build/SdkResourceGenerator.kt
index 1a4dc97..991a22c 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SdkResourceGenerator.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SdkResourceGenerator.kt
@@ -61,7 +61,7 @@
     @get:Input val agpDependency: String = AGP_LATEST
 
     @get:Input
-    val navigationRuntime: String = "androidx.navigation:navigation-runtime:2.4.0-alpha01"
+    val navigationRuntime: String = "androidx.navigation:navigation-runtime:2.4.0"
 
     @get:Input abstract val kotlinStdlib: Property<String>
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt
index 4d1fddd..37150eb 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt
@@ -29,14 +29,18 @@
  */
 @RequiresApi(21)
 class DynamicRangeProfilesCompat internal constructor(
-    private val mImpl: DynamicRangeProfilesCompatImpl
+    private val impl: DynamicRangeProfilesCompatImpl
 ) {
+    /** The set of supported dynamic ranges. */
+    val supportedDynamicRanges: Set<DynamicRange>
+        get() = impl.supportedDynamicRanges
+
     /**
      * Returns a set of supported [DynamicRange] that can be referenced in a single
      * capture request.
      *
      * For example if a particular 10-bit output capable device returns (STANDARD,
-     * HLG10, HDR10) as result from calling [getSupportedDynamicRanges] and
+     * HLG10, HDR10) as result from calling [supportedDynamicRanges] and
      * [DynamicRangeProfiles.getProfileCaptureRequestConstraints]
      * returns (STANDARD, HLG10) when given an argument
      * of STANDARD. This means that the corresponding camera device will only accept and process
@@ -50,21 +54,12 @@
      * @param dynamicRange The dynamic range that will be checked for constraints
      * @return non-modifiable set of dynamic ranges
      * @throws IllegalArgumentException If the dynamic range argument is not within the set
-     * returned by [getSupportedDynamicRanges].
+     * returned by [supportedDynamicRanges].
      */
     fun getDynamicRangeCaptureRequestConstraints(
         dynamicRange: DynamicRange
     ): Set<DynamicRange> {
-        return mImpl.getDynamicRangeCaptureRequestConstraints(dynamicRange)
-    }
-
-    /**
-     * Returns a set of supported dynamic ranges.
-     *
-     * @return a non-modifiable set of dynamic ranges.
-     */
-    fun getSupportedDynamicRanges(): Set<DynamicRange> {
-        return mImpl.getSupportedDynamicRanges()
+        return impl.getDynamicRangeCaptureRequestConstraints(dynamicRange)
     }
 
     /**
@@ -80,10 +75,10 @@
      * @return `true` if the given profile is not suitable for latency sensitive use cases,
      * `false` otherwise.
      * @throws IllegalArgumentException If the dynamic range argument is not within the set
-     * returned by [getSupportedDynamicRanges].
+     * returned by [supportedDynamicRanges].
      */
     fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
-        return mImpl.isExtraLatencyPresent(dynamicRange)
+        return impl.isExtraLatencyPresent(dynamicRange)
     }
 
     /**
@@ -99,16 +94,15 @@
             33, "DynamicRangesCompat can only be " +
                 "converted to DynamicRangeProfiles on API 33 or higher."
         )
-        return mImpl.unwrap()
+        return impl.unwrap()
     }
 
     internal interface DynamicRangeProfilesCompatImpl {
+        val supportedDynamicRanges: Set<DynamicRange>
         fun getDynamicRangeCaptureRequestConstraints(
             dynamicRange: DynamicRange
         ): Set<DynamicRange>
 
-        fun getSupportedDynamicRanges(): Set<DynamicRange>
-
         fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean
         fun unwrap(): DynamicRangeProfiles?
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt
index 12a5687..0197afe 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt
@@ -26,6 +26,10 @@
 internal class DynamicRangeProfilesCompatApi33Impl(
     private val dynamicRangeProfiles: DynamicRangeProfiles
 ) : DynamicRangeProfilesCompat.DynamicRangeProfilesCompatImpl {
+    override val supportedDynamicRanges: Set<DynamicRange>
+        get() = profileSetToDynamicRangeSet(
+            dynamicRangeProfiles.supportedProfiles
+        )
 
     override fun getDynamicRangeCaptureRequestConstraints(
         dynamicRange: DynamicRange
@@ -39,10 +43,6 @@
         )
     }
 
-    override fun getSupportedDynamicRanges() = profileSetToDynamicRangeSet(
-        dynamicRangeProfiles.supportedProfiles
-    )
-
     override fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
         val dynamicRangeProfile = dynamicRangeToFirstSupportedProfile(dynamicRange)
         require(
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt
index b9a0cad..2d5cc50 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt
@@ -24,6 +24,9 @@
 @RequiresApi(21)
 internal class DynamicRangeProfilesCompatBaseImpl :
     DynamicRangeProfilesCompat.DynamicRangeProfilesCompatImpl {
+    override val supportedDynamicRanges: Set<DynamicRange>
+        get() = SDR_ONLY
+
     override fun getDynamicRangeCaptureRequestConstraints(
         dynamicRange: DynamicRange
     ): Set<DynamicRange> {
@@ -34,10 +37,6 @@
         return SDR_ONLY
     }
 
-    override fun getSupportedDynamicRanges(): Set<DynamicRange> {
-        return SDR_ONLY
-    }
-
     override fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
         Preconditions.checkArgument(
             DynamicRange.SDR == dynamicRange,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt
index 947d31c..7871614 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt
@@ -54,7 +54,7 @@
         }
 
         // Get the supported dynamic ranges from the device
-        val supportedDynamicRanges = dynamicRangesInfo.getSupportedDynamicRanges()
+        val supportedDynamicRanges = dynamicRangesInfo.supportedDynamicRanges
 
         // Collect initial dynamic range constraints. This set will potentially shrink as we add
         // more dynamic ranges. We start with the initial set of supported dynamic ranges to
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt
index b06ab92..73172a9 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt
@@ -64,7 +64,7 @@
     fun canSupportDynamicRangeFromHlg10Profile() {
         val dynamicRangeProfilesCompat =
             DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
-        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+        Truth.assertThat(dynamicRangeProfilesCompat?.supportedDynamicRanges)
             .contains(DynamicRange.HLG_10_BIT)
     }
 
@@ -73,7 +73,7 @@
     fun canSupportDynamicRangeFromHdr10Profile() {
         val dynamicRangeProfilesCompat =
             DynamicRangeProfilesCompat.toDynamicRangesCompat(HDR10_UNCONSTRAINED)
-        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+        Truth.assertThat(dynamicRangeProfilesCompat?.supportedDynamicRanges)
             .contains(DynamicRange.HDR10_10_BIT)
     }
 
@@ -82,7 +82,7 @@
     fun canSupportDynamicRangeFromHdr10PlusProfile() {
         val dynamicRangeProfilesCompat =
             DynamicRangeProfilesCompat.toDynamicRangesCompat(HDR10_PLUS_UNCONSTRAINED)
-        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+        Truth.assertThat(dynamicRangeProfilesCompat?.supportedDynamicRanges)
             .contains(DynamicRange.HDR10_PLUS_10_BIT)
     }
 
@@ -91,7 +91,7 @@
     fun canSupportDynamicRangeFromDolbyVision10bProfile() {
         val dynamicRangeProfilesCompat =
             DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_10B_UNCONSTRAINED)
-        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+        Truth.assertThat(dynamicRangeProfilesCompat?.supportedDynamicRanges)
             .contains(DynamicRange.DOLBY_VISION_10_BIT)
     }
 
@@ -100,7 +100,7 @@
     fun canSupportDynamicRangeFromDolbyVision8bProfile() {
         val dynamicRangeProfilesCompat =
             DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_8B_UNCONSTRAINED)
-        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+        Truth.assertThat(dynamicRangeProfilesCompat?.supportedDynamicRanges)
             .contains(DynamicRange.DOLBY_VISION_8_BIT)
     }
 
@@ -203,7 +203,7 @@
         val dynamicRangeProfilesCompat =
             DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
 
-        Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+        Truth.assertThat(dynamicRangeProfilesCompat.supportedDynamicRanges)
             .containsExactly(DynamicRange.SDR)
         Truth.assertThat(
             dynamicRangeProfilesCompat.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
@@ -225,10 +225,10 @@
             DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
 
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
-            Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+            Truth.assertThat(dynamicRangeProfilesCompat.supportedDynamicRanges)
                 .containsExactly(DynamicRange.SDR)
         } else {
-            Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+            Truth.assertThat(dynamicRangeProfilesCompat.supportedDynamicRanges)
                 .containsExactly(
                     DynamicRange.SDR, DynamicRange.DOLBY_VISION_8_BIT
                 )
diff --git a/cardview/cardview/src/main/java/androidx/cardview/widget/CardView.java b/cardview/cardview/src/main/java/androidx/cardview/widget/CardView.java
index 08b5580..b940212 100644
--- a/cardview/cardview/src/main/java/androidx/cardview/widget/CardView.java
+++ b/cardview/cardview/src/main/java/androidx/cardview/widget/CardView.java
@@ -86,8 +86,6 @@
     static {
         if (Build.VERSION.SDK_INT >= 21) {
             IMPL = new CardViewApi21Impl();
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            IMPL = new CardViewApi17Impl();
         } else {
             IMPL = new CardViewBaseImpl();
         }
diff --git a/cardview/cardview/src/main/java/androidx/cardview/widget/CardViewApi17Impl.java b/cardview/cardview/src/main/java/androidx/cardview/widget/CardViewApi17Impl.java
deleted file mode 100644
index 49387fd..0000000
--- a/cardview/cardview/src/main/java/androidx/cardview/widget/CardViewApi17Impl.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.cardview.widget;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(17)
-class CardViewApi17Impl extends CardViewBaseImpl {
-
-    @Override
-    public void initStatic() {
-        RoundRectDrawableWithShadow.sRoundRectHelper =
-                new RoundRectDrawableWithShadow.RoundRectHelper() {
-                    @Override
-                    public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
-                            Paint paint) {
-                        canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
-                    }
-                };
-    }
-}
diff --git a/cardview/cardview/src/main/java/androidx/cardview/widget/CardViewBaseImpl.java b/cardview/cardview/src/main/java/androidx/cardview/widget/CardViewBaseImpl.java
index bc6cd5d..c5a8dec 100644
--- a/cardview/cardview/src/main/java/androidx/cardview/widget/CardViewBaseImpl.java
+++ b/cardview/cardview/src/main/java/androidx/cardview/widget/CardViewBaseImpl.java
@@ -17,64 +17,17 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.RectF;
 
 import androidx.annotation.Nullable;
 
 class CardViewBaseImpl implements CardViewImpl {
 
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final RectF mCornerRect = new RectF();
-
     @Override
     public void initStatic() {
-        // Draws a round rect using 7 draw operations. This is faster than using
-        // canvas.drawRoundRect before JBMR1 because API 11-16 used alpha mask textures to draw
-        // shapes.
         RoundRectDrawableWithShadow.sRoundRectHelper =
-                new RoundRectDrawableWithShadow.RoundRectHelper() {
-            @Override
-            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
-                    Paint paint) {
-                final float twoRadius = cornerRadius * 2;
-                final float innerWidth = bounds.width() - twoRadius - 1;
-                final float innerHeight = bounds.height() - twoRadius - 1;
-                if (cornerRadius >= 1f) {
-                    // increment corner radius to account for half pixels.
-                    float roundedCornerRadius = cornerRadius + .5f;
-                    mCornerRect.set(-roundedCornerRadius, -roundedCornerRadius, roundedCornerRadius,
-                            roundedCornerRadius);
-                    int saved = canvas.save();
-                    canvas.translate(bounds.left + roundedCornerRadius,
-                            bounds.top + roundedCornerRadius);
-                    canvas.drawArc(mCornerRect, 180, 90, true, paint);
-                    canvas.translate(innerWidth, 0);
-                    canvas.rotate(90);
-                    canvas.drawArc(mCornerRect, 180, 90, true, paint);
-                    canvas.translate(innerHeight, 0);
-                    canvas.rotate(90);
-                    canvas.drawArc(mCornerRect, 180, 90, true, paint);
-                    canvas.translate(innerWidth, 0);
-                    canvas.rotate(90);
-                    canvas.drawArc(mCornerRect, 180, 90, true, paint);
-                    canvas.restoreToCount(saved);
-                    //draw top and bottom pieces
-                    canvas.drawRect(bounds.left + roundedCornerRadius - 1f, bounds.top,
-                            bounds.right - roundedCornerRadius + 1f,
-                            bounds.top + roundedCornerRadius, paint);
-
-                    canvas.drawRect(bounds.left + roundedCornerRadius - 1f,
-                            bounds.bottom - roundedCornerRadius,
-                            bounds.right - roundedCornerRadius + 1f, bounds.bottom, paint);
-                }
-                // center
-                canvas.drawRect(bounds.left, bounds.top + cornerRadius,
-                        bounds.right, bounds.bottom - cornerRadius , paint);
-            }
-        };
+                (canvas, bounds, cornerRadius, paint) ->
+                        canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
     }
 
     @Override
diff --git a/compose/animation/animation/api/current.txt b/compose/animation/animation/api/current.txt
index 2e7140c..f8817a9 100644
--- a/compose/animation/animation/api/current.txt
+++ b/compose/animation/animation/api/current.txt
@@ -19,8 +19,6 @@
   public sealed interface AnimatedContentTransitionScope<S> extends androidx.compose.animation.core.Transition.Segment<S> {
     method public androidx.compose.ui.Alignment getContentAlignment();
     method public default androidx.compose.animation.ExitTransition getKeepUntilTransitionsFinished(androidx.compose.animation.ExitTransition.Companion);
-    method @SuppressCompatibility @androidx.compose.animation.ExperimentalAnimationApi public androidx.compose.animation.EnterTransition scaleInToFitContainer(optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale);
-    method @SuppressCompatibility @androidx.compose.animation.ExperimentalAnimationApi public androidx.compose.animation.ExitTransition scaleOutToFitContainer(optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale);
     method public androidx.compose.animation.EnterTransition slideIntoContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialOffset);
     method public androidx.compose.animation.ExitTransition slideOutOfContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetOffset);
     method public infix androidx.compose.animation.ContentTransform using(androidx.compose.animation.ContentTransform, androidx.compose.animation.SizeTransform? sizeTransform);
diff --git a/compose/animation/animation/api/restricted_current.txt b/compose/animation/animation/api/restricted_current.txt
index 2e7140c..f8817a9 100644
--- a/compose/animation/animation/api/restricted_current.txt
+++ b/compose/animation/animation/api/restricted_current.txt
@@ -19,8 +19,6 @@
   public sealed interface AnimatedContentTransitionScope<S> extends androidx.compose.animation.core.Transition.Segment<S> {
     method public androidx.compose.ui.Alignment getContentAlignment();
     method public default androidx.compose.animation.ExitTransition getKeepUntilTransitionsFinished(androidx.compose.animation.ExitTransition.Companion);
-    method @SuppressCompatibility @androidx.compose.animation.ExperimentalAnimationApi public androidx.compose.animation.EnterTransition scaleInToFitContainer(optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale);
-    method @SuppressCompatibility @androidx.compose.animation.ExperimentalAnimationApi public androidx.compose.animation.ExitTransition scaleOutToFitContainer(optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale);
     method public androidx.compose.animation.EnterTransition slideIntoContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialOffset);
     method public androidx.compose.animation.ExitTransition slideOutOfContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetOffset);
     method public infix androidx.compose.animation.ContentTransform using(androidx.compose.animation.ContentTransform, androidx.compose.animation.SizeTransform? sizeTransform);
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/ContainerTransform.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/ContainerTransform.kt
deleted file mode 100644
index cf03291..0000000
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/ContainerTransform.kt
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.animation.demos.layoutanimation
-
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.animation.core.animateIntAsState
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.togetherWith
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.RadioButton
-import androidx.compose.material.Text
-import androidx.compose.material.TextField
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.AccountCircle
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.rememberVectorPainter
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-
-@OptIn(ExperimentalAnimationApi::class)
-@Preview
-@Composable
-fun LocalContainerTransformDemo() {
-    Box(Modifier.fillMaxSize()) {
-        LazyColumn {
-            items(20) {
-                Box(
-                    Modifier
-                        .fillMaxWidth()
-                        .height(150.dp)
-                        .padding(15.dp)
-                        .background(MaterialTheme.colors.primary)
-                )
-            }
-        }
-    }
-    var selectedAlignment by remember { mutableStateOf(Alignment.Center) }
-    var contentScale by remember { mutableStateOf(ContentScale.FillWidth) }
-    Column(
-        Modifier.padding(top = 100.dp)
-    ) {
-        Column(
-            Modifier
-                .background(Color.LightGray, RoundedCornerShape(10.dp)),
-        ) {
-            Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
-                RadioButton(
-                    selected = selectedAlignment == Alignment.TopStart,
-                    onClick = { selectedAlignment = Alignment.TopStart }
-                )
-                Text("TopStart", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = selectedAlignment == Alignment.TopCenter,
-                    onClick = { selectedAlignment = Alignment.TopCenter }
-                )
-                Text("TopCenter", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = selectedAlignment == Alignment.TopEnd,
-                    onClick = { selectedAlignment = Alignment.TopEnd }
-                )
-                Text("TopEnd", Modifier.padding(5.dp))
-            }
-            Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
-                RadioButton(
-                    selected = selectedAlignment == Alignment.CenterStart,
-                    onClick = { selectedAlignment = Alignment.CenterStart }
-                )
-                Text("CenterStart", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = selectedAlignment == Alignment.Center,
-                    onClick = { selectedAlignment = Alignment.Center }
-                )
-                Text("Center", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = selectedAlignment == Alignment.CenterEnd,
-                    onClick = { selectedAlignment = Alignment.CenterEnd }
-                )
-                Text("CenterEnd", Modifier.padding(5.dp))
-            }
-            Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
-                RadioButton(
-                    selected = selectedAlignment == Alignment.BottomStart,
-                    onClick = { selectedAlignment = Alignment.BottomStart }
-                )
-                Text("BottomStart", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = selectedAlignment == Alignment.BottomCenter,
-                    onClick = { selectedAlignment = Alignment.BottomCenter }
-                )
-                Text("BottomCenter", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = selectedAlignment == Alignment.BottomEnd,
-                    onClick = { selectedAlignment = Alignment.BottomEnd }
-                )
-                Text("BottomEnd", Modifier.padding(5.dp))
-            }
-        }
-        Column(
-            Modifier
-                .background(Color.Gray, RoundedCornerShape(10.dp)),
-        ) {
-            Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
-                RadioButton(
-                    selected = contentScale == ContentScale.FillWidth,
-                    onClick = { contentScale = ContentScale.FillWidth }
-                )
-                Text("FillWidth", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = contentScale == ContentScale.FillHeight,
-                    onClick = { contentScale = ContentScale.FillHeight }
-                )
-                Text("FillHeight", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = contentScale == ContentScale.FillBounds,
-                    onClick = { contentScale = ContentScale.FillBounds }
-                )
-                Text("FillBounds", Modifier.padding(5.dp))
-            }
-            Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
-                RadioButton(
-                    selected = contentScale == ContentScale.Crop,
-                    onClick = { contentScale = ContentScale.Crop }
-                )
-                Text("Crop", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = contentScale == ContentScale.Fit,
-                    onClick = { contentScale = ContentScale.Fit }
-                )
-                Text("Fit", Modifier.padding(5.dp))
-                RadioButton(
-                    selected = contentScale == ContentScale.Inside,
-                    onClick = { contentScale = ContentScale.Inside }
-                )
-                Text("Inside", Modifier.padding(5.dp))
-            }
-        }
-    }
-    Box(Modifier.fillMaxSize()) {
-        var target by remember { mutableStateOf(ContainerState.FAB) }
-        // Corner radius
-        val cr by animateIntAsState(if (target == ContainerState.FAB) 50 else 0)
-        val padding by animateDpAsState(if (target == ContainerState.FAB) 10.dp else 0.dp)
-        AnimatedContent(
-            target,
-            label = "",
-            transitionSpec = {
-                fadeIn(tween(200, delayMillis = 100)) +
-                    scaleInToFitContainer(selectedAlignment, contentScale) togetherWith
-                    fadeOut(tween(100)) + scaleOutToFitContainer(selectedAlignment, contentScale)
-            },
-            modifier = Modifier
-                .align(Alignment.BottomEnd)
-                .padding(padding)
-                .clip(RoundedCornerShape(cr))
-                .background(Color.White)
-        ) {
-            if (it == ContainerState.FAB) {
-                Icon(
-                    rememberVectorPainter(image = Icons.Default.Add),
-                    null,
-                    modifier = Modifier
-                        .clickable {
-                            target = ContainerState.FullScreen
-                        }
-                        .padding(20.dp))
-            } else {
-                Column(Modifier.fillMaxSize()) {
-                    Icon(
-                        rememberVectorPainter(image = Icons.AutoMirrored.Filled.ArrowBack),
-                        null,
-                        modifier = Modifier
-                            .clickable {
-                                target = ContainerState.FAB
-                            }
-                            .padding(20.dp))
-                    Spacer(Modifier.height(60.dp))
-                    Text("Page Title", fontSize = 20.sp, modifier = Modifier.padding(20.dp))
-                    Spacer(
-                        Modifier
-                            .fillMaxWidth()
-                            .height(2.dp)
-                            .background(Color.LightGray)
-                    )
-                    Row(
-                        Modifier
-                            .fillMaxWidth()
-                            .padding(20.dp)
-                    ) {
-                        Icon(rememberVectorPainter(image = Icons.Default.AccountCircle), null)
-                        Spacer(Modifier.width(20.dp))
-                        TextField(value = "Account Name", onValueChange = {})
-                    }
-                }
-            }
-        }
-    }
-}
-
-private enum class ContainerState {
-    FAB,
-    FullScreen
-}
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt
index c9c4f15..b9eeaff 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt
@@ -22,7 +22,6 @@
 import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
 import androidx.compose.animation.ContentTransform
 import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.SizeTransform
 import androidx.compose.animation.core.animateDp
 import androidx.compose.animation.core.keyframes
@@ -55,7 +54,6 @@
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
@@ -309,30 +307,6 @@
     }
 }
 
-@OptIn(ExperimentalAnimationApi::class)
-@Suppress("UNUSED_VARIABLE")
-@Sampled
-@Composable
-fun ScaleInToFitContainerSample() {
-    // enum class CartState { Expanded, Collapsed }
-    // This is an example of scaling both the incoming content and outgoing content to fit in the
-    // animating container size while animating alpha.
-    val transitionSpec: AnimatedContentTransitionScope<CartState>.() -> ContentTransform = {
-        // Fade in while scaling the content.
-        fadeIn() + scaleInToFitContainer() togetherWith
-            // Fade out outgoing content while scaling it. It is important
-            // to combine `scaleOutToFitContainer` with another ExitTransition that defines
-            // a timeframe for the exit (such as fade/shrink/slide/Hold).
-            fadeOut() + scaleOutToFitContainer(
-                // Default alignment is the content alignment defined in AnimatedContent
-                Alignment.Center,
-                // Content will be scaled based on the height of the content. Default content
-                // scale is ContentScale.FillWidth.
-                ContentScale.FillHeight
-            )
-    }
-}
-
 private enum class CartState {
     Expanded,
     Collapsed
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 0568edf..56d0163 100644
--- a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -51,19 +51,13 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.SubcomposeLayout
-import androidx.compose.ui.layout.boundsInRoot
-import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -72,7 +66,6 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.round
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
@@ -789,334 +782,6 @@
     }
 
     @Test
-    fun testScaleToFitDefault() {
-        var target by mutableStateOf(1)
-        var box1Coords: LayoutCoordinates? = null
-        var box2Coords: LayoutCoordinates? = null
-        var box1Disposed = true
-        var box2Disposed = true
-        rule.setContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                AnimatedContent(
-                    targetState = target,
-                    transitionSpec = {
-                        if (1 isTransitioningTo 2) {
-                            fadeIn(tween(300)) + scaleInToFitContainer() togetherWith
-                                scaleOutToFitContainer()
-                        } else {
-                            fadeIn() + scaleInToFitContainer() togetherWith
-                                fadeOut(tween(150))
-                        } using SizeTransform { initialSize, targetSize ->
-                            keyframes {
-                                durationMillis = 300
-                                initialSize at 100 using LinearEasing
-                                targetSize at 200 using LinearEasing
-                            }
-                        }
-                    }) {
-                    if (it == 1) {
-                        Box(
-                            Modifier
-                                .onPlaced {
-                                    box1Coords = it
-                                }
-                                .size(200.dp, 400.dp)) {
-                            DisposableEffect(key1 = Unit) {
-                                box1Disposed = false
-                                onDispose {
-                                    box1Disposed = true
-                                }
-                            }
-                        }
-                    } else {
-                        Box(
-                            Modifier
-                                .onPlaced { box2Coords = it }
-                                .size(100.dp, 50.dp)) {
-
-                            DisposableEffect(key1 = Unit) {
-                                box2Disposed = false
-                                onDispose {
-                                    box2Disposed = true
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        rule.mainClock.autoAdvance = false
-        assertEquals(IntSize(200, 400), box1Coords?.size)
-        assertNull(box2Coords)
-
-        assertFalse(box1Disposed)
-        assertTrue(box2Disposed)
-
-        rule.runOnIdle {
-            // Start transition from 1 -> 2, size 200,400 -> 100,50
-            target = 2
-        }
-        rule.mainClock.advanceTimeByFrame()
-        rule.waitForIdle()
-
-        // Box1 doesn't have any other ExitTransition than scale, so it'll be disposed
-        // after a couple of frames
-        assertFalse(box1Disposed)
-        assertFalse(box2Disposed)
-
-        repeat(20) {
-            rule.mainClock.advanceTimeByFrame()
-
-            val playTime = 16 * it
-            val bounds2 = box2Coords?.boundsInRoot()
-            if (playTime <= 100) {
-                assertEquals(Rect(0f, 0f, 200f, 100f), bounds2)
-            } else if (playTime <= 200) {
-                val fraction = (playTime - 100) / 100f
-                val width = 200 * (1 - fraction) + 100 * fraction
-                // Since we are testing default behavior, the scaling is based on width.
-                val height = width / 100f * 50
-                assertEquals(Offset.Zero, bounds2?.topLeft)
-                assertEquals(width, bounds2?.width)
-                assertEquals(height, bounds2?.height)
-            } else {
-                assertEquals(Rect(0f, 0f, 100f, 50f), bounds2)
-            }
-        }
-
-        rule.runOnIdle {
-            // Start transition from false -> true, size 100, 50 -> 200,400
-            target = 1
-        }
-        rule.mainClock.advanceTimeByFrame()
-        rule.waitForIdle()
-
-        assertFalse(box1Disposed)
-        assertFalse(box2Disposed)
-
-        repeat(20) {
-            rule.mainClock.advanceTimeByFrame()
-            val playTime = 16 * it
-            val bounds = box1Coords?.boundsInRoot()
-            if (playTime <= 100) {
-                assertEquals(100f, bounds?.width)
-                assertFalse(box2Disposed)
-            } else if (playTime <= 150) {
-                val fraction = (playTime - 100) / 100f
-                val width = 100 * (1 - fraction) + 200 * fraction
-                // Since we are testing default behavior, the scaling is based on width.
-                assertEquals(Offset.Zero, bounds?.topLeft)
-                assertEquals(width, bounds?.width)
-            } else {
-                rule.waitForIdle()
-                assertThat(box2Disposed)
-            }
-        }
-    }
-
-    @Test
-    fun testScaleToFitCenterAlignment() {
-        var target by mutableStateOf(true)
-        var box1Coords: LayoutCoordinates? = null
-        var box2Coords: LayoutCoordinates? = null
-        var layoutDirection: LayoutDirection? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                layoutDirection = LocalLayoutDirection.current
-                AnimatedContent(
-                    targetState = target,
-                    transitionSpec = {
-                        fadeIn() + scaleInToFitContainer(Alignment.Center) togetherWith
-                            fadeOut(tween(100)) using
-                            SizeTransform { _, _ ->
-                                tween(100, easing = LinearEasing)
-                            }
-                    }) {
-                    if (target) {
-                        Box(
-                            Modifier
-                                .onPlaced {
-                                    box1Coords = it
-                                }
-                                .size(200.dp, 400.dp))
-                    } else {
-                        Box(
-                            Modifier
-                                .onPlaced { box2Coords = it }
-                                .size(100.dp, 50.dp))
-                    }
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        assertEquals(IntSize(200, 400), box1Coords?.size)
-        assertNull(box2Coords)
-
-        rule.runOnIdle {
-            // Start transition from true -> false, size 200,400 -> 100,50
-            target = false
-        }
-        rule.mainClock.advanceTimeByFrame()
-        repeat(10) {
-            rule.mainClock.advanceTimeByFrame()
-            val playTime = 16 * it
-            val bounds = box2Coords?.boundsInRoot()
-            assertNotNull(bounds)
-            val fraction = (playTime / 100f).coerceAtMost(1f)
-            val width = 200 * (1 - fraction) + 100 * fraction
-            val containerHeight = 400 * (1 - fraction) + 50 * fraction
-            // Since we are testing default behavior, the scaling is based on width.
-            val height = width / 100f * 50
-            assertEquals(width, bounds!!.width, 0.01f)
-            assertEquals(height, bounds.height, 0.01f)
-            val offset = Alignment.Center.align(
-                IntSize(width.roundToInt(), height.roundToInt()),
-                IntSize(width.roundToInt(), containerHeight.roundToInt()), layoutDirection!!
-            )
-            assertEquals(offset, bounds.topLeft.round())
-        }
-    }
-
-    @Test
-    fun testScaleToFitBottomCenterAlignment() {
-        var target by mutableStateOf(true)
-        var box1Coords: LayoutCoordinates? = null
-        var box2Coords: LayoutCoordinates? = null
-        var layoutDirection: LayoutDirection? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                layoutDirection = LocalLayoutDirection.current
-                AnimatedContent(
-                    targetState = target,
-                    transitionSpec = {
-                        fadeIn() + scaleInToFitContainer(
-                            Alignment.BottomCenter
-                        ) togetherWith
-                            fadeOut(tween(100)) using
-                            SizeTransform { _, _ ->
-                                tween(100, easing = LinearEasing)
-                            }
-                    }) {
-                    if (target) {
-                        Box(
-                            Modifier
-                                .onPlaced {
-                                    box1Coords = it
-                                }
-                                .size(200.dp, 400.dp))
-                    } else {
-                        Box(
-                            Modifier
-                                .onPlaced { box2Coords = it }
-                                .size(100.dp, 50.dp))
-                    }
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        rule.mainClock.autoAdvance = false
-        assertEquals(IntSize(200, 400), box1Coords?.size)
-        assertNull(box2Coords)
-
-        rule.runOnIdle {
-            // Start transition from true -> false, size 200,400 -> 100,50
-            target = false
-        }
-        rule.mainClock.advanceTimeByFrame()
-        repeat(10) {
-            rule.mainClock.advanceTimeByFrame()
-            val playTime = 16 * it
-            val bounds = box2Coords?.boundsInRoot()
-            assertNotNull(bounds)
-            val fraction = (playTime / 100f).coerceAtMost(1f)
-            val width = 200 * (1 - fraction) + 100 * fraction
-            val containerHeight = 400 * (1 - fraction) + 50 * fraction
-            // Since we are testing default behavior, the scaling is based on width.
-            val height = width / 100f * 50
-            assertEquals(width, bounds!!.width, 0.01f)
-            assertEquals(height, bounds.height, 0.01f)
-            val offset = Alignment.BottomCenter.align(
-                IntSize(width.roundToInt(), height.roundToInt()),
-                IntSize(width.roundToInt(), containerHeight.roundToInt()), layoutDirection!!
-            )
-            assertEquals(offset, bounds.topLeft.round())
-        }
-    }
-
-    @Test
-    fun testScaleToFitInsideBottomEndAlignment() {
-        var target by mutableStateOf(true)
-        var box1Coords: LayoutCoordinates? = null
-        var box2Coords: LayoutCoordinates? = null
-        var layoutDirection: LayoutDirection? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                layoutDirection = LocalLayoutDirection.current
-                AnimatedContent(
-                    targetState = target,
-                    transitionSpec = {
-                        fadeIn() + scaleInToFitContainer(
-                            Alignment.BottomEnd, ContentScale.Inside
-                        ) togetherWith
-                            fadeOut(tween(100)) using
-                            SizeTransform { _, _ ->
-                                tween(100, easing = LinearEasing)
-                            }
-                    }) {
-                    if (target) {
-                        Box(
-                            Modifier
-                                .onPlaced {
-                                    box1Coords = it
-                                }
-                                .size(200.dp, 400.dp))
-                    } else {
-                        Box(
-                            Modifier
-                                .onPlaced { box2Coords = it }
-                                .size(100.dp, 50.dp))
-                    }
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        rule.mainClock.autoAdvance = false
-        assertEquals(IntSize(200, 400), box1Coords?.size)
-        assertNull(box2Coords)
-
-        rule.runOnIdle {
-            // Start transition from true -> false, size 200,400 -> 100,50
-            target = false
-        }
-        rule.mainClock.advanceTimeByFrame()
-        repeat(10) {
-            rule.mainClock.advanceTimeByFrame()
-            val playTime = 16 * it
-            val bounds = box2Coords?.boundsInRoot()
-            assertNotNull(bounds)
-            val fraction = (playTime / 100f).coerceAtMost(1f)
-            val width = 100f
-            val containerWidth = 200 * (1 - fraction) + 100 * fraction
-            val containerHeight = 400 * (1 - fraction) + 50 * fraction
-            // Since we are testing default behavior, the scaling is based on width.
-            val height = 50f
-            assertEquals(width, bounds!!.width, 0.01f)
-            assertEquals(height, bounds.height, 0.01f)
-            val offset = Alignment.BottomEnd.align(
-                IntSize(width.roundToInt(), height.roundToInt()),
-                IntSize(containerWidth.roundToInt(), containerHeight.roundToInt()),
-                layoutDirection!!
-            )
-            assertEquals(offset, bounds.topLeft.round())
-        }
-    }
-
-    @Test
     fun testRightEnterExitTransitionIsChosenDuringInterruption() {
         var flag by mutableStateOf(false)
         var fixedPosition: Offset? = null
@@ -1187,72 +852,6 @@
         rule.waitForIdle()
     }
 
-    @Test
-    fun testScaleToFitWithFitHeight() {
-        var target by mutableStateOf(true)
-        var box1Coords: LayoutCoordinates? = null
-        var box2Coords: LayoutCoordinates? = null
-        var layoutDirection: LayoutDirection? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                layoutDirection = LocalLayoutDirection.current
-                AnimatedContent(
-                    targetState = target,
-                    transitionSpec = {
-                        fadeIn() + scaleInToFitContainer(
-                            Alignment.Center, ContentScale.FillHeight
-                        ) togetherWith fadeOut(tween(100)) using
-                            SizeTransform { _, _ ->
-                                tween(100, easing = LinearEasing)
-                            }
-                    }) {
-                    if (target) {
-                        Box(
-                            Modifier
-                                .onPlaced {
-                                    box1Coords = it
-                                }
-                                .size(200.dp, 400.dp))
-                    } else {
-                        Box(
-                            Modifier
-                                .onPlaced { box2Coords = it }
-                                .size(100.dp, 250.dp))
-                    }
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        rule.mainClock.autoAdvance = false
-        assertEquals(IntSize(200, 400), box1Coords?.size)
-        assertNull(box2Coords)
-
-        rule.runOnIdle {
-            // Start transition from true -> false, size 200,400 -> 100,250
-            target = false
-        }
-        rule.mainClock.advanceTimeByFrame()
-        repeat(10) {
-            rule.mainClock.advanceTimeByFrame()
-            val playTime = 16 * it
-            val bounds = box2Coords?.boundsInRoot()
-            assertNotNull(bounds)
-            val fraction = (playTime / 100f).coerceAtMost(1f)
-            val height = 400 * (1 - fraction) + 250 * fraction
-            val containerWidth = 200 * (1 - fraction) + 100 * fraction
-            // Since we are testing default behavior, the scaling is based on width.
-            val width = height / 250f * 100
-            assertEquals(width, bounds!!.width, 0.01f)
-            assertEquals(height, bounds.height, 0.01f)
-            val offset = Alignment.Center.align(
-                IntSize(width.roundToInt(), height.roundToInt()),
-                IntSize(containerWidth.roundToInt(), height.roundToInt()), layoutDirection!!
-            )
-            assertEquals(offset, bounds.topLeft.round())
-        }
-    }
-
     @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun testExitHoldDefersUntilAllFinished() {
@@ -1327,51 +926,6 @@
         assertTrue(box2EnterFinished)
     }
 
-    /**
-     * This test checks that scaleInToFitContainer and scaleOutToFitContainer handle empty
-     * content correctly.
-     */
-    @Test
-    fun testAnimateToEmptyComposable() {
-        var isEmpty by mutableStateOf(false)
-        var targetSize: IntSize? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                AnimatedContent(targetState = isEmpty,
-                    transitionSpec = {
-                        scaleInToFitContainer() togetherWith scaleOutToFitContainer()
-                    },
-                    modifier = Modifier.layout { measurable, constraints ->
-                        measurable.measure(constraints).run {
-                            if (isLookingAhead) {
-                                targetSize = IntSize(width, height)
-                            }
-                            layout(width, height) {
-                                place(0, 0)
-                            }
-                        }
-                    }
-                ) {
-                    if (!it) {
-                        Box(Modifier.size(200.dp))
-                    }
-                }
-            }
-        }
-        rule.runOnIdle {
-            assertEquals(IntSize(200, 200), targetSize)
-            isEmpty = true
-        }
-
-        rule.runOnIdle {
-            assertEquals(IntSize.Zero, targetSize)
-            isEmpty = !isEmpty
-        }
-        rule.runOnIdle {
-            assertEquals(IntSize(200, 200), targetSize)
-        }
-    }
-
     @OptIn(InternalAnimationApi::class)
     private val Transition<*>.playTimeMillis get() = (playTimeNanos / 1_000_000L).toInt()
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index 24ce9d9..896308a 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -1,3 +1,4 @@
+
 /*
  * Copyright 2021 The Android Open Source Project
  *
@@ -13,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:OptIn(InternalAnimationApi::class, ExperimentalAnimationApi::class)
-
+@file:OptIn(InternalAnimationApi::class)
 package androidx.compose.animation
-
 import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Down
 import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.End
 import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Left
@@ -37,7 +36,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
@@ -45,44 +43,30 @@
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
-import androidx.compose.ui.graphics.TransformOrigin
-import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasurePolicy
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.ParentDataModifier
 import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.ScaleFactor
 import androidx.compose.ui.layout.layout
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachIndexed
 import androidx.compose.ui.util.fastMaxOfOrNull
-import androidx.compose.ui.util.fastRoundToInt
-import kotlinx.coroutines.CoroutineScope
-
 /**
  * [AnimatedContent] is a container that automatically animates its content when [targetState]
  * changes. Its [content] for different target states is defined in a mapping between a target
@@ -161,7 +145,6 @@
         content = content
     )
 }
-
 /**
  * [ContentTransform] defines how the target content (i.e. content associated with target state)
  * enters [AnimatedContent] and how the initial content disappears.
@@ -212,7 +195,6 @@
      * content with the same index, the target content will be placed on top.
      */
     var targetContentZIndex by mutableFloatStateOf(targetContentZIndex)
-
     /**
      * [sizeTransform] manages the expanding and shrinking of the container if there is any size
      * change as new content enters the [AnimatedContent] and old content leaves.
@@ -223,7 +205,6 @@
     var sizeTransform: SizeTransform? = sizeTransform
         internal set
 }
-
 /**
  * This creates a [SizeTransform] with the provided [clip] and [sizeAnimationSpec]. By default,
  * [clip] will be true. This means during the size animation, the content will be clipped to the
@@ -241,7 +222,6 @@
             )
         }
 ): SizeTransform = SizeTransformImpl(clip, sizeAnimationSpec)
-
 /**
  * [SizeTransform] defines how to transform from one size to another when the size of the content
  * changes. When [clip] is true, the content will be clipped to the animation size.
@@ -255,14 +235,12 @@
      * Whether the content should be clipped using the animated size.
      */
     val clip: Boolean
-
     /**
      * This allows [FiniteAnimationSpec] to be defined based on the [initialSize] before the size
      * animation and the [targetSize] of the animation.
      */
     fun createAnimationSpec(initialSize: IntSize, targetSize: IntSize): FiniteAnimationSpec<IntSize>
 }
-
 /**
  * Private implementation of SizeTransform interface.
  */
@@ -276,7 +254,6 @@
         targetSize: IntSize
     ): FiniteAnimationSpec<IntSize> = sizeAnimationSpec(initialSize, targetSize)
 }
-
 /**
  * This creates a [ContentTransform] using the provided [EnterTransition] and [exit], where the
  * enter and exit transition will be running simultaneously.
@@ -285,14 +262,12 @@
  * @sample androidx.compose.animation.samples.AnimatedContentTransitionSpecSample
  */
 infix fun EnterTransition.togetherWith(exit: ExitTransition) = ContentTransform(this, exit)
-
 @ExperimentalAnimationApi
 @Deprecated(
     "Infix fun EnterTransition.with(ExitTransition) has been renamed to" +
         " togetherWith", ReplaceWith("togetherWith(exit)")
 )
 infix fun EnterTransition.with(exit: ExitTransition) = ContentTransform(this, exit)
-
 /**
  * [AnimatedContentTransitionScope] provides functions that are convenient and only applicable in the
  * context of [AnimatedContent], such as [slideIntoContainer] and [slideOutOfContainer].
@@ -304,7 +279,6 @@
      * @sample androidx.compose.animation.samples.AnimatedContentTransitionSpecSample
      */
     infix fun ContentTransform.using(sizeTransform: SizeTransform?): ContentTransform
-
     /**
      * [SlideDirection] defines the direction of the slide in/out for [slideIntoContainer] and
      * [slideOutOfContainer]. The supported directions are: [Left], [Right], [Up] and [Down].
@@ -320,7 +294,6 @@
             val Start = SlideDirection(4)
             val End = SlideDirection(5)
         }
-
         override fun toString(): String {
             return when (this) {
                 Left -> "Left"
@@ -333,7 +306,6 @@
             }
         }
     }
-
     /**
      * This defines a horizontal/vertical slide-in that is specific to [AnimatedContent] from the
      * edge of the container. The offset amount is dynamically calculated based on the current
@@ -363,7 +335,6 @@
         ),
         initialOffset: (offsetForFullSlide: Int) -> Int = { it }
     ): EnterTransition
-
     /**
      * This defines a horizontal/vertical exit transition to completely slide out of the
      * [AnimatedContent] container. The offset amount is dynamically calculated based on the current
@@ -392,7 +363,6 @@
         ),
         targetOffset: (offsetForFullSlide: Int) -> Int = { it }
     ): ExitTransition
-
     /**
      * [KeepUntilTransitionsFinished] defers the disposal of the exiting content till both enter and
      * exit transitions have finished. It can be combined with other [ExitTransition]s using
@@ -408,81 +378,28 @@
      */
     val ExitTransition.Companion.KeepUntilTransitionsFinished: ExitTransition
         get() = KeepUntilTransitionsFinished
-
     /**
      * This returns the [Alignment] specified on [AnimatedContent].
      */
     val contentAlignment: Alignment
-
-    /**
-     * [scaleInToFitContainer] defines an [EnterTransition] that scales the incoming content
-     * based on the (potentially animating) container (i.e. [AnimatedContent]) size. [contentScale]
-     * defines the scaling function. By default, the incoming content will be scaled based on its
-     * width (i.e. [ContentScale.FillWidth]), so that the content fills the container's width.
-     * [alignment] can be used to specify the alignment of the scaled content
-     * within the container of AnimatedContent.
-     *
-     * [scaleInToFitContainer] will measure the content using the final (i.e. lookahead)
-     * constraints, in order to obtain the final layout and apply scaling to that final layout
-     * while the container is resizing.
-     *
-     * @sample androidx.compose.animation.samples.ScaleInToFitContainerSample
-     */
-    @ExperimentalAnimationApi
-    fun scaleInToFitContainer(
-        alignment: Alignment = contentAlignment,
-        contentScale: ContentScale = ContentScale.FillWidth
-    ): EnterTransition
-
-    /**
-     * [scaleOutToFitContainer] defines an [ExitTransition] that scales the outgoing content
-     * based on the (potentially animating) container (i.e. [AnimatedContent]) size.
-     * [contentScale] defines the scaling function. By default, the outgoing content will be scaled
-     * using [ContentScale.FillWidth], so that it fits the container's width.
-     * [alignment] can be used to specify the alignment of the scaled content
-     * within the container of AnimatedContent.
-     *
-     * [scaleOutToFitContainer] will measure the content using the constraints cached
-     * at the beginning of the exit animation so that the content does not get re-laid out during
-     * the exit animation, and instead only scaling will be applied as the container resizes.
-     *
-     * **IMPORTANT**: [scaleOutToFitContainer] does NOT keep the exiting content from being
-     * disposed. Therefore it relies on other ExitTransitions such as [fadeOut] to define a
-     * timeframe for when should be active.
-     *
-     * @sample androidx.compose.animation.samples.ScaleInToFitContainerSample
-     */
-    @ExperimentalAnimationApi
-    fun scaleOutToFitContainer(
-        alignment: Alignment = contentAlignment,
-        contentScale: ContentScale = ContentScale.FillWidth,
-    ): ExitTransition
 }
-
-internal class AnimatedContentRootScope<S> internal constructor(
+internal class AnimatedContentTransitionScopeImpl<S> internal constructor(
     internal val transition: Transition<S>,
-    lookaheadScope: LookaheadScope,
-    internal val coroutineScope: CoroutineScope,
     override var contentAlignment: Alignment,
     internal var layoutDirection: LayoutDirection
-) : AnimatedContentTransitionScope<S>, LookaheadScope by lookaheadScope {
-    lateinit var rootCoords: LayoutCoordinates
-    lateinit var rootLookaheadCoords: LayoutCoordinates
-
+) : AnimatedContentTransitionScope<S> {
     /**
      * Initial state of a Transition Segment. This is the state that transition starts from.
      */
     override val initialState: S
         @Suppress("UnknownNullness")
         get() = transition.segment.initialState
-
     /**
      * Target state of a Transition Segment. This is the state that transition will end on.
      */
     override val targetState: S
         @Suppress("UnknownNullness")
         get() = transition.segment.targetState
-
     /**
      * Customizes the [SizeTransform] of a given [ContentTransform]. For example:
      *
@@ -491,7 +408,6 @@
     override infix fun ContentTransform.using(sizeTransform: SizeTransform?) = this.apply {
         this.sizeTransform = sizeTransform
     }
-
     /**
      * This defines a horizontal/vertical slide-in that is specific to [AnimatedContent] from the
      * edge of the container. The offset amount is dynamically calculated based on the current
@@ -528,24 +444,19 @@
                     currentSize.width - calculateOffset(IntSize(it, it), currentSize).x
                 )
             }
-
             towards.isRight -> slideInHorizontally(animationSpec) {
                 initialOffset.invoke(-calculateOffset(IntSize(it, it), currentSize).x - it)
             }
-
             towards == Up -> slideInVertically(animationSpec) {
                 initialOffset.invoke(
                     currentSize.height - calculateOffset(IntSize(it, it), currentSize).y
                 )
             }
-
             towards == Down -> slideInVertically(animationSpec) {
                 initialOffset.invoke(-calculateOffset(IntSize(it, it), currentSize).y - it)
             }
-
             else -> EnterTransition.None
         }
-
     private val AnimatedContentTransitionScope.SlideDirection.isLeft: Boolean
         get() {
             return this == Left || this == Start && layoutDirection == LayoutDirection.Ltr ||
@@ -556,11 +467,9 @@
             return this == Right || this == Start && layoutDirection == LayoutDirection.Rtl ||
                 this == End && layoutDirection == LayoutDirection.Ltr
         }
-
     private fun calculateOffset(fullSize: IntSize, currentSize: IntSize): IntOffset {
         return contentAlignment.align(fullSize, currentSize, LayoutDirection.Ltr)
     }
-
     /**
      * This defines a horizontal/vertical exit transition to completely slide out of the
      * [AnimatedContent] container. The offset amount is dynamically calculated based on the current
@@ -596,69 +505,32 @@
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                 targetOffset.invoke(-calculateOffset(IntSize(it, it), targetSize).x - it)
             }
-
             towards.isRight -> slideOutHorizontally(animationSpec) {
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                 targetOffset.invoke(
                     -calculateOffset(IntSize(it, it), targetSize).x + targetSize.width
                 )
             }
-
             towards == Up -> slideOutVertically(animationSpec) {
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                 targetOffset.invoke(-calculateOffset(IntSize(it, it), targetSize).y - it)
             }
-
             towards == Down -> slideOutVertically(animationSpec) {
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                 targetOffset.invoke(
                     -calculateOffset(IntSize(it, it), targetSize).y + targetSize.height
                 )
             }
-
             else -> ExitTransition.None
         }
     }
-
-    @ExperimentalAnimationApi
-    override fun scaleInToFitContainer(
-        alignment: Alignment,
-        contentScale: ContentScale
-    ): EnterTransition = EnterTransition(
-        ScaleToFitTransitionKey, ScaleToFitInLookaheadElement(
-            this@AnimatedContentRootScope,
-            contentScale,
-            alignment
-        )
-    )
-
-    @ExperimentalAnimationApi
-    override fun scaleOutToFitContainer(
-        alignment: Alignment,
-        contentScale: ContentScale
-    ): ExitTransition = ExitTransition(
-        ScaleToFitTransitionKey,
-        ScaleToFitInLookaheadElement(
-            this@AnimatedContentRootScope,
-            contentScale,
-            alignment
-        )
-    )
-
     internal var measuredSize: IntSize by mutableStateOf(IntSize.Zero)
-    internal val targetSizeMap = mutableMapOf<S, MutableState<IntSize>>()
+    internal val targetSizeMap = mutableMapOf<S, State<IntSize>>()
     internal var animatedSize: State<IntSize>? = null
-
     // Current size of the container. If there's any size animation, the current size will be
     // read from the animation value, otherwise we'll use the current
-    internal val currentSize: IntSize
+    private val currentSize: IntSize
         get() = animatedSize?.value ?: measuredSize
-
-    internal val targetSize: IntSize
-        get() = requireNotNull(targetSizeMap[targetState]) {
-            "Error: Target size for AnimatedContent has not been set."
-        }.value
-
     @Suppress("ComposableModifierFactory", "ModifierFactoryExtensionFunction")
     @Composable
     internal fun createSizeAnimationModifier(
@@ -668,47 +540,67 @@
         val sizeTransform = rememberUpdatedState(contentTransform.sizeTransform)
         if (transition.currentState == transition.targetState) {
             shouldAnimateSize = false
-        } else if (sizeTransform.value != null) {
-            shouldAnimateSize = true
+        } else {
+            // TODO: CurrentSize is only relevant to enter/exit transition, not so much for sizeAnim
+            if (sizeTransform.value != null) {
+                shouldAnimateSize = true
+            }
         }
-
         return if (shouldAnimateSize) {
-            val sizeAnimation =
-                transition.createDeferredAnimation(IntSize.VectorConverter, "sizeTransform")
+            val sizeAnimation = transition.createDeferredAnimation(IntSize.VectorConverter)
             remember(sizeAnimation) {
                 (if (sizeTransform.value?.clip == false) Modifier else Modifier.clipToBounds())
-                    .then(
-                        SizeModifierInLookaheadElement(
-                            this, sizeAnimation, sizeTransform
-                        )
-                    )
+                    .then(SizeModifier(sizeAnimation, sizeTransform))
             }
         } else {
             animatedSize = null
             Modifier
         }
     }
-
     // This helps track the target measurable without affecting the placement order. Target
     // measurable needs to be measured first but placed last.
-    internal data class ChildData<T>(var targetState: T) : ParentDataModifier {
+    internal data class ChildData(var isTarget: Boolean) : ParentDataModifier {
         override fun Density.modifyParentData(parentData: Any?): Any {
             return this@ChildData
         }
     }
+    private inner class SizeModifier(
+        val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>,
+        val sizeTransform: State<SizeTransform?>,
+    ) : LayoutModifierWithPassThroughIntrinsics() {
+        override fun MeasureScope.measure(
+            measurable: Measurable,
+            constraints: Constraints
+        ): MeasureResult {
+            val placeable = measurable.measure(constraints)
+            val size = sizeAnimation.animate(
+                transitionSpec = {
+                    val initial = targetSizeMap[initialState]?.value ?: IntSize.Zero
+                    val target = targetSizeMap[targetState]?.value ?: IntSize.Zero
+                    sizeTransform.value?.createAnimationSpec(initial, target) ?: spring()
+                }
+            ) {
+                targetSizeMap[it]?.value ?: IntSize.Zero
+            }
+            animatedSize = size
+            val offset = contentAlignment.align(
+                IntSize(placeable.width, placeable.height), size.value, LayoutDirection.Ltr
+            )
+            return layout(size.value.width, size.value.height) {
+                placeable.place(offset)
+            }
+        }
+    }
 }
-
 /**
  * Receiver scope for content lambda for AnimatedContent. In this scope,
  * [transition][AnimatedVisibilityScope.transition] can be used to observe the state of the
  * transition, or to add more enter/exit transition for the content.
  */
 sealed interface AnimatedContentScope : AnimatedVisibilityScope
-
 private class AnimatedContentScopeImpl internal constructor(
     animatedVisibilityScope: AnimatedVisibilityScope
 ) : AnimatedContentScope, AnimatedVisibilityScope by animatedVisibilityScope
-
 /**
  * [AnimatedContent] is a container that automatically animates its content when
  * [Transition.targetState] changes. Its [content] for different target states is defined in a
@@ -772,268 +664,145 @@
     content: @Composable() AnimatedContentScope.(targetState: S) -> Unit
 ) {
     val layoutDirection = LocalLayoutDirection.current
-    val coroutineScope = rememberCoroutineScope()
-    LookaheadScope {
-        val rootScope = remember(this@AnimatedContent) {
-            AnimatedContentRootScope(
-                this@AnimatedContent, this@LookaheadScope,
-                coroutineScope, contentAlignment, layoutDirection
-            )
-        }
-        val currentlyVisible = remember(this) { mutableStateListOf(currentState) }
-        val contentMap = remember(this) { mutableMapOf<S, @Composable() () -> Unit>() }
-        val constraintsMap = remember { mutableMapOf<S, Constraints>() }
-
-        // This is needed for tooling because it could change currentState directly,
-        // as opposed to changing target only. When that happens we need to clear all the
-        // visible content and only display the content for the new current state and target state.
-        if (!currentlyVisible.contains(currentState)) {
+    val rootScope = remember(this) {
+        AnimatedContentTransitionScopeImpl(this, contentAlignment, layoutDirection)
+    }
+    // TODO: remove screen as soon as they are animated out
+    val currentlyVisible = remember(this) { mutableStateListOf(currentState) }
+    val contentMap = remember(this) { mutableMapOf<S, @Composable() () -> Unit>() }
+    // This is needed for tooling because it could change currentState directly,
+    // as opposed to changing target only. When that happens we need to clear all the
+    // visible content and only display the content for the new current state and target state.
+    if (!currentlyVisible.contains(currentState)) {
+        currentlyVisible.clear()
+        currentlyVisible.add(currentState)
+    }
+    if (currentState == targetState) {
+        if (currentlyVisible.size != 1 || currentlyVisible[0] != currentState) {
             currentlyVisible.clear()
             currentlyVisible.add(currentState)
         }
-
-        if (currentState == targetState) {
-            if (currentlyVisible.size != 1 || currentlyVisible[0] != currentState) {
-                currentlyVisible.clear()
-                currentlyVisible.add(currentState)
-            }
-            if (contentMap.size != 1 || contentMap.containsKey(currentState)) {
-                contentMap.clear()
-            }
-            val targetConstraints = constraintsMap[targetState]
-            constraintsMap.clear()
-            targetConstraints?.let { constraintsMap[targetState] = it }
-            // TODO: Do we want to support changing contentAlignment amid animation?
-            rootScope.contentAlignment = contentAlignment
-            rootScope.layoutDirection = layoutDirection
-        } else if (!currentlyVisible.contains(targetState)) {
-            // Currently visible list always keeps the targetState at the end of the list, unless
-            // it's already in the list in the case of interruption. This makes the composable
-            // associated with the targetState get placed last, so the target composable will be
-            // displayed on top of content associated with other states, unless zIndex is specified.
-            // Replace the target with the same key if any.
-            val id = currentlyVisible.indexOfFirst { contentKey(it) == contentKey(targetState) }
-            if (id == -1) {
-                currentlyVisible.add(targetState)
-            } else {
-                currentlyVisible[id] = targetState
-            }
-        }
-        if (!contentMap.containsKey(targetState) || !contentMap.containsKey(currentState)) {
+        if (contentMap.size != 1 || contentMap.containsKey(currentState)) {
             contentMap.clear()
-            currentlyVisible.fastForEach { stateForContent ->
-                contentMap[stateForContent] = {
-                    // Only update content transform when enter/exit _direction_ changes.
-                    val contentTransform = remember(stateForContent == targetState) {
-                        rootScope.transitionSpec()
-                    }
-                    PopulateContentFor(
-                        stateForContent, rootScope, contentTransform, currentlyVisible, content
-                    )
-                }
-            }
         }
-        val contentTransform = remember(rootScope, segment) { transitionSpec(rootScope) }
-        val sizeModifier = rootScope.createSizeAnimationModifier(contentTransform)
-        Layout(
-            modifier = modifier
-                .layout { measurable, constraints ->
-                    val placeable = measurable.measure(constraints)
-                    layout(placeable.width, placeable.height) {
-                        coordinates?.let {
-                            if (isLookingAhead) {
-                                rootScope.rootLookaheadCoords = it
-                            } else {
-                                rootScope.rootCoords = it
+        // TODO: Do we want to support changing contentAlignment amid animation?
+        rootScope.contentAlignment = contentAlignment
+        rootScope.layoutDirection = layoutDirection
+    }
+    // Currently visible list always keeps the targetState at the end of the list, unless it's
+    // already in the list in the case of interruption. This makes the composable associated with
+    // the targetState get placed last, so the target composable will be displayed on top of
+    // content associated with other states, unless zIndex is specified.
+    if (currentState != targetState && !currentlyVisible.contains(targetState)) {
+        // Replace the target with the same key if any
+        val id = currentlyVisible.indexOfFirst { contentKey(it) == contentKey(targetState) }
+        if (id == -1) {
+            currentlyVisible.add(targetState)
+        } else {
+            currentlyVisible[id] = targetState
+        }
+    }
+    if (!contentMap.containsKey(targetState) || !contentMap.containsKey(currentState)) {
+        contentMap.clear()
+        currentlyVisible.fastForEach { stateForContent ->
+            contentMap[stateForContent] = {
+                val specOnEnter = remember { transitionSpec(rootScope) }
+                // NOTE: enter and exit for this AnimatedVisibility will be using different spec,
+                // naturally.
+                val exit =
+                    remember(segment.targetState == stateForContent) {
+                        if (segment.targetState == stateForContent) {
+                            ExitTransition.None
+                        } else {
+                            rootScope.transitionSpec().initialContentExit
+                        }
+                    }
+                val childData = remember {
+                    AnimatedContentTransitionScopeImpl.ChildData(stateForContent == targetState)
+                }
+                // TODO: Will need a custom impl of this to: 1) get the signal for when
+                // the animation is finished, 2) get the target size properly
+                AnimatedEnterExitImpl(
+                    this,
+                    { it == stateForContent },
+                    enter = specOnEnter.targetContentEnter,
+                    exit = exit,
+                    modifier = Modifier
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
+                            layout(placeable.width, placeable.height) {
+                                placeable.place(0, 0, zIndex = specOnEnter.targetContentZIndex)
                             }
                         }
-                        placeable.place(0, 0)
+                        .then(childData.apply { isTarget = stateForContent == targetState }),
+                    shouldDisposeBlock = { currentState, targetState ->
+                        currentState == EnterExitState.PostExit &&
+                            targetState == EnterExitState.PostExit &&
+                            !exit.data.hold
+                    }
+                ) {
+                    // TODO: Should Transition.AnimatedVisibility have an end listener?
+                    DisposableEffect(this) {
+                        onDispose {
+                            currentlyVisible.remove(stateForContent)
+                            rootScope.targetSizeMap.remove(stateForContent)
+                        }
+                    }
+                    rootScope.targetSizeMap[stateForContent] =
+                        (this as AnimatedVisibilityScopeImpl).targetSize
+                    with(remember { AnimatedContentScopeImpl(this) }) {
+                        content(stateForContent)
                     }
                 }
-                .then(sizeModifier),
-            content = {
-                currentlyVisible.fastForEach {
-                    key(contentKey(it)) { contentMap[it]?.invoke() }
-                }
-            },
-            measurePolicy = remember {
-                AnimatedContentMeasurePolicy(
-                    rootScope, constraintsMap
-                )
             }
-        )
-    }
-}
-
-/**
- * Creates content for a specific state based on the current Transition, enter/exit and the content
- * lookup lambda.
- */
-@Composable
-private inline fun <S> Transition<S>.PopulateContentFor(
-    stateForContent: S,
-    rootScope: AnimatedContentRootScope<S>,
-    contentTransform: ContentTransform,
-    currentlyVisible: SnapshotStateList<S>,
-    crossinline content: @Composable() AnimatedContentScope.(targetState: S) -> Unit
-) {
-    var activeEnter by remember { mutableStateOf(contentTransform.targetContentEnter) }
-    var activeExit by remember { mutableStateOf(ExitTransition.None) }
-    val targetZIndex = remember { contentTransform.targetContentZIndex }
-
-    val isEntering = targetState == stateForContent
-    if (targetState == currentState) {
-        // Transition finished, reset active enter & exit.
-        activeEnter = EnterTransition.None
-        activeExit = ExitTransition.None
-    } else if (isEntering) {
-        // If the previous enter transition never finishes when multiple
-        // interruptions happen, avoid adding new enter transitions for simplicity.
-        if (activeEnter == EnterTransition.None)
-            activeEnter += contentTransform.targetContentEnter
-    } else {
-        // If the previous exit transition never finishes when multiple
-        // interruptions happen, avoid adding new enter transitions for simplicity.
-        if (activeExit == ExitTransition.None) {
-            activeExit += contentTransform.initialContentExit
         }
     }
-
-    val childData = remember { AnimatedContentRootScope.ChildData(stateForContent) }
-    AnimatedEnterExitImpl(
-        this,
-        { it == stateForContent },
-        enter = activeEnter,
-        exit = activeExit,
-        modifier = Modifier
-            .layout { measurable, constraints ->
-                val placeable = measurable.measure(constraints)
-                layout(placeable.width, placeable.height) {
-                    placeable.place(0, 0, zIndex = targetZIndex)
+    val contentTransform = remember(rootScope, segment) { transitionSpec(rootScope) }
+    val sizeModifier = rootScope.createSizeAnimationModifier(contentTransform)
+    Layout(
+        modifier = modifier.then(sizeModifier),
+        content = {
+            currentlyVisible.fastForEach {
+                key(contentKey(it)) {
+                    contentMap[it]?.invoke()
                 }
             }
-            .then(childData)
-            .then(
-                if (isEntering) {
-                    activeEnter[ScaleToFitTransitionKey]
-                        ?: activeExit[ScaleToFitTransitionKey] ?: Modifier
-                } else {
-                    activeExit[ScaleToFitTransitionKey]
-                        ?: activeEnter[ScaleToFitTransitionKey] ?: Modifier
-                }
-            ),
-        shouldDisposeBlock = { currentState, targetState ->
-            currentState == EnterExitState.PostExit &&
-                targetState == EnterExitState.PostExit && !activeExit.data.hold
         },
-        onLookaheadMeasured = {
-            if (isEntering) rootScope.targetSizeMap.getOrPut(targetState) {
-                mutableStateOf(it)
-            }.value = it
-        }
-    ) {
-        // TODO: Should Transition.AnimatedVisibility have an end listener?
-        DisposableEffect(this) {
-            onDispose {
-                currentlyVisible.remove(stateForContent)
-                rootScope.targetSizeMap.remove(stateForContent)
-            }
-        }
-        with(remember { AnimatedContentScopeImpl(this) }) {
-            content(stateForContent)
-        }
-    }
+        measurePolicy = remember { AnimatedContentMeasurePolicy(rootScope) }
+    )
 }
-
-/**
- * This measure policy returns the target content size in the lookahead pass, and the max width
- * and height needed for all contents to fit during the main measure pass.
- *
- * The measure policy will measure all children with lookahead constraints. For outgoing content,
- * we will use the constraints recorded before the content started to exit. This enables the
- * outgoing content to not change constraints on its way out.
- */
-@Suppress("UNCHECKED_CAST")
-private class AnimatedContentMeasurePolicy<S>(
-    val rootScope: AnimatedContentRootScope<S>,
-    val constraintsMap: MutableMap<S, Constraints>
-) : MeasurePolicy {
+private class AnimatedContentMeasurePolicy(val rootScope: AnimatedContentTransitionScopeImpl<*>) :
+    MeasurePolicy {
     override fun MeasureScope.measure(
         measurables: List<Measurable>,
         constraints: Constraints
     ): MeasureResult {
         val placeables = arrayOfNulls<Placeable>(measurables.size)
         // Measure the target composable first (but place it on top unless zIndex is specified)
-        val targetState = rootScope.targetState
         measurables.fastForEachIndexed { index, measurable ->
-            if ((measurable.parentData as? AnimatedContentRootScope.ChildData<*>)
-                    ?.targetState == targetState
+            if ((measurable.parentData as? AnimatedContentTransitionScopeImpl.ChildData)
+                    ?.isTarget == true
             ) {
-                // Record lookahead constraints and always use it to measure target content.
-                val lookaheadConstraints = if (isLookingAhead) {
-                    constraintsMap[targetState] = constraints
-                    constraints
-                } else {
-                    requireNotNull(constraintsMap[targetState]) {
-                        "Lookahead pass was never done for target content."
-                    }
-                }
-                placeables[index] = measurable.measure(lookaheadConstraints)
+                placeables[index] = measurable.measure(constraints)
             }
         }
-        // If no content is defined for target state, set the target size to zero
-        rootScope.targetSizeMap.getOrPut(targetState) { mutableStateOf(IntSize.Zero) }
-
-        val initialState = rootScope.initialState
         // Measure the non-target composables after target, since these have no impact on
         // container size in the size animation.
         measurables.fastForEachIndexed { index, measurable ->
-            val stateForContent =
-                (measurable.parentData as? AnimatedContentRootScope.ChildData<*>)
-                    ?.targetState
             if (placeables[index] == null) {
-                val lookaheadConstraints =
-                    constraintsMap[stateForContent] ?: if (isLookingAhead) {
-                        constraintsMap[stateForContent as S] = constraints
-                        constraints
-                    } else {
-                        requireNotNull(constraintsMap[stateForContent as S]) {
-                            "Error: Lookahead pass never happened for state: $stateForContent"
-                        }
-                    }
-                placeables[index] = measurable.measure(lookaheadConstraints).also {
-                    // If the initial state size isn't in the map, add it. This could be possible
-                    // when the initial state is specified to be different than target state upon
-                    // entering composition.
-                    if (stateForContent == initialState &&
-                        isLookingAhead &&
-                        !rootScope.targetSizeMap.containsKey(initialState)
-                    ) {
-                        rootScope.targetSizeMap[initialState] =
-                            mutableStateOf(IntSize(it.width, it.height))
-                    }
-                }
+                placeables[index] = measurable.measure(constraints)
             }
         }
-        val lookaheadSize = rootScope.targetSizeMap[targetState]!!.value
-        val measuredWidth = if (isLookingAhead) {
-            lookaheadSize.width
-        } else {
-            placeables.maxByOrNull { it?.width ?: 0 }?.width ?: 0
-        }
-        val measuredHeight = if (isLookingAhead) {
-            lookaheadSize.height
-        } else {
-            placeables.maxByOrNull { it?.height ?: 0 }?.height ?: 0
-        }
-        rootScope.measuredSize = IntSize(measuredWidth, measuredHeight)
+        val maxWidth: Int = placeables.maxByOrNull { it?.width ?: 0 }?.width ?: 0
+        val maxHeight = placeables.maxByOrNull { it?.height ?: 0 }?.height ?: 0
+        rootScope.measuredSize = IntSize(maxWidth, maxHeight)
         // Position the children.
-        return layout(measuredWidth, measuredHeight) {
+        return layout(maxWidth, maxHeight) {
             placeables.forEach { placeable ->
                 placeable?.let {
                     val offset = rootScope.contentAlignment.align(
                         IntSize(it.width, it.height),
-                        IntSize(measuredWidth, measuredHeight),
+                        IntSize(maxWidth, maxHeight),
                         LayoutDirection.Ltr
                     )
                     it.place(offset.x, offset.y)
@@ -1041,178 +810,20 @@
             }
         }
     }
-
     override fun IntrinsicMeasureScope.minIntrinsicWidth(
         measurables: List<IntrinsicMeasurable>,
         height: Int
     ) = measurables.fastMaxOfOrNull { it.minIntrinsicWidth(height) } ?: 0
-
     override fun IntrinsicMeasureScope.minIntrinsicHeight(
         measurables: List<IntrinsicMeasurable>,
         width: Int
     ) = measurables.fastMaxOfOrNull { it.minIntrinsicHeight(width) } ?: 0
-
     override fun IntrinsicMeasureScope.maxIntrinsicWidth(
         measurables: List<IntrinsicMeasurable>,
         height: Int
     ) = measurables.fastMaxOfOrNull { it.maxIntrinsicWidth(height) } ?: 0
-
     override fun IntrinsicMeasureScope.maxIntrinsicHeight(
         measurables: List<IntrinsicMeasurable>,
         width: Int
     ) = measurables.fastMaxOfOrNull { it.maxIntrinsicHeight(width) } ?: 0
 }
-
-private class SizeModifierInLookaheadNode<S>(
-    var rootScope: AnimatedContentRootScope<S>,
-    var sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>,
-    var sizeTransform: State<SizeTransform?>,
-) : LayoutModifierNodeWithPassThroughIntrinsics() {
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints,
-    ): MeasureResult {
-        val placeable = measurable.measure(constraints)
-        val size = if (isLookingAhead) {
-            val targetSize = IntSize(placeable.width, placeable.height)
-            // lookahead pass
-            rootScope.animatedSize = sizeAnimation.animate(
-                transitionSpec = {
-                    val initial = rootScope.targetSizeMap[initialState]?.value ?: IntSize.Zero
-                    val target = rootScope.targetSizeMap[targetState]?.value ?: IntSize.Zero
-                    sizeTransform.value?.createAnimationSpec(initial, target) ?: spring()
-                }
-            ) {
-                rootScope.targetSizeMap[it]?.value ?: IntSize.Zero
-            }
-            targetSize
-        } else {
-            rootScope.animatedSize!!.value
-        }
-        val offset = rootScope.contentAlignment.align(
-            IntSize(placeable.width, placeable.height), size, LayoutDirection.Ltr
-        )
-        return layout(size.width, size.height) {
-            placeable.place(offset)
-        }
-    }
-}
-
-private data class SizeModifierInLookaheadElement<S>(
-    val rootScope: AnimatedContentRootScope<S>,
-    val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>,
-    val sizeTransform: State<SizeTransform?>,
-) : ModifierNodeElement<SizeModifierInLookaheadNode<S>>() {
-    override fun create(): SizeModifierInLookaheadNode<S> {
-        return SizeModifierInLookaheadNode(rootScope, sizeAnimation, sizeTransform)
-    }
-
-    override fun update(node: SizeModifierInLookaheadNode<S>) {
-        node.rootScope = rootScope
-        node.sizeTransform = sizeTransform
-        node.sizeAnimation = sizeAnimation
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        name = "sizeTransform"
-        properties["sizeTransform"] = sizeTransform
-        properties["sizeAnimation"] = sizeAnimation
-    }
-}
-
-private data class ScaleToFitInLookaheadElement(
-    val rootScope: AnimatedContentRootScope<*>,
-    val contentScale: ContentScale,
-    val alignment: Alignment
-) : ModifierNodeElement<ScaleToFitInLookaheadNode>() {
-    override fun create(): ScaleToFitInLookaheadNode =
-        ScaleToFitInLookaheadNode(rootScope, contentScale, alignment)
-
-    override fun update(node: ScaleToFitInLookaheadNode) {
-        node.rootScope = rootScope
-        node.contentScale = contentScale
-        node.alignment = alignment
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        name = "scaleToFit"
-        properties["rootScope"] = rootScope
-        properties["scale"] = contentScale
-        properties["alignment"] = alignment
-    }
-}
-
-/**
- * Creates a Modifier Node to: 1) measure the layout with lookahead constraints, 2) scale the
- * resulting (potentially unfitting) layout based on the resizing container using the given
- * [contentScale] lambda.
- *
- * This node is designed to work in a lookahead scope, therefore it anticipates lookahead pass
- * before actual measure pass.
- */
-private class ScaleToFitInLookaheadNode(
-    var rootScope: AnimatedContentRootScope<*>,
-    var contentScale: ContentScale,
-    var alignment: Alignment
-) : Modifier.Node(), LayoutModifierNode {
-    private var lookaheadConstraints: Constraints = Constraints()
-        set(value) {
-            lookaheadPassOccurred = true
-            field = value
-        }
-        get() {
-            require(lookaheadPassOccurred) {
-                "Error: Attempting to read lookahead constraints before lookahead pass."
-            }
-            return field
-        }
-    private var lookaheadPassOccurred = false
-
-    override fun onDetach() {
-        super.onDetach()
-        lookaheadPassOccurred = false
-    }
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        if (isLookingAhead) lookaheadConstraints = constraints
-        // Measure with lookahead constraints.
-        val placeable = measurable.measure(lookaheadConstraints)
-        val contentSize = IntSize(placeable.width, placeable.height)
-        val sizeToReport = if (isLookingAhead) {
-            // report size of the target content, as that's what the content will be scaled to.
-            rootScope.targetSize
-        } else {
-            // report current animated size && scale based on that and full size
-            rootScope.currentSize
-        }
-        val resolvedScale =
-            if (contentSize.width == 0 || contentSize.height == 0) {
-                ScaleFactor(1f, 1f)
-            } else
-                contentScale.computeScaleFactor(contentSize.toSize(), sizeToReport.toSize())
-        return layout(sizeToReport.width, sizeToReport.height) {
-            val (x, y) = alignment.align(
-                IntSize(
-                    (contentSize.width * resolvedScale.scaleX).fastRoundToInt(),
-                    (contentSize.height * resolvedScale.scaleY).fastRoundToInt()
-                ),
-                sizeToReport,
-                layoutDirection
-            )
-            placeable.placeWithLayer(x, y) {
-                scaleX = resolvedScale.scaleX
-                scaleY = resolvedScale.scaleY
-                transformOrigin = TransformOrigin(0f, 0f)
-            }
-        }
-    }
-}
-
-/**
- * Fixed key to read customization out of EnterTransition and ExitTransition.
- */
-private val ScaleToFitTransitionKey = Any()
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
index 190bd74..bbb3dc3 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
@@ -1814,4 +1814,35 @@
     ) {
         assertFalse(it.contains("INVOKESTATIC kotlin/jvm/internal/Reflection.property0 (Lkotlin/jvm/internal/PropertyReference0;)Lkotlin/reflect/KProperty0;"))
     }
+
+    @Test
+    fun testComposableAdaptedFunctionReference() = validateBytecode(
+        """
+            class ScrollState {
+                fun test(index: Int, default: Int = 0): Int = 0
+                fun testExact(index: Int): Int = 0
+            }
+            fun scrollState(): ScrollState = TODO()
+
+            @Composable fun rememberFooInline() = fooInline(scrollState()::test)
+            @Composable fun rememberFoo() = foo(scrollState()::test)
+            @Composable fun rememberFooExactInline() = fooInline(scrollState()::testExact)
+            @Composable fun rememberFooExact() = foo(scrollState()::testExact)
+
+            @Composable
+            inline fun fooInline(block: (Int) -> Int) = block(0)
+
+            @Composable
+            fun foo(block: (Int) -> Int) = block(0)
+        """,
+        validate = {
+            // Validate that function references in inline calls are actually getting inlined
+            assertFalse(
+                it.contains("""INVOKESPECIAL Test_0Kt${'$'}rememberFooInline$1$1.<init> (Ljava/lang/Object;)V""")
+            )
+            assertFalse(
+                it.contains("""INVOKESPECIAL Test_0Kt${'$'}rememberFooExactInline$1$1.<init> (Ljava/lang/Object;)V""")
+            )
+        }
+    )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
index b8dceda..4164c6d 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
@@ -652,4 +652,28 @@
             }
         """
     )
+
+    @Test
+    fun testAdaptedFunctionRef() = verifyGoldenComposeIrTransform(
+        """
+            import androidx.compose.runtime.Composable
+
+            class ScrollState {
+                fun test(index: Int, default: Int = 0): Int = 0
+                fun testExact(index: Int): Int = 0
+            }
+            fun scrollState(): ScrollState = TODO()
+
+            @Composable fun rememberFooInline() = fooInline(scrollState()::test)
+            @Composable fun rememberFoo() = foo(scrollState()::test)
+            @Composable fun rememberFooExactInline() = fooInline(scrollState()::testExact)
+            @Composable fun rememberFooExact() = foo(scrollState()::testExact)
+
+            @Composable
+            inline fun fooInline(block: (Int) -> Int) = block(0)
+
+            @Composable
+            fun foo(block: (Int) -> Int) = block(0)
+        """,
+    )
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testAdaptedFunctionRef\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testAdaptedFunctionRef\133useFir = false\135.txt"
new file mode 100644
index 0000000..5785249
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testAdaptedFunctionRef\133useFir = false\135.txt"
@@ -0,0 +1,149 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+
+class ScrollState {
+    fun test(index: Int, default: Int = 0): Int = 0
+    fun testExact(index: Int): Int = 0
+}
+fun scrollState(): ScrollState = TODO()
+
+@Composable fun rememberFooInline() = fooInline(scrollState()::test)
+@Composable fun rememberFoo() = foo(scrollState()::test)
+@Composable fun rememberFooExactInline() = fooInline(scrollState()::testExact)
+@Composable fun rememberFooExact() = foo(scrollState()::testExact)
+
+@Composable
+inline fun fooInline(block: (Int) -> Int) = block(0)
+
+@Composable
+fun foo(block: (Int) -> Int) = block(0)
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@StabilityInferred(parameters = 1)
+class ScrollState {
+  fun test(index: Int, default: Int = 0): Int {
+    return 0
+  }
+  fun testExact(index: Int): Int {
+    return 0
+  }
+  static val %stable: Int = 0
+}
+fun scrollState(): ScrollState {
+  return TODO()
+}
+@Composable
+fun rememberFooInline(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFooInline)<fooInl...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = fooInline(<block>{
+    fun ScrollState.test(p0: Int): Int {
+      val tmp0_return = receiver.test(
+        index = p0
+      )
+      tmp0_return
+    }
+    scrollState()::test
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun rememberFoo(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFoo)<scroll...>,<foo(sc...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = foo(<block>{
+    val tmp0 = scrollState()
+    %composer.startReplaceableGroup(<>)
+    sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+    val tmp1_group = %composer.cache(%composer.changed(tmp0)) {
+      fun ScrollState.test(p0: Int): Int {
+        receiver.test(
+          index = p0
+        )
+      }
+      tmp0::test
+    }
+    %composer.endReplaceableGroup()
+    tmp1_group
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun rememberFooExactInline(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFooExactInline)<fooInl...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = fooInline(scrollState()::testExact, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun rememberFooExact(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFooExact)<scroll...>,<foo(sc...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = foo(<block>{
+    val tmp0 = scrollState()
+    %composer.startReplaceableGroup(<>)
+    sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+    val tmp1_group = %composer.cache(%composer.changed(tmp0)) {
+      tmp0::testExact
+    }
+    %composer.endReplaceableGroup()
+    tmp1_group
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun fooInline(block: Function1<Int, Int>, %composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "CC(fooInline):Test.kt")
+  val tmp0 = block(0)
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun foo(block: Function1<Int, Int>, %composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(foo):Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = block(0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testAdaptedFunctionRef\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testAdaptedFunctionRef\133useFir = true\135.txt"
new file mode 100644
index 0000000..ab1b9ff
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LambdaMemoizationTransformTests/testAdaptedFunctionRef\133useFir = true\135.txt"
@@ -0,0 +1,149 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.Composable
+
+class ScrollState {
+    fun test(index: Int, default: Int = 0): Int = 0
+    fun testExact(index: Int): Int = 0
+}
+fun scrollState(): ScrollState = TODO()
+
+@Composable fun rememberFooInline() = fooInline(scrollState()::test)
+@Composable fun rememberFoo() = foo(scrollState()::test)
+@Composable fun rememberFooExactInline() = fooInline(scrollState()::testExact)
+@Composable fun rememberFooExact() = foo(scrollState()::testExact)
+
+@Composable
+inline fun fooInline(block: (Int) -> Int) = block(0)
+
+@Composable
+fun foo(block: (Int) -> Int) = block(0)
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@StabilityInferred(parameters = 1)
+class ScrollState {
+  fun test(index: Int, default: Int = 0): Int {
+    return 0
+  }
+  fun testExact(index: Int): Int {
+    return 0
+  }
+  static val %stable: Int = 0
+}
+fun scrollState(): ScrollState {
+  return TODO()
+}
+@Composable
+fun rememberFooInline(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFooInline)<fooInl...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = fooInline(<block>{
+    fun ScrollState.test(p0: Int): Int {
+      val tmp0_return = receiver.test(
+        index = p0
+      )
+      tmp0_return
+    }
+    scrollState()::test
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun rememberFoo(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFoo)<test>,<foo(sc...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = foo(<block>{
+    val tmp0 = scrollState()
+    %composer.startReplaceableGroup(<>)
+    sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+    val tmp1_group = %composer.cache(%composer.changed(tmp0)) {
+      fun ScrollState.test(p0: Int): Int {
+        receiver.test(
+          index = p0
+        )
+      }
+      tmp0::test
+    }
+    %composer.endReplaceableGroup()
+    tmp1_group
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun rememberFooExactInline(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFooExactInline)<fooInl...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = fooInline(scrollState()::testExact, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun rememberFooExact(%composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(rememberFooExact)<testEx...>,<foo(sc...>:Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = foo(<block>{
+    val tmp0 = scrollState()
+    %composer.startReplaceableGroup(<>)
+    sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+    val tmp1_group = %composer.cache(%composer.changed(tmp0)) {
+      tmp0::testExact
+    }
+    %composer.endReplaceableGroup()
+    tmp1_group
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun fooInline(block: Function1<Int, Int>, %composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "CC(fooInline):Test.kt")
+  val tmp0 = block(0)
+  %composer.endReplaceableGroup()
+  return tmp0
+}
+@Composable
+fun foo(block: Function1<Int, Int>, %composer: Composer?, %changed: Int): Int {
+  %composer.startReplaceableGroup(<>)
+  sourceInformation(%composer, "C(foo):Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  val tmp0 = block(0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endReplaceableGroup()
+  return tmp0
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = false\135.txt"
index 05ba6c9..e276214 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = false\135.txt"
@@ -30,9 +30,12 @@
     used(<block>{
       %composer.startReplaceableGroup(<>)
       sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
-      val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100) {
-        effect()
-      }
+      val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100, <block>{
+        fun effect(): Int {
+          effect()
+        }
+        ::effect
+      })
       %composer.endReplaceableGroup()
       tmp0_group
     })
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = true\135.txt"
index 05ba6c9..e276214 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testRememberAdaptedFunctionReference\133useFir = true\135.txt"
@@ -30,9 +30,12 @@
     used(<block>{
       %composer.startReplaceableGroup(<>)
       sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
-      val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100) {
-        effect()
-      }
+      val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100, <block>{
+        fun effect(): Int {
+          effect()
+        }
+        ::effect
+      })
       %composer.endReplaceableGroup()
       tmp0_group
     })
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index ff49298..90acac0 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -62,7 +62,7 @@
 import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
 import org.jetbrains.kotlin.ir.declarations.IrValueParameter
 import org.jetbrains.kotlin.ir.declarations.IrVariable
-import org.jetbrains.kotlin.ir.declarations.copyAttributes
+import org.jetbrains.kotlin.ir.expressions.IrBlock
 import org.jetbrains.kotlin.ir.expressions.IrCall
 import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
 import org.jetbrains.kotlin.ir.expressions.IrExpression
@@ -74,7 +74,6 @@
 import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
 import org.jetbrains.kotlin.ir.expressions.IrValueAccessExpression
 import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
-import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
@@ -458,17 +457,64 @@
         return super.visitValueAccess(expression)
     }
 
+    override fun visitBlock(expression: IrBlock): IrExpression {
+        val result = super.visitBlock(expression)
+
+        if (result is IrBlock && result.origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE) {
+            if (inlineLambdaInfo.isInlineFunctionExpression(expression)) {
+                // Do not memoize function references for inline lambdas
+                return result
+            }
+
+            val functionReference = result.statements.last()
+            if (functionReference !is IrFunctionReference) {
+                //  Do not memoize if the expected shape doesn't match.
+                return result
+            }
+
+            return rememberFunctionReference(functionReference, expression)
+        }
+
+        return result
+    }
+
     // Memoize the instance created by using the :: operator
     override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
+        val result = super.visitFunctionReference(expression)
+
+        if (
+            inlineLambdaInfo.isInlineFunctionExpression(expression) ||
+                inlineLambdaInfo.isInlineLambda(expression.symbol.owner)
+        ) {
+            // Do not memoize function references used in inline parameters.
+            return result
+        }
+
+        if (expression.symbol.owner.origin == IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE) {
+            // Adapted function reference (inexact function signature match) is handled in block
+            return result
+        }
+
+        if (result !is IrFunctionReference) {
+            // Do not memoize if the shape doesn't match
+            return result
+        }
+
+        return rememberFunctionReference(result, result)
+    }
+
+    private fun rememberFunctionReference(
+        reference: IrFunctionReference,
+        expression: IrExpression
+    ): IrExpression {
         // Get the local captures for local function ref, to make sure we invalidate memoized
         // reference if its capture is different.
-        val localCaptures = if (expression.symbol.owner.isLocal) {
-            declarationContextStack.recordLocalCapture(expression.symbol.owner)
+        val localCaptures = if (reference.symbol.owner.isLocal) {
+            declarationContextStack.recordLocalCapture(reference.symbol.owner)
         } else {
             null
         }
-        val result = super.visitFunctionReference(expression)
-        val functionContext = currentFunctionContext ?: return result
+        val functionContext = currentFunctionContext ?: return expression
 
         // The syntax <expr>::<method>(<params>) and ::<function>(<params>) is reserved for
         // future use. Revisit implementation if this syntax is as a curry syntax in the future.
@@ -476,27 +522,27 @@
         // receivers are treated below.
 
         // Do not attempt memoization if the referenced function has context receivers.
-        if (expression.symbol.owner.contextReceiverParametersCount > 0) {
-            return result
+        if (reference.symbol.owner.contextReceiverParametersCount > 0) {
+            return expression
         }
 
         // Do not attempt memoization if value parameters are not null. This is to guard against
         // unexpected IR shapes.
-        for (i in 0 until expression.valueArgumentsCount) {
-            if (expression.getValueArgument(i) != null) {
-                return result
+        for (i in 0 until reference.valueArgumentsCount) {
+            if (reference.getValueArgument(i) != null) {
+                return expression
             }
         }
 
         if (functionContext.canRemember) {
             // Memoize the reference for <expr>::<method>
-            val dispatchReceiver = expression.dispatchReceiver
-            val extensionReceiver = expression.extensionReceiver
+            val dispatchReceiver = reference.dispatchReceiver
+            val extensionReceiver = reference.extensionReceiver
 
             val hasReceiver = dispatchReceiver != null || extensionReceiver != null
             val receiverIsStable =
                 dispatchReceiver.isNullOrStable() &&
-                extensionReceiver.isNullOrStable()
+                    extensionReceiver.isNullOrStable()
 
             val captures = mutableListOf<IrValueDeclaration>()
             if (localCaptures != null) {
@@ -526,28 +572,22 @@
                         tmp
                     }
 
+                    // Patch reference receiver in place
+                    reference.dispatchReceiver = tempDispatchReceiver?.let { irGet(it) }
+                    reference.extensionReceiver = tempExtensionReceiver?.let { irGet(it) }
+
                     +rememberExpression(
                         functionContext,
-                        IrFunctionReferenceImpl(
-                            startOffset,
-                            endOffset,
-                            expression.type,
-                            expression.symbol,
-                            expression.typeArgumentsCount,
-                            expression.valueArgumentsCount,
-                            expression.reflectionTarget
-                        ).copyAttributes(expression).apply {
-                            this.dispatchReceiver = tempDispatchReceiver?.let { irGet(it) }
-                            this.extensionReceiver = tempExtensionReceiver?.let { irGet(it) }
-                        },
+                        expression,
                         captures
                     )
                 }
             } else if (dispatchReceiver == null && extensionReceiver == null) {
-                return rememberExpression(functionContext, result, captures)
+                return rememberExpression(functionContext, expression, captures)
             }
         }
-        return result
+
+        return expression
     }
 
     override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt
index 852ae18..8c72dac 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrInlineReferenceLocator.kt
@@ -46,10 +46,14 @@
 
 class ComposeInlineLambdaLocator(private val context: IrPluginContext) {
     private val inlineLambdaToParameter = mutableMapOf<IrFunctionSymbol, IrValueParameter>()
+    private val inlineFunctionExpressions = mutableSetOf<IrExpression>()
 
     fun isInlineLambda(irFunction: IrFunction): Boolean =
         irFunction.symbol in inlineLambdaToParameter.keys
 
+    fun isInlineFunctionExpression(expression: IrExpression): Boolean =
+        expression in inlineFunctionExpressions
+
     fun preservesComposableScope(irFunction: IrFunction): Boolean =
         inlineLambdaToParameter[irFunction.symbol]?.let {
             !it.isCrossinline && !it.type.hasAnnotation(ComposeFqNames.DisallowComposableCalls)
@@ -66,7 +70,7 @@
                 declaration.acceptChildrenVoid(this)
                 val parent = declaration.parent as? IrFunction
                 if (parent?.isInlineFunctionCall(context) == true &&
-                    declaration.isInlineParameter()) {
+                    declaration.isInlinedFunction()) {
                     declaration.defaultValue?.expression?.unwrapLambda()?.let {
                         inlineLambdaToParameter[it] = declaration
                     }
@@ -78,8 +82,9 @@
                 val function = expression.symbol.owner
                 if (function.isInlineFunctionCall(context)) {
                     for (parameter in function.valueParameters) {
-                        if (parameter.isInlineParameter()) {
+                        if (parameter.isInlinedFunction()) {
                             expression.getValueArgument(parameter.index)
+                                ?.also { inlineFunctionExpressions += it }
                                 ?.unwrapLambda()
                                 ?.let { inlineLambdaToParameter[it] = parameter }
                         }
@@ -119,7 +124,7 @@
 
 // This is copied from JvmIrInlineUtils.kt in the Kotlin compiler, since we
 // need to check for synthetic composable functions.
-private fun IrValueParameter.isInlineParameter(): Boolean =
+private fun IrValueParameter.isInlinedFunction(): Boolean =
     index >= 0 && !isNoinline && (type.isFunction() || type.isSuspendFunction() ||
         type.isSyntheticComposableFunction()) &&
         // Parameters with default values are always nullable, so check the expression too.
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
index 9980397..0b0f561 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/IrSourcePrinter.kt
@@ -522,8 +522,7 @@
                 val param = symbol.owner.valueParameters[i]
                 val isLambda = arg is IrFunctionExpression ||
                     (arg is IrBlock &&
-                        (arg.origin == IrStatementOrigin.LAMBDA ||
-                            arg.origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE))
+                        (arg.origin == IrStatementOrigin.LAMBDA))
                 if (isLambda) {
                     arg.unwrapLambda()?.let {
                         returnTargetToCall[it] = this
@@ -881,7 +880,7 @@
                 lhs.print()
                 print("--")
             }
-            IrStatementOrigin.LAMBDA, IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE -> {
+            IrStatementOrigin.LAMBDA -> {
                 val function = expression.statements[0] as IrFunction
                 function.printAsLambda()
             }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt
index ed67719..9cc7e7a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt
@@ -922,6 +922,7 @@
             focusManager = LocalFocusManager.current
             TestMarqueeContent(
                 Modifier
+                    .focusable() // extra focusable for initial focus.
                     .basicMarqueeWithTestParams(animationMode = WhileFocused)
                     .focusRequester(focusRequester)
                     .focusable()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
index 56d0bec..4c72289 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
@@ -81,7 +81,9 @@
 import androidx.test.filters.LargeTest
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import org.junit.After
@@ -97,6 +99,11 @@
     @get:Rule
     val rule = createComposeRule()
 
+    private val InstanceOf = Correspondence.from<Any, KClass<*>>(
+        { obj, clazz -> clazz?.isInstance(obj) ?: false },
+        "is an instance of"
+    )
+
     @Before
     fun before() {
         isDebugInspectorInfoEnabled = true
@@ -1054,22 +1061,19 @@
         val focusRequester = FocusRequester()
         lateinit var focusManager: FocusManager
         lateinit var inputModeManager: InputModeManager
-        rule.setContent {
+        rule.setFocusableContent {
             scope = rememberCoroutineScope()
             focusManager = LocalFocusManager.current
             inputModeManager = LocalInputModeManager.current
-                Box {
-                    BasicText(
-                        "ClickableText",
-                        modifier = Modifier
-                            .testTag("myClickable")
-                            .focusRequester(focusRequester)
-                            .clickable(
-                                interactionSource = interactionSource,
-                                indication = null
-                            ) {}
-                    )
-                }
+            Box {
+                BasicText(
+                    "ClickableText",
+                    modifier = Modifier
+                        .testTag("myClickable")
+                        .focusRequester(focusRequester)
+                        .clickable(interactionSource = interactionSource, indication = null) {}
+                )
+            }
         }
         rule.runOnIdle {
             @OptIn(ExperimentalComposeUiApi::class)
@@ -1092,8 +1096,9 @@
 
         // Keyboard mode, so we should now be focused and see an interaction
         rule.runOnIdle {
-            assertThat(interactions).hasSize(1)
-            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+            assertThat(interactions)
+                .comparingElementsUsing(InstanceOf)
+                .containsExactly(FocusInteraction.Focus::class)
         }
 
         rule.runOnIdle {
@@ -1101,12 +1106,12 @@
         }
 
         rule.runOnIdle {
-            assertThat(interactions).hasSize(2)
-            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
-            assertThat(interactions[1])
-                .isInstanceOf(FocusInteraction.Unfocus::class.java)
-            assertThat((interactions[1] as FocusInteraction.Unfocus).focus)
-                .isEqualTo(interactions[0])
+            // TODO(b/308811852): Simplify the other assertions in FocusableTest, ClickableTest and
+            //  CombinedClickable by using InstanceOf (like we do here).
+            assertThat(interactions)
+                .comparingElementsUsing(InstanceOf)
+                .containsExactly(FocusInteraction.Focus::class, FocusInteraction.Unfocus::class)
+                .inOrder()
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
index 3c6d481..0defe94 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
@@ -1298,7 +1298,7 @@
         val focusRequester = FocusRequester()
         lateinit var focusManager: FocusManager
         lateinit var inputModeManager: InputModeManager
-        rule.setContent {
+        rule.setFocusableContent {
             scope = rememberCoroutineScope()
             focusManager = LocalFocusManager.current
             inputModeManager = LocalInputModeManager.current
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
index 46b4931..80b1504 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FocusableTest.kt
@@ -97,7 +97,7 @@
 
     @Test
     fun focusable_defaultSemantics() {
-        rule.setContent {
+        rule.setFocusableContent {
             Box {
                 BasicText(
                     "focusableText",
@@ -115,7 +115,7 @@
 
     @Test
     fun focusable_disabledSemantics() {
-        rule.setContent {
+        rule.setFocusableContent {
             Box {
                 BasicText(
                     "focusableText",
@@ -133,7 +133,7 @@
     @Test
     fun focusable_focusAcquire() {
         val (focusRequester, otherFocusRequester) = FocusRequester.createRefs()
-        rule.setContent {
+        rule.setFocusableContent {
             Box {
                 BasicText(
                     "focusableText",
@@ -176,7 +176,7 @@
 
         lateinit var scope: CoroutineScope
 
-        rule.setContent {
+        rule.setFocusableContent {
             scope = rememberCoroutineScope()
             Box {
                 BasicText(
@@ -236,7 +236,7 @@
 
         lateinit var scope: CoroutineScope
 
-        rule.setContent {
+        rule.setFocusableContent {
             scope = rememberCoroutineScope()
             Box {
                 if (emitFocusableText) {
@@ -284,7 +284,6 @@
         }
     }
 
-    @OptIn(ExperimentalFoundationApi::class)
     @Test
     fun focusable_pins_whenItIsFocused() {
         // Arrange.
@@ -296,7 +295,7 @@
                 return PinnedHandle {}
             }
         }
-        rule.setContent {
+        rule.setFocusableContent {
             CompositionLocalProvider(LocalPinnableContainer provides pinnableContainer) {
                 Box(
                     Modifier
@@ -318,7 +317,6 @@
         }
     }
 
-    @OptIn(ExperimentalFoundationApi::class)
     @Test
     fun focusable_unpins_whenItIsUnfocused() {
         // Arrange.
@@ -330,7 +328,7 @@
                 return PinnedHandle { onUnpinInvoked = true }
             }
         }
-        rule.setContent {
+        rule.setFocusableContent {
             CompositionLocalProvider(LocalPinnableContainer provides pinnableContainer) {
                 Box(
                     Modifier
@@ -386,7 +384,7 @@
         }
         val focusRequester = FocusRequester()
 
-        rule.setContent {
+        rule.setFocusableContent {
             with(rule.density) {
                 Box(
                     Modifier
@@ -422,7 +420,7 @@
         lateinit var state: LazyListState
         lateinit var coroutineScope: CoroutineScope
         var items by mutableStateOf((1..20).toList())
-        rule.setContent {
+        rule.setFocusableContent {
             state = rememberLazyListState()
             coroutineScope = rememberCoroutineScope()
             LazyRow(
@@ -457,7 +455,7 @@
         // Arrange.
         var hasFocus = false
         var itemVisible by mutableStateOf(true)
-        rule.setContent {
+        rule.setFocusableContent {
             SubcomposeLayout(
                 modifier = Modifier
                     .requiredSize(100.dp)
@@ -490,7 +488,6 @@
         }
     }
 
-    @OptIn(ExperimentalFoundationApi::class)
     @Test
     fun focusable_updatePinnableContainer_staysPinned() {
         // Arrange.
@@ -510,7 +507,7 @@
             }
         }
         var pinnableContainer by mutableStateOf<PinnableContainer>(pinnableContainer1)
-        rule.setContent {
+        rule.setFocusableContent {
             CompositionLocalProvider(LocalPinnableContainer provides pinnableContainer) {
                 Box(
                     Modifier
@@ -544,7 +541,7 @@
         val focusRequester = FocusRequester()
         lateinit var state: FocusState
         var key by mutableStateOf(0)
-        rule.setContent {
+        rule.setFocusableContent {
             ReusableContent(key) {
                 BasicText(
                     "focusableText",
@@ -594,7 +591,7 @@
             )
         }
 
-        rule.setContent {
+        rule.setFocusableContent {
             scope = rememberCoroutineScope()
             if (moveContent) {
                 Box(Modifier.size(5.dp)) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FoundationTestUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FoundationTestUtils.kt
new file mode 100644
index 0000000..990aaec
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/FoundationTestUtils.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.unit.dp
+
+/**
+ * This function adds a parent composable which has size.
+ * [View.requestFocus()][android.view.View.requestFocus] will not take focus if the view has no
+ * size.
+ *
+ * @param extraItemForInitialFocus Includes an extra item that takes focus initially. This is
+ * useful in cases where we need tests that could be affected by initial focus. Eg. When there is
+ * only one focusable item and we clear focus, that item could end up being focused on again by the
+ * initial focus logic.
+ */
+internal fun ComposeContentTestRule.setFocusableContent(
+    extraItemForInitialFocus: Boolean = true,
+    content: @Composable () -> Unit
+) {
+    setContent {
+        if (extraItemForInitialFocus) {
+            Row {
+                Box(modifier = Modifier.requiredSize(10.dp, 10.dp).focusable())
+                Box { content() }
+            }
+        } else {
+            Box(modifier = Modifier.requiredSize(100.dp, 100.dp)) { content() }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt
index 5b384af..74078e3 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/SelectableTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.setFocusableContent
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.getValue
@@ -548,7 +549,7 @@
         lateinit var focusManager: FocusManager
         lateinit var inputModeManager: InputModeManager
 
-        rule.setContent {
+        rule.setFocusableContent {
             scope = rememberCoroutineScope()
             focusManager = LocalFocusManager.current
             inputModeManager = LocalInputModeManager.current
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt
index 4174820..312e6b5 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/selection/ToggleableTest.kt
@@ -29,6 +29,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.setFocusableContent
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.getValue
@@ -640,7 +641,7 @@
         lateinit var focusManager: FocusManager
         lateinit var inputModeManager: InputModeManager
 
-        rule.setContent {
+        rule.setFocusableContent {
             scope = rememberCoroutineScope()
             focusManager = LocalFocusManager.current
             inputModeManager = LocalInputModeManager.current
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
index 6860c1a..489b3e2 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.setFocusableContent
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
@@ -370,7 +371,7 @@
         lateinit var textLayoutResult: TextLayoutResult
         val focusRequester = FocusRequester()
 
-        setContent {
+        setContent(extraItemForInitialFocus = false) {
             CoreTextField(
                 value = value,
                 modifier = Modifier.focusRequester(focusRequester),
@@ -400,7 +401,7 @@
         lateinit var textLayoutResult: TextLayoutResult
         val focusRequester = FocusRequester()
 
-        setContent {
+        setContent(extraItemForInitialFocus = false) {
             Box(Modifier.offset { offset }) {
                 CoreTextField(
                     value = value,
@@ -440,7 +441,7 @@
         var value by mutableStateOf(TextFieldValue(""))
         lateinit var textLayoutResult: TextLayoutResult
 
-        setContent {
+        setContent(extraItemForInitialFocus = false) {
             CoreTextField(
                 value = value,
                 modifier = Modifier.testTag(tag),
@@ -494,7 +495,7 @@
         val focusRequester = FocusRequester()
         val matrix = Matrix()
 
-        setContent {
+        setContent(extraItemForInitialFocus = false) {
             Box(Modifier.offset { offset }) {
                 CoreTextField(value = value,
                     modifier = Modifier.focusRequester(focusRequester),
@@ -539,8 +540,11 @@
         }
     }
 
-    private fun setContent(content: @Composable () -> Unit) {
-        rule.setContent {
+    private fun setContent(
+        extraItemForInitialFocus: Boolean = true,
+        content: @Composable () -> Unit
+    ) {
+        rule.setFocusableContent(extraItemForInitialFocus) {
             focusManager = LocalFocusManager.current
             CompositionLocalProvider(
                 LocalTextInputService provides textInputService,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt
index ce06e74..d6aedab 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt
@@ -17,6 +17,10 @@
 package androidx.compose.foundation.text2
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text2.input.InputTransformation
 import androidx.compose.foundation.text2.input.TextFieldBuffer
@@ -44,6 +48,7 @@
 import androidx.compose.ui.test.requestFocus
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
@@ -95,14 +100,14 @@
     @Test
     fun stopsBeingTextEditor_whenFocusLost() {
         val state = TextFieldState()
-        var focusManager: FocusManager? = null
+        lateinit var focusManager: FocusManager
         inputMethodInterceptor.setTextFieldTestContent {
             focusManager = LocalFocusManager.current
             BasicTextField2(state, Modifier.testTag(Tag))
         }
         requestFocus(Tag)
         rule.runOnIdle {
-            focusManager!!.clearFocus()
+            focusManager.clearFocus()
         }
         inputMethodInterceptor.assertNoSessionActive()
     }
@@ -215,6 +220,7 @@
 
             commitText("hello", 1)
 
+            @Suppress("SpellCheckingInspection")
             assertThat(state.text.toString()).isEqualTo("helloworld")
         }
 
@@ -408,6 +414,12 @@
         override val isWindowFocused = true
     }
     this.setContent {
-        CompositionLocalProvider(LocalWindowInfo provides windowInfo, content)
+        CompositionLocalProvider(LocalWindowInfo provides windowInfo) {
+            Row {
+                // Extra focusable that takes initial focus when focus is cleared.
+                Box(Modifier.size(10.dp).focusable())
+                Box { content() }
+            }
+        }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
index 2130384..5a07ca6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
@@ -390,7 +390,7 @@
             } else {
                 absoluteOffset
             }
-            positionedItems.addAll(
+            positionedItems.addAllFromArray(
                 line.position(relativeOffset, layoutWidth, layoutHeight)
             )
         }
@@ -405,7 +405,7 @@
 
         currentMainAxis = firstLineScrollOffset
         lines.fastForEach {
-            positionedItems.addAll(it.position(currentMainAxis, layoutWidth, layoutHeight))
+            positionedItems.addAllFromArray(it.position(currentMainAxis, layoutWidth, layoutHeight))
             currentMainAxis += it.mainAxisSizeWithSpacings
         }
 
@@ -417,3 +417,10 @@
     }
     return positionedItems
 }
+
+// Faster version of addAll that does not create a list for each array
+private fun <T> MutableList<T>.addAllFromArray(arr: Array<T>) {
+    for (item in arr) {
+        add(item)
+    }
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index c4701d4..7ca2ade 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -597,43 +597,19 @@
     property public abstract kotlin.ranges.IntRange yearRange;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
-    method public static androidx.compose.material3.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.DismissDirection[] values();
-    enum_constant public static final androidx.compose.material3.DismissDirection EndToStart;
-    enum_constant public static final androidx.compose.material3.DismissDirection StartToEnd;
+  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
+    method @Deprecated public static androidx.compose.material3.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method @Deprecated public static androidx.compose.material3.DismissDirection[] values();
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection EndToStart;
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection StartToEnd;
   }
 
-  public final class DismissState {
-    ctor public DismissState(androidx.compose.material3.DismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
-    ctor @Deprecated public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
-    method public suspend Object? dismiss(androidx.compose.material3.DismissDirection direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.compose.material3.DismissValue getCurrentValue();
-    method public androidx.compose.material3.DismissDirection? getDismissDirection();
-    method public float getProgress();
-    method public androidx.compose.material3.DismissValue getTargetValue();
-    method public boolean isDismissed(androidx.compose.material3.DismissDirection direction);
-    method public float requireOffset();
-    method public suspend Object? reset(kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public suspend Object? snapTo(androidx.compose.material3.DismissValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    property public final androidx.compose.material3.DismissValue currentValue;
-    property public final androidx.compose.material3.DismissDirection? dismissDirection;
-    property public final float progress;
-    property public final androidx.compose.material3.DismissValue targetValue;
-    field public static final androidx.compose.material3.DismissState.Companion Companion;
-  }
-
-  public static final class DismissState.Companion {
-    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
-    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
-    method public static androidx.compose.material3.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.DismissValue[] values();
-    enum_constant public static final androidx.compose.material3.DismissValue Default;
-    enum_constant public static final androidx.compose.material3.DismissValue DismissedToEnd;
-    enum_constant public static final androidx.compose.material3.DismissValue DismissedToStart;
+  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
+    method @Deprecated public static androidx.compose.material3.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method @Deprecated public static androidx.compose.material3.DismissValue[] values();
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue Default;
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToEnd;
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToStart;
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DisplayMode {
@@ -1559,15 +1535,45 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissBoxDefaults {
-    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
-    property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> fixedPositionalThreshold;
+    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getPositionalThreshold();
+    property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> positionalThreshold;
     field public static final androidx.compose.material3.SwipeToDismissBoxDefaults INSTANCE;
   }
 
   public final class SwipeToDismissBoxKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.SwipeToDismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.SwipeToDismissValue> directions);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.SwipeToDismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional boolean enableDismissFromStartToEnd, optional boolean enableDismissFromEndToStart, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SwipeToDismissState rememberSwipeToDismissState(optional androidx.compose.material3.SwipeToDismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissState {
+    ctor public SwipeToDismissState(androidx.compose.material3.SwipeToDismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method public suspend Object? dismiss(androidx.compose.material3.SwipeToDismissValue direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.material3.SwipeToDismissValue getCurrentValue();
+    method public androidx.compose.material3.SwipeToDismissValue getDismissDirection();
+    method @FloatRange(from=0.0, to=1.0) public float getProgress();
+    method public androidx.compose.material3.SwipeToDismissValue getTargetValue();
+    method @Deprecated public boolean isDismissed(androidx.compose.material3.DismissDirection direction);
+    method public float requireOffset();
+    method public suspend Object? reset(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? snapTo(androidx.compose.material3.SwipeToDismissValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final androidx.compose.material3.SwipeToDismissValue currentValue;
+    property public final androidx.compose.material3.SwipeToDismissValue dismissDirection;
+    property @FloatRange(from=0.0, to=1.0) public final float progress;
+    property public final androidx.compose.material3.SwipeToDismissValue targetValue;
+    field public static final androidx.compose.material3.SwipeToDismissState.Companion Companion;
+  }
+
+  public static final class SwipeToDismissState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SwipeToDismissState,androidx.compose.material3.SwipeToDismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SwipeToDismissValue {
+    method public static androidx.compose.material3.SwipeToDismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.material3.SwipeToDismissValue[] values();
+    enum_constant public static final androidx.compose.material3.SwipeToDismissValue EndToStart;
+    enum_constant public static final androidx.compose.material3.SwipeToDismissValue Settled;
+    enum_constant public static final androidx.compose.material3.SwipeToDismissValue StartToEnd;
   }
 
   @androidx.compose.runtime.Immutable public final class SwitchColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index c4701d4..7ca2ade 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -597,43 +597,19 @@
     property public abstract kotlin.ranges.IntRange yearRange;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
-    method public static androidx.compose.material3.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.DismissDirection[] values();
-    enum_constant public static final androidx.compose.material3.DismissDirection EndToStart;
-    enum_constant public static final androidx.compose.material3.DismissDirection StartToEnd;
+  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
+    method @Deprecated public static androidx.compose.material3.DismissDirection valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method @Deprecated public static androidx.compose.material3.DismissDirection[] values();
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection EndToStart;
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection StartToEnd;
   }
 
-  public final class DismissState {
-    ctor public DismissState(androidx.compose.material3.DismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
-    ctor @Deprecated public DismissState(androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
-    method public suspend Object? dismiss(androidx.compose.material3.DismissDirection direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.compose.material3.DismissValue getCurrentValue();
-    method public androidx.compose.material3.DismissDirection? getDismissDirection();
-    method public float getProgress();
-    method public androidx.compose.material3.DismissValue getTargetValue();
-    method public boolean isDismissed(androidx.compose.material3.DismissDirection direction);
-    method public float requireOffset();
-    method public suspend Object? reset(kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public suspend Object? snapTo(androidx.compose.material3.DismissValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    property public final androidx.compose.material3.DismissValue currentValue;
-    property public final androidx.compose.material3.DismissDirection? dismissDirection;
-    property public final float progress;
-    property public final androidx.compose.material3.DismissValue targetValue;
-    field public static final androidx.compose.material3.DismissState.Companion Companion;
-  }
-
-  public static final class DismissState.Companion {
-    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
-    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DismissState,androidx.compose.material3.DismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
-  }
-
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
-    method public static androidx.compose.material3.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.DismissValue[] values();
-    enum_constant public static final androidx.compose.material3.DismissValue Default;
-    enum_constant public static final androidx.compose.material3.DismissValue DismissedToEnd;
-    enum_constant public static final androidx.compose.material3.DismissValue DismissedToStart;
+  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
+    method @Deprecated public static androidx.compose.material3.DismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method @Deprecated public static androidx.compose.material3.DismissValue[] values();
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue Default;
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToEnd;
+    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToStart;
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DisplayMode {
@@ -1559,15 +1535,45 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissBoxDefaults {
-    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getFixedPositionalThreshold();
-    property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> fixedPositionalThreshold;
+    method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getPositionalThreshold();
+    property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> positionalThreshold;
     field public static final androidx.compose.material3.SwipeToDismissBoxDefaults INSTANCE;
   }
 
   public final class SwipeToDismissBoxKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.DismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.DismissDirection> directions, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DismissState rememberDismissState(optional androidx.compose.material3.DismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.SwipeToDismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.SwipeToDismissValue> directions);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.SwipeToDismissState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional boolean enableDismissFromStartToEnd, optional boolean enableDismissFromEndToStart, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SwipeToDismissState rememberSwipeToDismissState(optional androidx.compose.material3.SwipeToDismissValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissState {
+    ctor public SwipeToDismissState(androidx.compose.material3.SwipeToDismissValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method public suspend Object? dismiss(androidx.compose.material3.SwipeToDismissValue direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.material3.SwipeToDismissValue getCurrentValue();
+    method public androidx.compose.material3.SwipeToDismissValue getDismissDirection();
+    method @FloatRange(from=0.0, to=1.0) public float getProgress();
+    method public androidx.compose.material3.SwipeToDismissValue getTargetValue();
+    method @Deprecated public boolean isDismissed(androidx.compose.material3.DismissDirection direction);
+    method public float requireOffset();
+    method public suspend Object? reset(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? snapTo(androidx.compose.material3.SwipeToDismissValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final androidx.compose.material3.SwipeToDismissValue currentValue;
+    property public final androidx.compose.material3.SwipeToDismissValue dismissDirection;
+    property @FloatRange(from=0.0, to=1.0) public final float progress;
+    property public final androidx.compose.material3.SwipeToDismissValue targetValue;
+    field public static final androidx.compose.material3.SwipeToDismissState.Companion Companion;
+  }
+
+  public static final class SwipeToDismissState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SwipeToDismissState,androidx.compose.material3.SwipeToDismissValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SwipeToDismissValue {
+    method public static androidx.compose.material3.SwipeToDismissValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.material3.SwipeToDismissValue[] values();
+    enum_constant public static final androidx.compose.material3.SwipeToDismissValue EndToStart;
+    enum_constant public static final androidx.compose.material3.SwipeToDismissValue Settled;
+    enum_constant public static final androidx.compose.material3.SwipeToDismissValue StartToEnd;
   }
 
   @androidx.compose.runtime.Immutable public final class SwitchColors {
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
index 6f691ed..f751e9f 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
@@ -16,10 +16,8 @@
 
 package androidx.compose.material3.demos
 
-import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.shrinkHorizontally
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -30,14 +28,13 @@
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material.icons.filled.Done
 import androidx.compose.material3.Card
-import androidx.compose.material3.DismissDirection
-import androidx.compose.material3.DismissValue
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.SwipeToDismissBox
+import androidx.compose.material3.SwipeToDismissValue
 import androidx.compose.material3.Text
-import androidx.compose.material3.rememberDismissState
+import androidx.compose.material3.rememberSwipeToDismissState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -86,79 +83,75 @@
             var unread by remember { mutableStateOf(false) }
             val scope = rememberCoroutineScope()
 
-            val dismissState = rememberDismissState(
+            val dismissState = rememberSwipeToDismissState(
                 confirmValueChange = {
-                    if (it == DismissValue.DismissedToEnd) unread = !unread
-                    it != DismissValue.DismissedToEnd
+                    if (it == SwipeToDismissValue.StartToEnd) unread = !unread
+                    it != SwipeToDismissValue.StartToEnd
                 },
                 positionalThreshold = { distance -> distance * .25f }
             )
-            val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)
-            AnimatedVisibility(
-                visible = !isDismissed,
-                exit = shrinkHorizontally(shrinkTowards = Alignment.Start)
-            ) {
-                SwipeToDismissBox(
-                    state = dismissState,
-                    backgroundContent = {
-                        val direction = dismissState.dismissDirection ?: return@SwipeToDismissBox
-                        val color by animateColorAsState(
-                            when (dismissState.targetValue) {
-                                DismissValue.Default -> Color.LightGray
-                                DismissValue.DismissedToEnd -> Color.Green
-                                DismissValue.DismissedToStart -> Color.Red
-                            }
-                        )
-                        val alignment = when (direction) {
-                            DismissDirection.StartToEnd -> Alignment.CenterStart
-                            DismissDirection.EndToStart -> Alignment.CenterEnd
+            SwipeToDismissBox(
+                state = dismissState,
+                modifier = Modifier.padding(vertical = 4.dp),
+                backgroundContent = {
+                    val direction = dismissState.dismissDirection
+                    val color by animateColorAsState(
+                        when (dismissState.targetValue) {
+                            SwipeToDismissValue.Settled -> Color.LightGray
+                            SwipeToDismissValue.StartToEnd -> Color.Green
+                            SwipeToDismissValue.EndToStart -> Color.Red
                         }
-                        val icon = when (direction) {
-                            DismissDirection.StartToEnd -> Icons.Default.Done
-                            DismissDirection.EndToStart -> Icons.Default.Delete
-                        }
-                        val scale by animateFloatAsState(
-                            if (dismissState.targetValue == DismissValue.Default)
-                                0.75f else 1f
-                        )
-                        Box(
-                            Modifier
-                                .fillMaxSize()
-                                .background(color)
-                                .padding(horizontal = 20.dp),
-                            contentAlignment = alignment
-                        ) {
-                            Icon(
-                                icon,
-                                contentDescription = "Localized description",
-                                modifier = Modifier.scale(scale)
-                            )
-                        }
-                    },
-                    modifier = Modifier.padding(vertical = 4.dp)
-                ) {
-                    Card {
-                        ListItem(
-                            headlineContent = {
-                                Text(item, fontWeight = if (unread) FontWeight.Bold else null)
-                            },
-                            modifier = Modifier.semantics {
-                                // Provide accessible alternatives to swipe actions.
-                                val label = if (unread) "Mark Read" else "Mark Unread"
-                                customActions = listOf(
-                                    CustomAccessibilityAction(label) { unread = !unread; true },
-                                    CustomAccessibilityAction("Delete") {
-                                        scope.launch {
-                                            dismissState.dismiss(DismissDirection.EndToStart)
-                                        }
-                                        true
-                                    }
-                                )
-                            },
-                            supportingContent = { Text("Swipe me left or right!") },
+                    )
+                    val alignment = when (direction) {
+                        SwipeToDismissValue.StartToEnd,
+                        SwipeToDismissValue.Settled -> Alignment.CenterStart
+                        SwipeToDismissValue.EndToStart -> Alignment.CenterEnd
+                    }
+                    val icon = when (direction) {
+                        SwipeToDismissValue.StartToEnd,
+                        SwipeToDismissValue.Settled -> Icons.Default.Done
+                        SwipeToDismissValue.EndToStart -> Icons.Default.Delete
+                    }
+                    val scale by animateFloatAsState(
+                        if (dismissState.targetValue == SwipeToDismissValue.Settled)
+                            0.75f else 1f
+                    )
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .background(color)
+                            .padding(horizontal = 20.dp),
+                        contentAlignment = alignment
+                    ) {
+                        Icon(
+                            icon,
+                            contentDescription = "Localized description",
+                            modifier = Modifier.scale(scale)
                         )
                     }
                 }
+            ) {
+                Card {
+                    ListItem(
+                        headlineContent = {
+                            Text(item, fontWeight = if (unread) FontWeight.Bold else null)
+                        },
+                        modifier = Modifier.semantics {
+                            // Provide accessible alternatives to swipe actions.
+                            val label = if (unread) "Mark Read" else "Mark Unread"
+                            customActions = listOf(
+                                CustomAccessibilityAction(label) { unread = !unread; true },
+                                CustomAccessibilityAction("Delete") {
+                                    scope.launch {
+                                        dismissState.dismiss(SwipeToDismissValue.EndToStart)
+                                    }
+                                    true
+                                }
+                            )
+                        },
+                        supportingContent = { Text("Swipe me left or right!") },
+                    )
+                }
             }
         }
     }
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
index 81d3b8e..b883b78 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
@@ -22,15 +22,13 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material3.Card
-import androidx.compose.material3.DismissValue.Default
-import androidx.compose.material3.DismissValue.DismissedToEnd
-import androidx.compose.material3.DismissValue.DismissedToStart
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.SwipeToDismissBox
+import androidx.compose.material3.SwipeToDismissValue
 import androidx.compose.material3.Text
-import androidx.compose.material3.rememberDismissState
+import androidx.compose.material3.rememberSwipeToDismissState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
@@ -42,15 +40,15 @@
 @Composable
 @ExperimentalMaterial3Api
 fun SwipeToDismissListItems() {
-    val dismissState = rememberDismissState()
+    val dismissState = rememberSwipeToDismissState()
     SwipeToDismissBox(
         state = dismissState,
         backgroundContent = {
             val color by animateColorAsState(
                 when (dismissState.targetValue) {
-                    Default -> Color.LightGray
-                    DismissedToEnd -> Color.Green
-                    DismissedToStart -> Color.Red
+                    SwipeToDismissValue.Settled -> Color.LightGray
+                    SwipeToDismissValue.StartToEnd -> Color.Green
+                    SwipeToDismissValue.EndToStart -> Color.Red
                 }
             )
             Box(Modifier.fillMaxSize().background(color))
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarTest.kt
index 89721c6..b025a39 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material3
 
 import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -70,6 +71,9 @@
                 val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
                 var active by remember { mutableStateOf(false) }
 
+                // Extra item for initial focus.
+                Box(Modifier.size(10.dp).focusable())
+
                 SearchBar(
                     modifier = Modifier.testTag(SearchBarTestTag),
                     query = "Query",
@@ -249,6 +253,9 @@
                 val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
                 var active by remember { mutableStateOf(false) }
 
+                // Extra item for initial focus.
+                Box(Modifier.size(10.dp).focusable())
+
                 DockedSearchBar(
                     modifier = Modifier.testTag(SearchBarTestTag),
                     query = "Query",
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
index 0ce7603..05a5858 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
@@ -69,7 +69,7 @@
     fun swipeDismiss_testOffset_whenDefault() {
         rule.setContent {
             SwipeToDismissBox(
-                state = rememberDismissState(DismissValue.Default),
+                state = rememberSwipeToDismissState(SwipeToDismissValue.Settled),
                 backgroundContent = { }
             ) {
                     Box(
@@ -87,7 +87,7 @@
     fun swipeDismiss_testOffset_whenDismissedToEnd() {
         rule.setContent {
             SwipeToDismissBox(
-                state = rememberDismissState(DismissValue.DismissedToEnd),
+                state = rememberSwipeToDismissState(SwipeToDismissValue.StartToEnd),
                 backgroundContent = { }
             ) {
                     Box(
@@ -106,8 +106,8 @@
     fun swipeDismiss_testOffset_whenDismissedToStart() {
         rule.setContent {
             SwipeToDismissBox(
-                state = rememberDismissState(DismissValue.DismissedToStart),
-                backgroundContent = { }
+                state = rememberSwipeToDismissState(SwipeToDismissValue.EndToStart),
+                backgroundContent = { },
             ) {
                     Box(
                         Modifier
@@ -125,7 +125,7 @@
     fun swipeDismiss_testBackgroundMatchesContentSize() {
         rule.setContent {
             SwipeToDismissBox(
-                state = rememberDismissState(DismissValue.Default),
+                state = rememberSwipeToDismissState(SwipeToDismissValue.Settled),
                 backgroundContent = {
                     Box(
                         Modifier
@@ -142,14 +142,15 @@
 
     @Test
     fun swipeDismiss_dismissBySwipe_toEnd() {
-        lateinit var dismissState: DismissState
+        lateinit var swipeToDismissState: SwipeToDismissState
         rule.setContent {
-            dismissState = rememberDismissState(DismissValue.Default)
+            swipeToDismissState = rememberSwipeToDismissState(SwipeToDismissValue.Settled)
             SwipeToDismissBox(
-                state = dismissState,
-                backgroundContent = { },
+                state = swipeToDismissState,
                 modifier = Modifier.testTag(swipeDismissTag),
-                directions = setOf(DismissDirection.StartToEnd)
+                enableDismissFromStartToEnd = true,
+                enableDismissFromEndToStart = false,
+                backgroundContent = { }
             ) { Box(Modifier.fillMaxSize()) }
         }
 
@@ -158,20 +159,21 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(dismissState.currentValue).isEqualTo(DismissValue.DismissedToEnd)
+            assertThat(swipeToDismissState.currentValue).isEqualTo(SwipeToDismissValue.StartToEnd)
         }
     }
 
     @Test
     fun swipeDismiss_dismissBySwipe_toStart() {
-        lateinit var dismissState: DismissState
+        lateinit var swipeToDismissState: SwipeToDismissState
         rule.setContent {
-            dismissState = rememberDismissState(DismissValue.Default)
+            swipeToDismissState = rememberSwipeToDismissState(SwipeToDismissValue.Settled)
             SwipeToDismissBox(
-                state = dismissState,
-                backgroundContent = { },
+                state = swipeToDismissState,
                 modifier = Modifier.testTag(swipeDismissTag),
-                directions = setOf(DismissDirection.EndToStart)
+                enableDismissFromStartToEnd = false,
+                enableDismissFromEndToStart = true,
+                backgroundContent = { },
             ) { Box(Modifier.fillMaxSize()) }
         }
 
@@ -180,21 +182,22 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(dismissState.currentValue).isEqualTo(DismissValue.DismissedToStart)
+            assertThat(swipeToDismissState.currentValue).isEqualTo(SwipeToDismissValue.EndToStart)
         }
     }
 
     @Test
     fun swipeDismiss_dismissBySwipe_toEnd_rtl() {
-        lateinit var dismissState: DismissState
+        lateinit var swipeToDismissState: SwipeToDismissState
         rule.setContent {
-            dismissState = rememberDismissState(DismissValue.Default)
+            swipeToDismissState = rememberSwipeToDismissState()
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 SwipeToDismissBox(
-                    state = dismissState,
-                    backgroundContent = { },
+                    state = swipeToDismissState,
                     modifier = Modifier.testTag(swipeDismissTag),
-                    directions = setOf(DismissDirection.StartToEnd)
+                    enableDismissFromStartToEnd = true,
+                    enableDismissFromEndToStart = false,
+                    backgroundContent = { },
                 ) { Box(Modifier.fillMaxSize()) }
             }
         }
@@ -204,21 +207,22 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(dismissState.currentValue).isEqualTo(DismissValue.DismissedToEnd)
+            assertThat(swipeToDismissState.currentValue).isEqualTo(SwipeToDismissValue.StartToEnd)
         }
     }
 
     @Test
     fun swipeDismiss_dismissBySwipe_toStart_rtl() {
-        lateinit var dismissState: DismissState
+        lateinit var swipeToDismissState: SwipeToDismissState
         rule.setContent {
-            dismissState = rememberDismissState(DismissValue.Default)
+            swipeToDismissState = rememberSwipeToDismissState(SwipeToDismissValue.Settled)
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 SwipeToDismissBox(
-                    state = dismissState,
-                    backgroundContent = { },
+                    state = swipeToDismissState,
                     modifier = Modifier.testTag(swipeDismissTag),
-                    directions = setOf(DismissDirection.EndToStart)
+                    enableDismissFromStartToEnd = false,
+                    enableDismissFromEndToStart = true,
+                    backgroundContent = { },
                 ) { Box(Modifier.fillMaxSize()) }
             }
         }
@@ -228,20 +232,21 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(dismissState.currentValue).isEqualTo(DismissValue.DismissedToStart)
+            assertThat(swipeToDismissState.currentValue).isEqualTo(SwipeToDismissValue.EndToStart)
         }
     }
 
     @Test
     fun swipeDismiss_dismissBySwipe_disabled() {
-        lateinit var dismissState: DismissState
+        lateinit var swipeToDismissState: SwipeToDismissState
         rule.setContent {
-            dismissState = rememberDismissState(DismissValue.Default)
+            swipeToDismissState = rememberSwipeToDismissState(SwipeToDismissValue.Settled)
             SwipeToDismissBox(
-                state = dismissState,
-                backgroundContent = { },
+                state = swipeToDismissState,
                 modifier = Modifier.testTag(swipeDismissTag),
-                directions = setOf()
+                enableDismissFromStartToEnd = false,
+                enableDismissFromEndToStart = false,
+                backgroundContent = { },
             ) { Box(Modifier.fillMaxSize()) }
         }
 
@@ -250,7 +255,7 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(dismissState.currentValue).isEqualTo(DismissValue.Default)
+            assertThat(swipeToDismissState.currentValue).isEqualTo(SwipeToDismissValue.Settled)
         }
 
         rule.onNodeWithTag(swipeDismissTag).performTouchInput { swipeLeft() }
@@ -258,7 +263,7 @@
         advanceClock()
 
         rule.runOnIdle {
-            assertThat(dismissState.currentValue).isEqualTo(DismissValue.Default)
+            assertThat(swipeToDismissState.currentValue).isEqualTo(SwipeToDismissValue.Settled)
         }
     }
 
@@ -273,7 +278,7 @@
         lateinit var lazyState: LazyListState
         lateinit var scope: CoroutineScope
         val amountOfItems = 100
-        val composedItems = mutableMapOf<Int, DismissState>()
+        val composedItems = mutableMapOf<Int, SwipeToDismissState>()
 
         rule.setContent {
             scope = rememberCoroutineScope()
@@ -281,9 +286,9 @@
                 lazyState = rememberLazyListState()
                 LazyColumn(state = lazyState) {
                     items(amountOfItems, key = { item -> item }) { index ->
-                        composedItems[index] = rememberDismissState()
-                        val isDismissed = composedItems[index]!!
-                            .isDismissed(DismissDirection.EndToStart)
+                        composedItems[index] = rememberSwipeToDismissState()
+                        val isDismissed =
+                            composedItems[index]!!.currentValue == SwipeToDismissValue.EndToStart
                         AnimatedVisibility(visible = !isDismissed) {
                             SwipeToDismissBox(
                                 modifier = Modifier
@@ -320,7 +325,7 @@
 
         // Dismiss an item so that the lazy layout is required to compose a new item
         scope.launch {
-            composedItems[initiallyVisibleItems - 1]!!.dismiss(DismissDirection.EndToStart)
+            composedItems[initiallyVisibleItems - 1]!!.dismiss(SwipeToDismissValue.EndToStart)
         }
         rule.waitForIdle()
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
index e9d0618..ad8a03e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
@@ -28,13 +28,10 @@
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
 import androidx.compose.material3.tokens.AssistChipTokens
 import androidx.compose.material3.tokens.FilterChipTokens
 import androidx.compose.material3.tokens.InputChipTokens
@@ -56,12 +53,18 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.offset
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
 
 /**
  * <a href="https://m3.material.io/components/chips/overview" class="external" target="_blank">Material Design assist chip</a>.
@@ -1778,31 +1781,80 @@
         LocalContentColor provides labelColor,
         LocalTextStyle provides labelTextStyle
     ) {
-        Row(
-            Modifier
-                .width(IntrinsicSize.Max)
+        Layout(
+            modifier = Modifier
                 .defaultMinSize(minHeight = minHeight)
                 .padding(paddingValues),
-            horizontalArrangement = Arrangement.Start,
-            verticalAlignment = Alignment.CenterVertically
-        ) {
-            if (avatar != null) {
-                avatar()
-            } else if (leadingIcon != null) {
-                CompositionLocalProvider(
-                    LocalContentColor provides leadingIconColor, content = leadingIcon
+            content = {
+                if (avatar != null || leadingIcon != null) {
+                    Box(
+                        modifier = Modifier
+                            .layoutId(LeadingIconLayoutId),
+                        contentAlignment = Alignment.Center,
+                        content = {
+                            if (avatar != null) {
+                                avatar()
+                            } else if (leadingIcon != null) {
+                                CompositionLocalProvider(
+                                    LocalContentColor provides leadingIconColor,
+                                    content = leadingIcon
+                                )
+                            }
+                        }
+                    )
+                }
+                Row(
+                    modifier = Modifier
+                        .layoutId(LabelLayoutId)
+                        .padding(HorizontalElementsPadding, 0.dp),
+                    horizontalArrangement = Arrangement.Start,
+                    verticalAlignment = Alignment.CenterVertically,
+                    content = { label() }
                 )
+                if (trailingIcon != null) {
+                    Box(
+                        modifier = Modifier
+                            .layoutId(TrailingIconLayoutId),
+                        contentAlignment = Alignment.Center,
+                        content = {
+                            CompositionLocalProvider(
+                                LocalContentColor provides trailingIconColor,
+                                content = trailingIcon
+                            )
+                        }
+                    )
+                }
             }
-            Spacer(Modifier.width(HorizontalElementsPadding))
-            Row(
-                modifier = Modifier.weight(1f),
-                horizontalArrangement = Arrangement.Start,
-                verticalAlignment = Alignment.CenterVertically
-            ) { label() }
-            Spacer(Modifier.width(HorizontalElementsPadding))
-            if (trailingIcon != null) {
-                CompositionLocalProvider(
-                    LocalContentColor provides trailingIconColor, content = trailingIcon
+        ) { measurables, constraints ->
+            val leadingIconPlaceable: Placeable? =
+                measurables.fastFirstOrNull { it.layoutId == LeadingIconLayoutId }
+                    ?.measure(constraints.copy(minWidth = 0, minHeight = 0))
+            val leadingIconWidth = widthOrZero(leadingIconPlaceable)
+            val leadingIconHeight = heightOrZero(leadingIconPlaceable)
+
+            val trailingIconPlaceable: Placeable? =
+                measurables.fastFirstOrNull { it.layoutId == TrailingIconLayoutId }
+                    ?.measure(constraints.copy(minWidth = 0, minHeight = 0))
+            val trailingIconWidth = widthOrZero(trailingIconPlaceable)
+            val trailingIconHeight = heightOrZero(trailingIconPlaceable)
+
+            val labelPlaceable = measurables.fastFirst { it.layoutId == LabelLayoutId }
+                .measure(
+                    constraints.offset(horizontal = -(leadingIconWidth + trailingIconWidth))
+                )
+
+            val width = leadingIconWidth + labelPlaceable.width + trailingIconWidth
+            val height = maxOf(leadingIconHeight, labelPlaceable.height, trailingIconHeight)
+
+            layout(width, height) {
+                leadingIconPlaceable?.placeRelative(
+                    0,
+                    Alignment.CenterVertically.align(leadingIconHeight, height)
+                )
+                labelPlaceable.placeRelative(leadingIconWidth, 0)
+                trailingIconPlaceable?.placeRelative(
+                    leadingIconWidth + labelPlaceable.width,
+                    Alignment.CenterVertically.align(trailingIconHeight, height)
                 )
             }
         }
@@ -2435,3 +2487,7 @@
  * Returns the [PaddingValues] for the suggestion chip.
  */
 private val SuggestionChipPadding = PaddingValues(horizontal = HorizontalElementsPadding)
+
+private const val LeadingIconLayoutId = "leadingIcon"
+private const val LabelLayoutId = "label"
+private const val TrailingIconLayoutId = "trailingIcon"
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
index a22cbe7..ebbe7c7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
@@ -16,18 +16,13 @@
 
 package androidx.compose.material3
 
+import androidx.annotation.FloatRange
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
-import androidx.compose.material3.DismissDirection.EndToStart
-import androidx.compose.material3.DismissDirection.StartToEnd
-import androidx.compose.material3.DismissState.Companion.Saver
-import androidx.compose.material3.DismissValue.Default
-import androidx.compose.material3.DismissValue.DismissedToEnd
-import androidx.compose.material3.DismissValue.DismissedToStart
+import androidx.compose.material3.SwipeToDismissState.Companion.Saver
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.rememberSaveable
@@ -52,7 +47,7 @@
  * The directions in which a [SwipeToDismissBox] can be dismissed.
  */
 @ExperimentalMaterial3Api
-enum class DismissDirection {
+enum class SwipeToDismissValue {
     /**
      * Can be dismissed by swiping in the reading direction.
      */
@@ -61,81 +56,38 @@
     /**
      * Can be dismissed by swiping in the reverse of the reading direction.
      */
-    EndToStart
-}
-
-/**
- * Possible values of [DismissState].
- */
-@ExperimentalMaterial3Api
-enum class DismissValue {
-    /**
-     * Indicates the component has not been dismissed yet.
-     */
-    Default,
+    EndToStart,
 
     /**
-     * Indicates the component has been dismissed in the reading direction.
+     * Cannot currently be dismissed.
      */
-    DismissedToEnd,
-
-    /**
-     * Indicates the component has been dismissed in the reverse of the reading direction.
-     */
-    DismissedToStart
+    Settled
 }
 
 /**
  * State of the [SwipeToDismissBox] composable.
  *
  * @param initialValue The initial value of the state.
+ * @param density The density that this state can use to convert values to and from dp.
  * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
  * @param positionalThreshold The positional threshold to be used when calculating the target state
  * while a swipe is in progress and when settling after the swipe ends. This is the distance from
  * the start of a transition. It will be, depending on the direction of the interaction, added or
  * subtracted from/to the origin offset. It should always be a positive value.
  */
-@OptIn(ExperimentalMaterial3Api::class)
-class DismissState @Deprecated(
-    message = "This constructor is deprecated. " +
-        "Please use the constructor that provides a [Density]",
-    replaceWith = ReplaceWith(
-        "DismissState(" +
-            "initialValue, LocalDensity.current, confirmValueChange, positionalThreshold)"
-    )
-) constructor(
-    initialValue: DismissValue,
-    confirmValueChange: (DismissValue) -> Boolean = { true },
+@ExperimentalMaterial3Api
+class SwipeToDismissState(
+    initialValue: SwipeToDismissValue,
+    internal val density: Density,
+    confirmValueChange: (SwipeToDismissValue) -> Boolean = { true },
     positionalThreshold: (totalDistance: Float) -> Float
 ) {
-
-    /**
-     * State of the [SwipeToDismissBox] composable.
-     *
-     * @param initialValue The initial value of the state.
-     * @param density The density that this state can use to convert values to and from dp.
-     * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
-     * @param positionalThreshold The positional threshold to be used when calculating the target state
-     * while a swipe is in progress and when settling after the swipe ends. This is the distance from
-     * the start of a transition. It will be, depending on the direction of the interaction, added or
-     * subtracted from/to the origin offset. It should always be a positive value.
-     */
-    @Suppress("Deprecation")
-    constructor(
-        initialValue: DismissValue,
-        density: Density,
-        confirmValueChange: (DismissValue) -> Boolean = { true },
-        positionalThreshold: (totalDistance: Float) -> Float
-    ) : this(initialValue, confirmValueChange, positionalThreshold) {
-        this.density = density
-    }
-
     internal val anchoredDraggableState = AnchoredDraggableState(
         initialValue = initialValue,
         animationSpec = AnchoredDraggableDefaults.AnimationSpec,
         confirmValueChange = confirmValueChange,
         positionalThreshold = positionalThreshold,
-        velocityThreshold = { with(requireDensity()) { DismissThreshold.toPx() } }
+        velocityThreshold = { with(density) { DismissThreshold.toPx() } }
     )
 
     internal val offset: Float get() = anchoredDraggableState.offset
@@ -148,40 +100,53 @@
     fun requireOffset(): Float = anchoredDraggableState.requireOffset()
 
     /**
-     * The current state value of the [DismissState].
+     * The current state value of the [SwipeToDismissState].
      */
-    val currentValue: DismissValue get() = anchoredDraggableState.currentValue
+    val currentValue: SwipeToDismissValue get() = anchoredDraggableState.currentValue
 
     /**
      * The target state. This is the closest state to the current offset (taking into account
      * positional thresholds). If no interactions like animations or drags are in progress, this
      * will be the current state.
      */
-    val targetValue: DismissValue get() = anchoredDraggableState.targetValue
+    val targetValue: SwipeToDismissValue get() = anchoredDraggableState.targetValue
 
     /**
      * The fraction of the progress going from currentValue to targetValue, within [0f..1f] bounds.
      */
+    @get:FloatRange(from = 0.0, to = 1.0)
     val progress: Float get() = anchoredDraggableState.progress
 
     /**
      * The direction (if any) in which the composable has been or is being dismissed.
      *
-     * If the composable is settled at the default state, then this will be null. Use this to
-     * change the background of the [SwipeToDismissBox] if you want different actions on each side.
+     * Use this to change the background of the [SwipeToDismissBox] if you want different actions on each
+     * side.
      */
-    val dismissDirection: DismissDirection?
+    val dismissDirection: SwipeToDismissValue
         get() = if (offset == 0f || offset.isNaN())
-            null
-        else if (offset > 0f) StartToEnd else EndToStart
+            SwipeToDismissValue.Settled
+        else if (offset > 0f) SwipeToDismissValue.StartToEnd else SwipeToDismissValue.EndToStart
 
     /**
      * Whether the component has been dismissed in the given [direction].
      *
      * @param direction The dismiss direction.
      */
+    @Deprecated(
+        message = "DismissDirection is no longer used by SwipeToDismissState. Please compare " +
+            "currentValue against SwipeToDismissValue instead.",
+        level = DeprecationLevel.HIDDEN
+    )
+    @Suppress("DEPRECATION")
     fun isDismissed(direction: DismissDirection): Boolean {
-        return currentValue == if (direction == StartToEnd) DismissedToEnd else DismissedToStart
+        return currentValue == (
+                if (direction == DismissDirection.StartToEnd) {
+                    SwipeToDismissValue.StartToEnd
+                } else {
+                    SwipeToDismissValue.EndToStart
+                }
+            )
     }
 
     /**
@@ -189,7 +154,7 @@
      *
      * @param targetValue The new target value
      */
-    suspend fun snapTo(targetValue: DismissValue) {
+    suspend fun snapTo(targetValue: SwipeToDismissValue) {
         anchoredDraggableState.snapTo(targetValue)
     }
 
@@ -200,7 +165,9 @@
      *
      * @return the reason the reset animation ended
      */
-    suspend fun reset() = anchoredDraggableState.animateTo(targetValue = Default)
+    suspend fun reset() = anchoredDraggableState.animateTo(
+        targetValue = SwipeToDismissValue.Settled
+    )
 
     /**
      * Dismiss the component in the given [direction], with an animation and suspend. This method
@@ -208,64 +175,32 @@
      *
      * @param direction The dismiss direction.
      */
-    suspend fun dismiss(direction: DismissDirection) {
-        val targetValue = if (direction == StartToEnd) DismissedToEnd else DismissedToStart
-        anchoredDraggableState.animateTo(targetValue = targetValue)
-    }
-
-    internal var density: Density? = null
-    private fun requireDensity() = requireNotNull(density) {
-        "DismissState did not have a density attached. Are you using DismissState with " +
-            "the SwipeDismiss component?"
+    suspend fun dismiss(direction: SwipeToDismissValue) {
+        anchoredDraggableState.animateTo(targetValue = direction)
     }
 
     companion object {
 
         /**
-         * The default [Saver] implementation for [DismissState].
+         * The default [Saver] implementation for [SwipeToDismissState].
          */
         fun Saver(
-            confirmValueChange: (DismissValue) -> Boolean,
+            confirmValueChange: (SwipeToDismissValue) -> Boolean,
             positionalThreshold: (totalDistance: Float) -> Float,
             density: Density
-        ) =
-            Saver<DismissState, DismissValue>(
-                save = { it.currentValue },
-                restore = {
-                    DismissState(
-                        it, density, confirmValueChange, positionalThreshold
-                    )
-                }
-            )
-
-        /**
-         * The default [Saver] implementation for [DismissState].
-         */
-        @Deprecated(
-            message = "This function is deprecated. Please use the overload where Density is" +
-                " provided.",
-            replaceWith = ReplaceWith(
-                "Saver(confirmValueChange, positionalThreshold, LocalDensity.current)"
-            )
+        ) = Saver<SwipeToDismissState, SwipeToDismissValue>(
+            save = { it.currentValue },
+            restore = {
+                SwipeToDismissState(
+                    it, density, confirmValueChange, positionalThreshold
+                )
+            }
         )
-        @Suppress("Deprecation")
-        fun Saver(
-            confirmValueChange: (DismissValue) -> Boolean,
-            positionalThreshold: (totalDistance: Float) -> Float,
-        ) =
-            Saver<DismissState, DismissValue>(
-                save = { it.currentValue },
-                restore = {
-                    DismissState(
-                        it, confirmValueChange, positionalThreshold
-                    )
-                }
-            )
     }
 }
 
 /**
- * Create and [remember] a [DismissState].
+ * Create and [remember] a [SwipeToDismissState].
  *
  * @param initialValue The initial value of the state.
  * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
@@ -276,12 +211,12 @@
  */
 @Composable
 @ExperimentalMaterial3Api
-fun rememberDismissState(
-    initialValue: DismissValue = Default,
-    confirmValueChange: (DismissValue) -> Boolean = { true },
+fun rememberSwipeToDismissState(
+    initialValue: SwipeToDismissValue = SwipeToDismissValue.Settled,
+    confirmValueChange: (SwipeToDismissValue) -> Boolean = { true },
     positionalThreshold: (totalDistance: Float) -> Float =
-        SwipeToDismissBoxDefaults.fixedPositionalThreshold,
-): DismissState {
+        SwipeToDismissBoxDefaults.positionalThreshold,
+): SwipeToDismissState {
     val density = LocalDensity.current
     return rememberSaveable(
         saver = Saver(
@@ -290,7 +225,7 @@
             positionalThreshold = positionalThreshold
         )
     ) {
-        DismissState(initialValue, density, confirmValueChange, positionalThreshold)
+        SwipeToDismissState(initialValue, density, confirmValueChange, positionalThreshold)
     }
 }
 
@@ -311,16 +246,26 @@
     level = DeprecationLevel.WARNING,
     message = "Use SwipeToDismissBox instead",
     replaceWith =
-        ReplaceWith("SwipeToDismissBox(state, background, modifier, directions, dismissContent)")
+        ReplaceWith("SwipeToDismissBox(state, background, modifier, " +
+            "enableDismissFromStartToEnd, enableDismissFromEndToStart, dismissContent)")
 )
 @ExperimentalMaterial3Api
 fun SwipeToDismiss(
-    state: DismissState,
+    state: SwipeToDismissState,
     background: @Composable RowScope.() -> Unit,
     dismissContent: @Composable RowScope.() -> Unit,
     modifier: Modifier = Modifier,
-    directions: Set<DismissDirection> = setOf(EndToStart, StartToEnd),
-) = SwipeToDismissBox(state, background, modifier, directions, dismissContent)
+    directions: Set<SwipeToDismissValue> = setOf(SwipeToDismissValue.EndToStart,
+        SwipeToDismissValue.StartToEnd
+    ),
+) = SwipeToDismissBox(
+    state = state,
+    backgroundContent = background,
+    modifier = modifier,
+    enableDismissFromStartToEnd = SwipeToDismissValue.StartToEnd in directions,
+    enableDismissFromEndToStart = SwipeToDismissValue.EndToStart in directions,
+    content = dismissContent
+)
 
 /**
  * A composable that can be dismissed by swiping left or right.
@@ -328,27 +273,23 @@
  * @sample androidx.compose.material3.samples.SwipeToDismissListItems
  *
  * @param state The state of this component.
- * @param backgroundContent A composable that is stacked behind the content and is exposed when the
+ * @param backgroundContent A composable that is stacked behind the [content] and is exposed when the
  * content is swiped. You can/should use the [state] to have different backgrounds on each side.
- * @param content The content that can be dismissed.
  * @param modifier Optional [Modifier] for this component.
- * @param directions The set of directions in which the component can be dismissed.
+ * @param enableDismissFromStartToEnd Whether SwipeToDismissBox can be dismissed from start to end.
+ * @param enableDismissFromEndToStart Whether SwipeToDismissBox can be dismissed from end to start.
+ * @param content The content that can be dismissed.
  */
 @Composable
 @ExperimentalMaterial3Api
 fun SwipeToDismissBox(
-    state: DismissState,
+    state: SwipeToDismissState,
     backgroundContent: @Composable RowScope.() -> Unit,
     modifier: Modifier = Modifier,
-    directions: Set<DismissDirection> = setOf(EndToStart, StartToEnd),
+    enableDismissFromStartToEnd: Boolean = true,
+    enableDismissFromEndToStart: Boolean = true,
     content: @Composable RowScope.() -> Unit,
 ) {
-    // b/278692145 Remove this once deprecated methods without density are removed
-    val density = LocalDensity.current
-    SideEffect {
-        state.density = density
-    }
-
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
 
     Box(
@@ -356,7 +297,7 @@
             .anchoredDraggable(
                 state = state.anchoredDraggableState,
                 orientation = Orientation.Horizontal,
-                enabled = state.currentValue == Default,
+                enabled = state.currentValue == SwipeToDismissValue.Settled,
                 reverseDirection = isRtl,
             ),
         propagateMinConstraints = true
@@ -367,66 +308,134 @@
         )
         Row(
             content = content,
-            modifier = Modifier.swipeDismissAnchors(state, directions)
+            modifier = Modifier.swipeToDismissAnchors(
+                state,
+                enableDismissFromStartToEnd,
+                enableDismissFromEndToStart
+            )
         )
     }
 }
 
-/** Contains default values for [SwipeToDismissBox] and [DismissState]. */
+/** Contains default values for [SwipeToDismissBox] and [SwipeToDismissState]. */
 @ExperimentalMaterial3Api
 object SwipeToDismissBoxDefaults {
-    /** Default positional threshold of 56.dp for [DismissState]. */
-    val fixedPositionalThreshold: (totalDistance: Float) -> Float
+    /** Default positional threshold of 56.dp for [SwipeToDismissState]. */
+    val positionalThreshold: (totalDistance: Float) -> Float
         @Composable get() = with(LocalDensity.current) {
             { 56.dp.toPx() }
         }
 }
 
+/**
+ * The directions in which a [SwipeToDismissBox] can be dismissed.
+ */
+@ExperimentalMaterial3Api
+@Deprecated(
+    message = "Dismiss direction is no longer used by SwipeToDismissState. Please use " +
+        "SwipeToDismissValue instead.",
+    level = DeprecationLevel.WARNING
+)
+enum class DismissDirection {
+    /**
+     * Can be dismissed by swiping in the reading direction.
+     */
+    StartToEnd,
+
+    /**
+     * Can be dismissed by swiping in the reverse of the reading direction.
+     */
+    EndToStart,
+}
+
+/**
+ * Possible values of [SwipeToDismissState].
+ */
+@ExperimentalMaterial3Api
+@Deprecated(
+    message = "DismissValue is no longer used by SwipeToDismissState. Please use " +
+        "SwipeToDismissValue instead.",
+    level = DeprecationLevel.WARNING
+)
+enum class DismissValue {
+    /**
+     * Indicates the component has not been dismissed yet.
+     */
+    Default,
+
+    /**
+     * Indicates the component has been dismissed in the reading direction.
+     */
+    DismissedToEnd,
+
+    /**
+     * Indicates the component has been dismissed in the reverse of the reading direction.
+     */
+    DismissedToStart
+}
+
 private val DismissThreshold = 125.dp
 
 @OptIn(ExperimentalMaterial3Api::class)
-private fun Modifier.swipeDismissAnchors(state: DismissState, directions: Set<DismissDirection>) =
-    this then SwipeDismissAnchorsElement(state, directions)
+private fun Modifier.swipeToDismissAnchors(
+    state: SwipeToDismissState,
+    enableDismissFromStartToEnd: Boolean,
+    enableDismissFromEndToStart: Boolean
+) = this then SwipeToDismissAnchorsElement(
+    state,
+    enableDismissFromStartToEnd,
+    enableDismissFromEndToStart
+)
 
 @OptIn(ExperimentalMaterial3Api::class)
-private class SwipeDismissAnchorsElement(
-    private val state: DismissState,
-    private val directions: Set<DismissDirection>,
-) : ModifierNodeElement<SwipeDismissAnchorsNode>() {
+private class SwipeToDismissAnchorsElement(
+    private val state: SwipeToDismissState,
+    private val enableDismissFromStartToEnd: Boolean,
+    private val enableDismissFromEndToStart: Boolean,
+) : ModifierNodeElement<SwipeToDismissAnchorsNode>() {
 
-    override fun create() = SwipeDismissAnchorsNode(state, directions)
+    override fun create() = SwipeToDismissAnchorsNode(
+        state,
+        enableDismissFromStartToEnd,
+        enableDismissFromEndToStart,
+    )
 
-    override fun update(node: SwipeDismissAnchorsNode) {
+    override fun update(node: SwipeToDismissAnchorsNode) {
         node.state = state
-        node.directions = directions
+        node.enableDismissFromStartToEnd = enableDismissFromStartToEnd
+        node.enableDismissFromEndToStart = enableDismissFromEndToStart
     }
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
-        other as SwipeDismissAnchorsElement
+        other as SwipeToDismissAnchorsElement
         if (state != other.state) return false
-        if (directions != other.directions) return false
+        if (enableDismissFromStartToEnd != other.enableDismissFromStartToEnd) return false
+        if (enableDismissFromEndToStart != other.enableDismissFromEndToStart) return false
         return true
     }
 
     override fun hashCode(): Int {
         var result = state.hashCode()
-        result = 31 * result + directions.hashCode()
+        result = 31 * result + enableDismissFromStartToEnd.hashCode()
+        result = 31 * result + enableDismissFromEndToStart.hashCode()
         return result
     }
 
     override fun InspectorInfo.inspectableProperties() {
         debugInspectorInfo {
             properties["state"] = state
-            properties["directions"] = directions
+            properties["enableDismissFromStartToEnd"] = enableDismissFromStartToEnd
+            properties["enableDismissFromEndToStart"] = enableDismissFromEndToStart
         }
     }
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
-private class SwipeDismissAnchorsNode(
-    var state: DismissState,
-    var directions: Set<DismissDirection>
+private class SwipeToDismissAnchorsNode(
+    var state: SwipeToDismissState,
+    var enableDismissFromStartToEnd: Boolean,
+    var enableDismissFromEndToStart: Boolean,
 ) : Modifier.Node(), LayoutModifierNode {
     private var didLookahead: Boolean = false
 
@@ -445,12 +454,12 @@
         if (isLookingAhead || !didLookahead) {
             val width = placeable.width.toFloat()
             val newAnchors = DraggableAnchors {
-                Default at 0f
-                if (StartToEnd in directions) {
-                    DismissedToEnd at width
+                SwipeToDismissValue.Settled at 0f
+                if (enableDismissFromStartToEnd) {
+                    SwipeToDismissValue.StartToEnd at width
                 }
-                if (EndToStart in directions) {
-                    DismissedToStart at -width
+                if (enableDismissFromEndToStart) {
+                    SwipeToDismissValue.EndToStart at -width
                 }
             }
             state.anchoredDraggableState.updateAnchors(newAnchors)
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
index 51b9f61e..7b97f58 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
@@ -162,8 +162,7 @@
             return if ((value and 0x3fUL) == 0UL) {
                 ((value shr 48) and 0xffUL).toFloat() / 255.0f
             } else {
-                Float16(((value shr 48) and 0xffffUL).toShort())
-                    .toFloat()
+                halfToFloat(((value shr 48) and 0xffffUL).toShort())
             }
         }
 
@@ -185,8 +184,7 @@
             return if ((value and 0x3fUL) == 0UL) {
                 ((value shr 40) and 0xffUL).toFloat() / 255.0f
             } else {
-                Float16(((value shr 32) and 0xffffUL).toShort())
-                    .toFloat()
+                halfToFloat(((value shr 32) and 0xffffUL).toShort())
             }
         }
 
@@ -208,8 +206,7 @@
             return if ((value and 0x3fUL) == 0UL) {
                 ((value shr 32) and 0xffUL).toFloat() / 255.0f
             } else {
-                Float16(((value shr 16) and 0xffffUL).toShort())
-                    .toFloat()
+                halfToFloat(((value shr 16) and 0xffffUL).toShort())
             }
         }
 
@@ -395,7 +392,7 @@
 
 /**
  * Create a [Color] by passing individual [red], [green], [blue], [alpha], and [colorSpace]
- * components. The default [color space][ColorSpace] is [SRGB][ColorSpaces.Srgb] and
+ * components. The default [color space][ColorSpace] is [sRGB][ColorSpaces.Srgb] and
  * the default [alpha] is `1.0` (opaque). [colorSpace] must have a [ColorSpace.componentCount] of
  * 3.
  */
@@ -407,7 +404,7 @@
     alpha: Float = 1f,
     colorSpace: ColorSpace = ColorSpaces.Srgb
 ): Color {
-    require(
+    requirePrecondition(
         red in colorSpace.getMinValue(0)..colorSpace.getMaxValue(0) &&
             green in colorSpace.getMinValue(1)..colorSpace.getMaxValue(1) &&
             blue in colorSpace.getMinValue(2)..colorSpace.getMaxValue(2) &&
@@ -419,41 +416,37 @@
     if (colorSpace.isSrgb) {
         val argb = (
             ((alpha * 255.0f + 0.5f).toInt() shl 24) or
-                ((red * 255.0f + 0.5f).toInt() shl 16) or
-                ((green * 255.0f + 0.5f).toInt() shl 8) or
-                (blue * 255.0f + 0.5f).toInt()
-            )
+            ((red * 255.0f + 0.5f).toInt() shl 16) or
+            ((green * 255.0f + 0.5f).toInt() shl 8) or
+            (blue * 255.0f + 0.5f).toInt()
+        )
         return Color(value = (argb.toULong() and 0xffffffffUL) shl 32)
     }
 
-    require(colorSpace.componentCount == 3) {
+    requirePrecondition(colorSpace.componentCount == 3) {
         "Color only works with ColorSpaces with 3 components"
     }
 
     val id = colorSpace.id
-    require(id != ColorSpace.MinId) {
+    requirePrecondition(id != ColorSpace.MinId) {
         "Unknown color space, please use a color space in ColorSpaces"
     }
 
-    val r = Float16(red)
-    val g = Float16(green)
-    val b = Float16(blue)
+    val r = floatToHalf(red)
+    val g = floatToHalf(green)
+    val b = floatToHalf(blue)
 
     val a = (max(0.0f, min(alpha, 1.0f)) * 1023.0f + 0.5f).toInt()
 
     // Suppress sign extension
     return Color(
         value = (
-            ((r.halfValue.toULong() and 0xffffUL) shl 48) or (
-                (g.halfValue.toULong() and 0xffffUL) shl 32
-                ) or (
-                (b.halfValue.toULong() and 0xffffUL) shl 16
-                ) or (
-                (a.toULong() and 0x3ffUL) shl 6
-                ) or (
-                id.toULong() and 0x3fUL
-                )
-            )
+            ((r.toULong() and 0xffffUL) shl 48) or
+            ((g.toULong() and 0xffffUL) shl 32) or
+            ((b.toULong() and 0xffffUL) shl 16) or
+            ((a.toULong() and 0x03ffUL) shl 6) or
+            (id.toULong() and 0x003fUL)
+        )
     )
 }
 
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 d48fce3..8aabeca6 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
@@ -85,7 +85,7 @@
  *
  * This table shows that numbers higher than 1024 lose all fractional precision.
  */
[email protected]
+@JvmInline
 internal value class Float16(val halfValue: Short) : Comparable<Float16> {
 
     /**
@@ -94,11 +94,7 @@
      *
      * @param value The value to be represented by the `Float16`
      */
-    constructor(value: Float) : this(
-        floatToHalf(
-            value
-        )
-    )
+    constructor(value: Float) : this(floatToHalf(value))
 
     /**
      * Constructs a newly allocated `Float16` object that
@@ -115,9 +111,7 @@
      * @return The half-precision float value represented by this object
      * converted to type `Byte`
      */
-    fun toByte(): Byte {
-        return toFloat().toInt().toByte()
-    }
+    fun toByte(): Byte = toFloat().toInt().toByte()
 
     /**
      * Returns the value of this `Float16` as a `Short` after
@@ -126,9 +120,7 @@
      * @return The half-precision float value represented by this object
      * converted to type `Short`
      */
-    fun toShort(): Short {
-        return toFloat().toInt().toShort()
-    }
+    fun toShort(): Short = toFloat().toInt().toShort()
 
     /**
      * Returns the value of this `Float16` as a `Int` after
@@ -137,9 +129,7 @@
      * @return The half-precision float value represented by this object
      * converted to type `Int`
      */
-    fun toInt(): Int {
-        return toFloat().toInt()
-    }
+    fun toInt(): Int = toFloat().toInt()
 
     /**
      * Returns the value of this `Float16` as a `Long` after
@@ -148,9 +138,7 @@
      * @return The half-precision float value represented by this object
      * converted to type `Long`
      */
-    fun toLong(): Long {
-        return toFloat().toLong()
-    }
+    fun toLong(): Long = toFloat().toLong()
 
     /**
      * Returns the value of this `Float16` as a `Float` after
@@ -161,9 +149,9 @@
      */
     fun toFloat(): Float {
         val bits = halfValue.toInt() and 0xffff
-        val s = bits and FP16_SIGN_MASK
-        val e = bits.ushr(FP16_EXPONENT_SHIFT) and FP16_EXPONENT_MASK
-        val m = bits and FP16_SIGNIFICAND_MASK
+        val s = bits and Fp16SignMask
+        val e = bits.ushr(Fp16ExponentShift) and Fp16ExponentMask
+        val m = bits and Fp16SignificandMask
 
         var outE = 0
         var outM = 0
@@ -171,8 +159,8 @@
         if (e == 0) { // Denormal or 0
             if (m != 0) {
                 // Convert denorm fp16 into normalized fp32
-                var o = floatFromBits(FP32_DENORMAL_MAGIC + m)
-                o -= FP32_DENORMAL_FLOAT
+                var o = floatFromBits(Fp32DenormalMagic + m)
+                o -= Fp32DenormalFloat
                 return if (s == 0) o else -o
             }
         } else {
@@ -180,14 +168,14 @@
             if (e == 0x1f) { // Infinite or NaN
                 outE = 0xff
                 if (outM != 0) { // SNaNs are quieted
-                    outM = outM or FP32_QNAN_MASK
+                    outM = outM or Fp32QNaNMask
                 }
             } else {
-                outE = e - FP16_EXPONENT_BIAS + FP32_EXPONENT_BIAS
+                outE = e - Fp16ExponentBias + Fp32ExponentBias
             }
         }
 
-        val out = s shl 16 or (outE shl FP32_EXPONENT_SHIFT) or outM
+        val out = s shl 16 or (outE shl Fp32ExponentShift) or outM
         return floatFromBits(out)
     }
 
@@ -198,9 +186,7 @@
      * @return The half-precision float value represented by this object
      * converted to type `Double`
      */
-    fun toDouble(): Double {
-        return toFloat().toDouble()
-    }
+    fun toDouble(): Double = toFloat().toDouble()
 
     /**
      * Returns a representation of the half-precision float value
@@ -212,12 +198,10 @@
      *
      * @return The bits that represent the half-precision float value
      */
-    fun toBits(): Int {
-        return if (isNaN()) {
-            NaN.halfValue.toInt()
-        } else {
-            halfValue.toInt() and 0xffff
-        }
+    fun toBits(): Int = if (isNaN()) {
+        NaN.halfValue.toInt()
+    } else {
+        halfValue.toInt() and 0xffff
     }
 
     /**
@@ -226,9 +210,7 @@
      *
      * @return The bits that represent the half-precision float value
      */
-    fun toRawBits(): Int {
-        return halfValue.toInt() and 0xffff
-    }
+    fun toRawBits(): Int = halfValue.toInt() and 0xffff
 
     /**
      * Returns a string representation of the specified half-precision
@@ -236,26 +218,24 @@
      *
      * @return A string representation of this `Float16` object
      */
-    override fun toString(): String {
-        return toFloat().toString()
-    }
+    override fun toString(): String = toFloat().toString()
 
     /**
      * Compares to another half-precision float value. The following
      * conditions apply during the comparison:
      *
      *  * [NaN] is considered by this method to be equal to itself and greater
-     * than all other half-precision float values (including `#PositiveInfinity`)
+     * than all other half-precision float values (including [PositiveInfinity])
      *  * [PositiveZero] is considered by this method to be greater than
      * [NegativeZero].
      *
      * @param other The half-precision float value to compare to the half-precision value
      * represented by this `Float16` object
      *
-     * @return The value `0` if `this` is numerically equal to `h`; a
-     * value less than `0` if `this` is numerically less than `h`;
+     * @return The value `0` if `this` is numerically equal to [other]; a
+     * value less than `0` if `this` is numerically less than [other];
      * and a value greater than `0` if `this` is numerically greater
-     * than `h`
+     * than [other]
      */
     override operator fun compareTo(other: Float16): Int {
         if (isNaN()) {
@@ -263,9 +243,7 @@
         } else if (other.isNaN()) {
             return -1
         }
-        return toCompareValue(halfValue).compareTo(
-            toCompareValue(other.halfValue)
-        )
+        return toCompareValue(halfValue).compareTo(toCompareValue(other.halfValue))
     }
 
     /**
@@ -291,9 +269,9 @@
     fun withSign(sign: Float16): Float16 =
         Float16(
             (
-                sign.halfValue.toInt() and FP16_SIGN_MASK or
-                    (halfValue.toInt() and FP16_COMBINED)
-                ).toShort()
+                sign.halfValue.toInt() and Fp16SignMask or
+                    (halfValue.toInt() and Fp16Combined)
+            ).toShort()
         )
 
     /**
@@ -307,7 +285,7 @@
      * the result is positive infinity (see [PositiveInfinity])
      */
     fun absoluteValue(): Float16 {
-        return Float16((halfValue.toInt() and FP16_COMBINED).toShort())
+        return Float16((halfValue.toInt() and Fp16Combined).toShort())
     }
 
     /**
@@ -330,7 +308,7 @@
         var result = bits
 
         if (e < 0x3c00) {
-            result = result and FP16_SIGN_MASK
+            result = result and Fp16SignMask
             result = result or (0x3c00 and if (e >= 0x3800) 0xffff else 0x0)
         } else if (e < 0x6400) {
             e = 25 - (e shr 10)
@@ -362,7 +340,7 @@
         var result = bits
 
         if (e < 0x3c00) {
-            result = result and FP16_SIGN_MASK
+            result = result and Fp16SignMask
             result = result or (0x3c00 and -((bits shr 15).inv() and if (e != 0) 1 else 0))
         } else if (e < 0x6400) {
             e = 25 - (e shr 10)
@@ -394,7 +372,7 @@
         var result = bits
 
         if (e < 0x3c00) {
-            result = result and FP16_SIGN_MASK
+            result = result and Fp16SignMask
             result = result or (0x3c00 and if (bits > 0x8000) 0xffff else 0x0)
         } else if (e < 0x6400) {
             e = 25 - (e shr 10)
@@ -425,7 +403,7 @@
         var result = bits
 
         if (e < 0x3c00) {
-            result = result and FP16_SIGN_MASK
+            result = result and Fp16SignMask
         } else if (e < 0x6400) {
             e = 25 - (e shr 10)
             val mask = (1 shl e) - 1
@@ -443,19 +421,15 @@
      * returns [MinExponent] - 1.
      */
     val exponent: Int
-        get() {
-            return (halfValue.toInt().ushr(FP16_EXPONENT_SHIFT) and FP16_EXPONENT_MASK) -
-                FP16_EXPONENT_BIAS
-        }
+        get() = (halfValue.toInt().ushr(Fp16ExponentShift) and Fp16ExponentMask) -
+            Fp16ExponentBias
 
     /**
      * The significand, or mantissa, used in the representation
      * of this half-precision float value.
      */
     val significand: Int
-        get() {
-            return halfValue.toInt() and FP16_SIGNIFICAND_MASK
-        }
+        get() = halfValue.toInt() and Fp16SignificandMask
 
     /**
      * Returns true if this `Float16` value represents a Not-a-Number,
@@ -463,9 +437,7 @@
      *
      * @return True if the value is a NaN, false otherwise
      */
-    fun isNaN(): Boolean {
-        return halfValue.toInt() and FP16_COMBINED > FP16_EXPONENT_MAX
-    }
+    fun isNaN(): Boolean = halfValue.toInt() and Fp16Combined > Fp16ExponentMax
 
     /**
      * Returns true if the half-precision float value represents
@@ -474,9 +446,7 @@
      * @return True if the value is positive infinity or negative infinity,
      * false otherwise
      */
-    fun isInfinite(): Boolean {
-        return halfValue.toInt() and FP16_COMBINED == FP16_EXPONENT_MAX
-    }
+    fun isInfinite(): Boolean = halfValue.toInt() and Fp16Combined == Fp16ExponentMax
 
     /**
      * Returns false if the half-precision float value represents
@@ -485,9 +455,7 @@
      * @return False if the value is positive infinity or negative infinity,
      * true otherwise
      */
-    fun isFinite(): Boolean {
-        return halfValue.toInt() and FP16_COMBINED != FP16_EXPONENT_MAX
-    }
+    fun isFinite(): Boolean = halfValue.toInt() and Fp16Combined != Fp16ExponentMax
 
     /**
      * Returns true if the half-precision float value is normalized
@@ -499,8 +467,8 @@
      * @return True if the value is normalized, false otherwise
      */
     fun isNormalized(): Boolean {
-        return halfValue.toInt() and FP16_EXPONENT_MAX != 0 &&
-            halfValue.toInt() and FP16_EXPONENT_MAX != FP16_EXPONENT_MAX
+        return halfValue.toInt() and Fp16ExponentMax != 0 &&
+            halfValue.toInt() and Fp16ExponentMax != Fp16ExponentMax
     }
 
     /**
@@ -532,9 +500,9 @@
         val o = StringBuilder()
 
         val bits = halfValue.toInt() and 0xffff
-        val s = bits.ushr(FP16_SIGN_SHIFT)
-        val e = bits.ushr(FP16_EXPONENT_SHIFT) and FP16_EXPONENT_MASK
-        val m = bits and FP16_SIGNIFICAND_MASK
+        val s = bits.ushr(Fp16SignShift)
+        val e = bits.ushr(Fp16ExponentShift) and Fp16ExponentMask
+        val m = bits and Fp16SignificandMask
 
         if (e == 0x1f) { // Infinite or NaN
             if (m == 0) {
@@ -559,7 +527,7 @@
                 val significand = m.toString(16)
                 o.append(significand.replaceFirst("0{2,}$".toRegex(), ""))
                 o.append('p')
-                o.append((e - FP16_EXPONENT_BIAS).toString())
+                o.append((e - Fp16ExponentBias).toString())
             }
         }
 
@@ -623,78 +591,118 @@
          * Positive 0 of type half-precision float.
          */
         val PositiveZero = Float16(0x0000.toShort())
+    }
+}
 
-        private val One = Float16(1f)
-        private val NegativeOne = Float16(-1f)
+private val One = Float16(1f)
+private val NegativeOne = Float16(-1f)
 
-        private const val FP16_SIGN_SHIFT = 15
-        private const val FP16_SIGN_MASK = 0x8000
-        private const val FP16_EXPONENT_SHIFT = 10
-        private const val FP16_EXPONENT_MASK = 0x1f
-        private const val FP16_SIGNIFICAND_MASK = 0x3ff
-        private const val FP16_EXPONENT_BIAS = 15
-        private const val FP16_COMBINED = 0x7fff
-        private const val FP16_EXPONENT_MAX = 0x7c00
+private const val Fp16SignShift = 15
+private const val Fp16SignMask = 0x8000
+private const val Fp16ExponentShift = 10
+private const val Fp16ExponentMask = 0x1f
+private const val Fp16SignificandMask = 0x3ff
+private const val Fp16ExponentBias = 15
+private const val Fp16Combined = 0x7fff
+private const val Fp16ExponentMax = 0x7c00
 
-        private const val FP32_SIGN_SHIFT = 31
-        private const val FP32_EXPONENT_SHIFT = 23
-        private const val FP32_EXPONENT_MASK = 0xff
-        private const val FP32_SIGNIFICAND_MASK = 0x7fffff
-        private const val FP32_EXPONENT_BIAS = 127
-        private const val FP32_QNAN_MASK = 0x400000
+private const val Fp32SignShift = 31
+private const val Fp32ExponentShift = 23
+private const val Fp32ExponentMask = 0xff
+private const val Fp32SignificandMask = 0x7fffff
+private const val Fp32ExponentBias = 127
+private const val Fp32QNaNMask = 0x400000
 
-        private const val FP32_DENORMAL_MAGIC = 126 shl 23
-        private val FP32_DENORMAL_FLOAT = floatFromBits(FP32_DENORMAL_MAGIC)
+private const val Fp32DenormalMagic = 126 shl 23
+private val Fp32DenormalFloat = floatFromBits(Fp32DenormalMagic)
 
-        private fun toCompareValue(value: Short): Int {
-            return if (value.toInt() and FP16_SIGN_MASK != 0) {
-                0x8000 - (value.toInt() and 0xffff)
+@Suppress("NOTHING_TO_INLINE")
+private inline fun toCompareValue(value: Short): Int {
+    return if (value.toInt() and Fp16SignMask != 0) {
+        0x8000 - (value.toInt() and 0xffff)
+    } else {
+        value.toInt() and 0xffff
+    }
+}
+
+/**
+ * Convert a single-precision float to a half-precision float, stored as
+ * [Short] data type to hold its 16 bits.
+ */
+internal fun floatToHalf(f: Float): Short {
+    val bits = f.toRawBits()
+    val s = bits.ushr(Fp32SignShift)
+    var e = bits.ushr(Fp32ExponentShift) and Fp32ExponentMask
+    var m = bits and Fp32SignificandMask
+
+    var outE = 0
+    var outM = 0
+
+    if (e == 0xff) { // Infinite or NaN
+        outE = 0x1f
+        outM = if (m != 0) 0x200 else 0
+    } else {
+        e = e - Fp32ExponentBias + Fp16ExponentBias
+        if (e >= 0x1f) { // Overflow
+            outE = 0x31
+        } else if (e <= 0) { // Underflow
+            if (e < -10) {
+                // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
             } else {
-                value.toInt() and 0xffff
+                // The fp32 value is a normalized float less than MIN_NORMAL,
+                // we convert to a denorm fp16
+                m = m or 0x800000 shr 1 - e
+                if (m and 0x1000 != 0) m += 0x2000
+                outM = m shr 13
             }
-        }
-
-        private fun floatToHalf(f: Float): Short {
-            val bits = f.toRawBits()
-            val s = bits.ushr(FP32_SIGN_SHIFT)
-            var e = bits.ushr(FP32_EXPONENT_SHIFT) and FP32_EXPONENT_MASK
-            var m = bits and FP32_SIGNIFICAND_MASK
-
-            var outE = 0
-            var outM = 0
-
-            if (e == 0xff) { // Infinite or NaN
-                outE = 0x1f
-                outM = if (m != 0) 0x200 else 0
-            } else {
-                e = e - FP32_EXPONENT_BIAS + FP16_EXPONENT_BIAS
-                if (e >= 0x1f) { // Overflow
-                    outE = 0x31
-                } else if (e <= 0) { // Underflow
-                    if (e < -10) {
-                        // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
-                    } else {
-                        // The fp32 value is a normalized float less than MIN_NORMAL,
-                        // we convert to a denorm fp16
-                        m = m or 0x800000 shr 1 - e
-                        if (m and 0x1000 != 0) m += 0x2000
-                        outM = m shr 13
-                    }
-                } else {
-                    outE = e
-                    outM = m shr 13
-                    if (m and 0x1000 != 0) {
-                        // Round to nearest "0.5" up
-                        var out = outE shl FP16_EXPONENT_SHIFT or outM
-                        out++
-                        return (out or (s shl FP16_SIGN_SHIFT)).toShort()
-                    }
-                }
+        } else {
+            outE = e
+            outM = m shr 13
+            if (m and 0x1000 != 0) {
+                // Round to nearest "0.5" up
+                var out = outE shl Fp16ExponentShift or outM
+                out++
+                return (out or (s shl Fp16SignShift)).toShort()
             }
-
-            return (s shl FP16_SIGN_SHIFT or (outE shl FP16_EXPONENT_SHIFT) or outM).toShort()
         }
     }
+
+    return (s shl Fp16SignShift or (outE shl Fp16ExponentShift) or outM).toShort()
+}
+
+/**
+ * Convert a half-precision float to a single-precision float.
+ */
+internal fun halfToFloat(h: Short): Float {
+    val bits = h.toInt() and 0xffff
+    val s = bits and Fp16SignMask
+    val e = bits.ushr(Fp16ExponentShift) and Fp16ExponentMask
+    val m = bits and Fp16SignificandMask
+
+    var outE = 0
+    var outM = 0
+
+    if (e == 0) { // Denormal or 0
+        if (m != 0) {
+            // Convert denorm fp16 into normalized fp32
+            var o = floatFromBits(Fp32DenormalMagic + m)
+            o -= Fp32DenormalFloat
+            return if (s == 0) o else -o
+        }
+    } else {
+        outM = m shl 13
+        if (e == 0x1f) { // Infinite or NaN
+            outE = 0xff
+            if (outM != 0) { // SNaNs are quieted
+                outM = outM or Fp32QNaNMask
+            }
+        } else {
+            outE = e - Fp16ExponentBias + Fp32ExponentBias
+        }
+    }
+
+    val out = s shl 16 or (outE shl Fp32ExponentShift) or outM
+    return floatFromBits(out)
 }
 
 /**
@@ -712,7 +720,6 @@
     if (x.isNaN() || y.isNaN()) {
         return Float16.NaN
     }
-
     return if (x <= y) x else y
 }
 
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/InlineClassHelper.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/InlineClassHelper.kt
new file mode 100644
index 0000000..724538e
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/InlineClassHelper.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// This function exists so we do *not* inline the throw. It keeps
+// the call site much smaller and since it's the slow path anyway,
+// we don't mind the extra function call
+internal fun throwIllegalArgumentException(message: String) {
+    throw IllegalArgumentException(message)
+}
+
+// Like Kotlin's require() but without the .toString() call
+@Suppress("BanInlineOptIn") // same opt-in as using Kotlin's require()
+@OptIn(ExperimentalContracts::class)
+internal inline fun requirePrecondition(value: Boolean, lazyMessage: () -> String) {
+    contract {
+        returns() implies value
+    }
+    if (!value) {
+        throwIllegalArgumentException(lazyMessage())
+    }
+}
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index b1cd79d..666a657 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -3,6 +3,8 @@
 
   public final class ActionsKt {
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performClick(androidx.compose.ui.test.SemanticsNodeInteraction);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performCustomAccessibilityActionLabelled(androidx.compose.ui.test.SemanticsNodeInteraction, String label);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performCustomAccessibilityActionWhere(androidx.compose.ui.test.SemanticsNodeInteraction, optional String? predicateDescription, kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> labelPredicate);
     method @Deprecated public static androidx.compose.ui.test.SemanticsNodeInteraction performGesture(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.GestureScope,kotlin.Unit> block);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performKeyInput(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performMouseInput(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.MouseInjectionScope,kotlin.Unit> block);
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index 78faf62..b8f4e37 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -3,6 +3,8 @@
 
   public final class ActionsKt {
     method public static androidx.compose.ui.test.SemanticsNodeInteraction performClick(androidx.compose.ui.test.SemanticsNodeInteraction);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performCustomAccessibilityActionLabelled(androidx.compose.ui.test.SemanticsNodeInteraction, String label);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performCustomAccessibilityActionWhere(androidx.compose.ui.test.SemanticsNodeInteraction, optional String? predicateDescription, kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> labelPredicate);
     method @Deprecated public static androidx.compose.ui.test.SemanticsNodeInteraction performGesture(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.GestureScope,kotlin.Unit> block);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performKeyInput(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.KeyInjectionScope,kotlin.Unit> block);
     method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static androidx.compose.ui.test.SemanticsNodeInteraction performMouseInput(androidx.compose.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.MouseInjectionScope,kotlin.Unit> block);
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/actions/CustomAccessibilityActionsTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/actions/CustomAccessibilityActionsTest.kt
new file mode 100644
index 0000000..4bffee2
--- /dev/null
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/actions/CustomAccessibilityActionsTest.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.test.actions
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performCustomAccessibilityActionLabelled
+import androidx.compose.ui.test.performCustomAccessibilityActionWhere
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTestApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomAccessibilityActionsTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val tag = "tag"
+
+    @Test
+    fun performCustomAccessibilityActionLabelled_failsWhenNoNodeMatches() {
+        rule.setContent {
+            Box(
+                Modifier.semantics {
+                    customActions = listOf(CustomAccessibilityAction("action") { true })
+                }
+            )
+        }
+
+        val interaction = rule.onNodeWithTag(tag)
+        val error = assertFailsWith<AssertionError> {
+            interaction.performCustomAccessibilityActionLabelled("action")
+        }
+        assertThat(error).hasMessageThat().contains("could not find any node that satisfies")
+    }
+
+    @Test
+    fun performCustomAccessibilityActionLabelled_failsWhenNoActionMatches() {
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(CustomAccessibilityAction("action") { true })
+                    }
+            )
+        }
+
+        val error = assertFailsWith<AssertionError> {
+            rule.onNodeWithTag(tag).performCustomAccessibilityActionLabelled("not action")
+        }
+        assertThat(error).hasMessageThat().startsWith(
+            "No custom accessibility actions matched [label is \"not action\"]"
+        )
+    }
+
+    @Test
+    fun performCustomAccessibilityActionLabelled_failsWhenMultipleActionsMatch() {
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(
+                            CustomAccessibilityAction("action") { true },
+                            CustomAccessibilityAction("action") { true },
+                        )
+                    }
+            )
+        }
+
+        val error = assertFailsWith<AssertionError> {
+            rule.onNodeWithTag(tag).performCustomAccessibilityActionLabelled("action")
+        }
+        assertThat(error).hasMessageThat().startsWith(
+            "Expected exactly one custom accessibility action to match [label is \"action\"], " +
+                "but found 2."
+        )
+    }
+
+    @Test
+    fun performCustomAccessibilityActionLabelled_invokesActionWhenExactlyOneActionMatches() {
+        var fooInvocationCount = 0
+        var barInvocationCount = 0
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(
+                            CustomAccessibilityAction("foo") { fooInvocationCount++; true },
+                            CustomAccessibilityAction("bar") { barInvocationCount++; true },
+                        )
+                    }
+            )
+        }
+
+        rule.onNodeWithTag(tag).performCustomAccessibilityActionLabelled("foo")
+
+        assertThat(fooInvocationCount).isEqualTo(1)
+        assertThat(barInvocationCount).isEqualTo(0)
+    }
+
+    @Test
+    fun performCustomAccessibilityActionLabelled_doesntFailWhenActionReturnsFalse() {
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(
+                            CustomAccessibilityAction("action") { false },
+                        )
+                    }
+            )
+        }
+
+        rule.onNodeWithTag(tag).performCustomAccessibilityActionLabelled("action")
+    }
+
+    @Test
+    fun performCustomAccessibilityActionWhere_failsWhenNoNodeMatches() {
+        rule.setContent {
+            Box(
+                Modifier.semantics {
+                    customActions = listOf(CustomAccessibilityAction("action") { true })
+                }
+            )
+        }
+
+        val interaction = rule.onNodeWithTag(tag)
+        val error = assertFailsWith<AssertionError> {
+            interaction.performCustomAccessibilityActionWhere("description") { true }
+        }
+        assertThat(error).hasMessageThat().contains("could not find any node that satisfies")
+    }
+
+    @Test
+    fun performCustomAccessibilityActionWhere_failsWhenNoActionMatches() {
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(CustomAccessibilityAction("action") { true })
+                    }
+            )
+        }
+
+        val error = assertFailsWith<AssertionError> {
+            rule.onNodeWithTag(tag).performCustomAccessibilityActionWhere("description") { false }
+        }
+        assertThat(error).hasMessageThat().startsWith(
+            "No custom accessibility actions matched [description]"
+        )
+    }
+
+    @Test
+    fun performCustomAccessibilityActionWhere_failsWhenMultipleActionsMatch() {
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(
+                            CustomAccessibilityAction("action") { true },
+                            CustomAccessibilityAction("action") { true },
+                        )
+                    }
+            )
+        }
+
+        val error = assertFailsWith<AssertionError> {
+            rule.onNodeWithTag(tag).performCustomAccessibilityActionWhere("description") { true }
+        }
+        assertThat(error).hasMessageThat().startsWith(
+            "Expected exactly one custom accessibility action to match [description], " +
+                "but found 2."
+        )
+    }
+
+    @Test
+    fun performCustomAccessibilityActionWhere_invokesActionWhenExactlyOneActionMatches() {
+        var fooInvocationCount = 0
+        var barInvocationCount = 0
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(
+                            CustomAccessibilityAction("foo") { fooInvocationCount++; true },
+                            CustomAccessibilityAction("bar") { barInvocationCount++; true },
+                        )
+                    }
+            )
+        }
+
+        rule.onNodeWithTag(tag).performCustomAccessibilityActionWhere("description") { it == "foo" }
+
+        assertThat(fooInvocationCount).isEqualTo(1)
+        assertThat(barInvocationCount).isEqualTo(0)
+    }
+
+    @Test
+    fun performCustomAccessibilityActionWhere_doesntFailWhenActionReturnsFalse() {
+        rule.setContent {
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .semantics {
+                        customActions = listOf(
+                            CustomAccessibilityAction("action") { false },
+                        )
+                    }
+            )
+        }
+
+        rule.onNodeWithTag(tag).performCustomAccessibilityActionWhere("description") { true }
+    }
+}
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
index 5f9f27e..a646fa8 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt
@@ -22,8 +22,10 @@
 import androidx.compose.ui.layout.boundsInParent
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.semantics.AccessibilityAction
+import androidx.compose.ui.semantics.CustomAccessibilityAction
 import androidx.compose.ui.semantics.ScrollAxisRange
 import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsActions.CustomActions
 import androidx.compose.ui.semantics.SemanticsActions.ScrollBy
 import androidx.compose.ui.semantics.SemanticsActions.ScrollToIndex
 import androidx.compose.ui.semantics.SemanticsNode
@@ -625,6 +627,70 @@
     return this
 }
 
+/**
+ * Finds the [CustomAccessibilityAction] in the node's [CustomActions] list whose label is equal
+ * to [label] and then invokes it.
+ *
+ * To use your own logic to find the action to perform instead of matching on the full label, use
+ * [performCustomAccessibilityActionWhere].
+ *
+ * @param label The exact label of the [CustomAccessibilityAction] to perform.
+ *
+ * @throws AssertionError If no [SemanticsNode] is found, or no [CustomAccessibilityAction] has
+ * [label], or more than one [CustomAccessibilityAction] has [label].
+ *
+ * @see performCustomAccessibilityActionWhere
+ */
+@ExperimentalTestApi
+fun SemanticsNodeInteraction.performCustomAccessibilityActionLabelled(
+    label: String
+): SemanticsNodeInteraction =
+    performCustomAccessibilityActionWhere("label is \"$label\"") { it == label }
+
+/**
+ * Finds the [CustomAccessibilityAction] in the node's [CustomActions] list whose label satisfies a
+ * predicate function and then invokes it.
+ *
+ * @param predicateDescription A description of [labelPredicate] that will be included in the error
+ * message if zero or >1 actions match.
+ * @param labelPredicate A predicate function used to select the [CustomAccessibilityAction] to
+ * perform.
+ *
+ * @throws AssertionError If no [SemanticsNode] is found, or no [CustomAccessibilityAction] matches
+ * [labelPredicate], or more than one [CustomAccessibilityAction] matches [labelPredicate].
+ *
+ * @see performCustomAccessibilityActionLabelled
+ */
+@ExperimentalTestApi
+fun SemanticsNodeInteraction.performCustomAccessibilityActionWhere(
+    predicateDescription: String? = null,
+    labelPredicate: (label: String) -> Boolean
+): SemanticsNodeInteraction {
+    val node = fetchSemanticsNode()
+    val actions = node.config[CustomActions]
+    val matchingActions = actions.filter { labelPredicate(it.label) }
+    if (matchingActions.isEmpty()) {
+        throw AssertionError(
+            buildGeneralErrorMessage(
+                "No custom accessibility actions matched [$predicateDescription].",
+                selector,
+                node
+            )
+        )
+    } else if (matchingActions.size > 1) {
+        throw AssertionError(
+            buildGeneralErrorMessage(
+                "Expected exactly one custom accessibility action to match" +
+                    " [$predicateDescription], but found ${matchingActions.size}.",
+                selector,
+                node
+            )
+        )
+    }
+    matchingActions[0].action()
+    return this
+}
+
 // TODO(200928505): get a more accurate indication if it is a lazy list
 private val SemanticsNode.isLazyList: Boolean
     get() = ScrollBy in config && ScrollToIndex in config
diff --git a/compose/ui/ui-text/lint-baseline.xml b/compose/ui/ui-text/lint-baseline.xml
index 26fe7b7..40b9c16 100644
--- a/compose/ui/ui-text/lint-baseline.xml
+++ b/compose/ui/ui-text/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-alpha04" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha04)" variant="all" version="8.3.0-alpha04">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
-        errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
-        errorLine2="                                       ~~~~~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt"/>
-    </issue>
+<issues format="6" by="lint 8.3.0-alpha10" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha10)" variant="all" version="8.3.0-alpha10">
 
     <issue
         id="NewApi"
@@ -40,10 +31,10 @@
     <issue
         id="NewApi"
         message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
-        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
-        errorLine2="                             ~~~~~~~~~">
+        errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                                       ~~~~~~~~~">
         <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt"/>
     </issue>
 
     <issue
@@ -57,6 +48,15 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
+        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                             ~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 29 (current min is 21): `android.graphics.Paint#setBlendMode`"
         errorLine1="        textPaint.blendMode = BlendMode.DstOver"
         errorLine2="                  ~~~~~~~~~">
@@ -66,6 +66,15 @@
 
     <issue
         id="NewApi"
+        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.DstOver)"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
         errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.DstOver)"
         errorLine2="                             ~~~~~~~~~">
@@ -74,12 +83,93 @@
     </issue>
 
     <issue
-        id="NewApi"
-        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
-        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.DstOver)"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~">
+        id="PrimitiveInCollection"
+        message="field paragraphEnds with type List&lt;Integer>: replace with IntList"
+        errorLine1="    private val paragraphEnds: List&lt;Int>"
+        errorLine2="                               ~~~~~~~~~">
         <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/LayoutHelper.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="variable lineFeeds with type List&lt;Integer>: replace with IntList"
+        errorLine1="        val lineFeeds = mutableListOf&lt;Int>()"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/LayoutHelper.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="return type List&lt;Integer> of breakInWords: replace with IntList"
+        errorLine1="    private fun breakInWords(layoutHelper: LayoutHelper): List&lt;Int> {"
+        errorLine2="                                                          ~~~~~~~~~">
+        <location
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="variable words with type List&lt;? extends Integer>: replace with IntList"
+        errorLine1="        val words = breakWithBreakIterator(text, BreakIterator.getLineInstance(Locale.getDefault()))"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="variable set with type TreeSet&lt;Integer>: replace with IntSet"
+        errorLine1="        val set = TreeSet&lt;Int>().apply {"
+        errorLine2="        ^">
+        <location
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="return type List&lt;Integer> of breakWithBreakIterator: replace with IntList"
+        errorLine1="    private fun breakWithBreakIterator(text: CharSequence, breaker: BreakIterator): List&lt;Int> {"
+        errorLine2="                                                                                    ~~~~~~~~~">
+        <location
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="variable res with type List&lt;Integer>: replace with IntList"
+        errorLine1="        val res = mutableListOf(0)"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="return type List&lt;Integer> of breakOffsets: replace with IntList"
+        errorLine1="    fun breakOffsets(layoutHelper: LayoutHelper, segmentType: SegmentType): List&lt;Int> {"
+        errorLine2="                                                                            ~~~~~~~~~">
+        <location
+            file="${:compose:ui:ui-text*debug*MAIN*sourceProvider*0*javaDir*4}/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="variable list with type List&lt;? extends Float>: replace with FloatList"
+        errorLine1="        @Suppress(&quot;UNCHECKED_CAST&quot;)"
+        errorLine2="        ^">
+        <location
+            file="src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt"/>
+    </issue>
+
+    <issue
+        id="PrimitiveInCollection"
+        message="return type List&lt;FontStyle> of values: replace with IntList"
+        errorLine1="        fun values(): List&lt;FontStyle> = listOf(Normal, Italic)"
+        errorLine2="                      ~~~~~~~~~~~~~~~">
+        <location
+            file="src/commonMain/kotlin/androidx/compose/ui/text/font/FontStyle.kt"/>
     </issue>
 
     <issue
@@ -93,11 +183,11 @@
 
     <issue
         id="PrimitiveInCollection"
-        message="return type List&lt;FontStyle> of values: replace with IntList"
-        errorLine1="        fun values(): List&lt;FontStyle> = listOf(Normal, Italic)"
+        message="return type List&lt;TextAlign> of values: replace with IntList"
+        errorLine1="        fun values(): List&lt;TextAlign> = listOf(Left, Right, Center, Justify, Start, End)"
         errorLine2="                      ~~~~~~~~~~~~~~~">
         <location
-            file="src/commonMain/kotlin/androidx/compose/ui/text/font/FontStyle.kt"/>
+            file="src/commonMain/kotlin/androidx/compose/ui/text/style/TextAlign.kt"/>
     </issue>
 
     <issue
@@ -127,94 +217,4 @@
             file="src/jvmMain/kotlin/androidx/compose/ui/text/JvmAnnotatedString.jvm.kt"/>
     </issue>
 
-    <issue
-        id="PrimitiveInCollection"
-        message="field paragraphEnds with type List&lt;Integer>: replace with IntList"
-        errorLine1="    private val paragraphEnds: List&lt;Int>"
-        errorLine2="                               ~~~~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable lineFeeds with type List&lt;Integer>: replace with IntList"
-        errorLine1="        val lineFeeds = mutableListOf&lt;Int>()"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable list with type List&lt;? extends Float>: replace with FloatList"
-        errorLine1="        @Suppress(&quot;UNCHECKED_CAST&quot;)"
-        errorLine2="        ^">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type List&lt;Integer> of breakInWords: replace with IntList"
-        errorLine1="    private fun breakInWords(layoutHelper: LayoutHelper): List&lt;Int> {"
-        errorLine2="                                                          ~~~~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable words with type List&lt;? extends Integer>: replace with IntList"
-        errorLine1="        val words = breakWithBreakIterator(text, BreakIterator.getLineInstance(Locale.getDefault()))"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable set with type TreeSet&lt;Integer>: replace with IntSet"
-        errorLine1="        val set = TreeSet&lt;Int>().apply {"
-        errorLine2="        ^">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type List&lt;Integer> of breakWithBreakIterator: replace with IntList"
-        errorLine1="    private fun breakWithBreakIterator(text: CharSequence, breaker: BreakIterator): List&lt;Int> {"
-        errorLine2="                                                                                    ~~~~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="variable res with type List&lt;Integer>: replace with IntList"
-        errorLine1="        val res = mutableListOf(0)"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type List&lt;Integer> of breakOffsets: replace with IntList"
-        errorLine1="    fun breakOffsets(layoutHelper: LayoutHelper, segmentType: SegmentType): List&lt;Int> {"
-        errorLine2="                                                                            ~~~~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInCollection"
-        message="return type List&lt;TextAlign> of values: replace with IntList"
-        errorLine1="        fun values(): List&lt;TextAlign> = listOf(Left, Right, Center, Justify, Start, End)"
-        errorLine2="                      ~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/ui/text/style/TextAlign.kt"/>
-    </issue>
-
 </issues>
diff --git a/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt b/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt
index bffd5f9..ce1ad15 100644
--- a/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt
+++ b/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt
@@ -599,8 +599,7 @@
             clock.setStateParameters(10.dp, 10.dp)
         }
         rule.runOnIdle {
-            // When initial == target state, no animation is active.
-            assertEquals(0, clock.getAnimatedProperties().size)
+            assertEquals(2, clock.getAnimatedProperties().size)
             clock.setStateParameters(20.dp, 40.dp)
         }
         rule.runOnIdle {
@@ -620,8 +619,7 @@
         rule.runOnIdle {
             // Default clock state.
             clock.getTransitions(100).let {
-                // When initial == target state, no animation is active.
-                assertEquals(0, it.size)
+                assertEquals(2, it.size)
             }
             // Change state
             clock.setStateParameters(20.dp, 40.dp)
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 2813b41..ece23e5 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
@@ -2399,12 +2399,17 @@
         // Arrange.
         val focusRequester = FocusRequester()
         setContent {
-            Box(
-                Modifier
-                    .testTag(tag)
-                    .focusRequester(focusRequester)
-                    .focusable()) {
-                BasicText("focusable")
+            Row {
+                // Initially focused item.
+                Box(Modifier.size(10.dp).focusable())
+                Box(
+                    Modifier
+                        .testTag(tag)
+                        .focusRequester(focusRequester)
+                        .focusable()
+                ) {
+                    BasicText("focusable")
+                }
             }
         }
         rule.runOnIdle { focusRequester.requestFocus() }
@@ -2550,11 +2555,15 @@
     fun testTextField_testFocusClearFocusAction() {
         // Arrange.
         setContent {
-            BasicTextField(
-                modifier = Modifier.testTag(tag),
-                value = "value",
-                onValueChange = {}
-            )
+            Row {
+                // Initially focused item.
+                Box(Modifier.size(10.dp).focusable())
+                BasicTextField(
+                    modifier = Modifier.testTag(tag),
+                    value = "value",
+                    onValueChange = {}
+                )
+            }
         }
         val textFieldId = rule.onNodeWithTag(tag)
             .assert(expectValue(Focused, false))
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/ClearFocusExitTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/ClearFocusExitTest.kt
index ae6436a..331bb2a 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/ClearFocusExitTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/ClearFocusExitTest.kt
@@ -37,13 +37,13 @@
     @get:Rule
     val rule = createComposeRule()
 
-    val focusRequester = FocusRequester()
-    var clearTriggered = false
-    lateinit var focusState: FocusState
-    lateinit var focusManager: FocusManager
+    private val focusRequester = FocusRequester()
+    private var clearTriggered = false
+    private lateinit var focusState: FocusState
+    private lateinit var focusManager: FocusManager
 
     @Test
-    fun clearFocus_doesNotTriggersExit() {
+    fun clearFocus_doesNotTriggerExit() {
         // Arrange.
         rule.setFocusableContent {
             focusManager = LocalFocusManager.current
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
index 48f89f2..2799818 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusManagerCompositionLocalTest.kt
@@ -18,17 +18,22 @@
 
 import android.view.View
 import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
+import androidx.compose.ui.input.InputMode.Companion.Keyboard
+import androidx.compose.ui.input.InputMode.Companion.Touch
+import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,24 +44,25 @@
     @get:Rule
     val rule = createComposeRule()
 
+    private lateinit var focusManager: FocusManager
+    private lateinit var inputModeManager: InputModeManager
+    private val focusStates = mutableListOf<FocusState>()
+
     @Test
-    fun clearFocus_singleLayout() {
+    fun clearFocus_singleLayout_focusIsRestoredAfterClear() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
+        rule.setTestContent(extraItemForInitialFocus = false) {
             Box(
                 modifier = Modifier
                     .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
         rule.runOnIdle {
             focusRequester.requestFocus()
-            assertThat(focusState.isFocused).isTrue()
+            focusStates.clear()
         }
 
         // Act.
@@ -64,8 +70,16 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusManager.rootFocusState.isFocused).isTrue()
-            assertThat(focusState.isFocused).isFalse()
+            when (inputModeManager.inputMode) {
+                Keyboard -> {
+                    assertThat(focusStates).containsExactly(Inactive, Active).inOrder()
+                    assertThat(focusManager.rootFocusState.hasFocus).isTrue()
+                }
+                Touch -> {
+                    assertThat(focusStates).containsExactly(Inactive).inOrder()
+                    assertThat(focusManager.rootFocusState.hasFocus).isFalse()
+                }
+            }
         }
     }
 
@@ -77,7 +91,7 @@
         lateinit var parentFocusState: FocusState
         lateinit var grandparentFocusState: FocusState
         val focusRequester = FocusRequester()
-        rule.setFocusableContent {
+        rule.setTestContent {
             focusManager = LocalFocusManager.current
             Box(
                 modifier = Modifier
@@ -119,96 +133,38 @@
     @Test
     fun takeFocus_whenRootIsInactive() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         lateinit var view: View
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
+        rule.setTestContent(extraItemForInitialFocus = false) {
             view = LocalView.current
             Box(
                 modifier = Modifier
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
 
         // Act.
-        rule.runOnIdle { view.requestFocus() }
-
-        // Assert.
         rule.runOnIdle {
-            assertThat(focusManager.rootFocusState).isEqualTo(Active)
-            assertThat(focusState.isFocused).isFalse()
+            focusStates.clear()
+            view.requestFocus()
         }
-    }
-
-    fun takeFocus_whenRootIsActive() {
-        // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
-        lateinit var view: View
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
-            view = LocalView.current
-            Box(
-                modifier = Modifier
-                    .onFocusChanged { focusState = it }
-                    .focusTarget()
-            )
-        }
-        rule.runOnIdle { focusManager.setRootFocusState(Active) }
-
-        // Act.
-        rule.runOnIdle { view.requestFocus() }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(focusManager.rootFocusState).isEqualTo(Active)
-            assertThat(focusState.isFocused).isFalse()
-        }
-    }
-
-    @Test
-    fun takeFocus_whenRootIsActiveParent() {
-        // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
-        lateinit var view: View
-        val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
-            view = LocalView.current
-            Box(
-                modifier = Modifier
-                    .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
-                    .focusTarget()
-            )
-        }
-        rule.runOnIdle { focusRequester.requestFocus() }
-
-        // Act.
-        rule.runOnIdle { view.requestFocus() }
 
         // Assert.
         rule.runOnIdle {
             assertThat(focusManager.rootFocusState).isEqualTo(ActiveParent)
-            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusStates).containsExactly(Active)
         }
     }
 
     @Test
     fun releaseFocus_whenRootIsInactive() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         lateinit var view: View
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
+        rule.setTestContent(extraItemForInitialFocus = false) {
             view = LocalView.current
             Box(
                 modifier = Modifier
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
@@ -219,55 +175,28 @@
         // Assert.
         rule.runOnIdle {
             assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
-            assertThat(focusState.isFocused).isFalse()
+            assertThat(focusStates).containsExactly(Inactive)
         }
     }
 
-    fun releaseFocus_whenRootIsActive() {
-        // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
-        lateinit var view: View
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
-            view = LocalView.current
-            Box(
-                modifier = Modifier
-                    .onFocusChanged { focusState = it }
-                    .focusTarget()
-            )
-        }
-        rule.runOnIdle { focusManager.setRootFocusState(Active) }
-
-        // Act.
-        rule.runOnIdle { view.clearFocus() }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
-            assertThat(focusState.isFocused).isFalse()
-        }
-    }
-
-    @Ignore("b/257499180")
     @Test
-    fun releaseFocus_whenRootIsActiveParent() {
+    fun releaseFocus_whenOwnerFocusIsCleared() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         lateinit var view: View
         val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
+        rule.setTestContent(extraItemForInitialFocus = false) {
             view = LocalView.current
             Box(
                 modifier = Modifier
                     .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
-        rule.runOnIdle { focusRequester.requestFocus() }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusStates.clear()
+        }
 
         // Act.
         rule.runOnIdle {
@@ -276,111 +205,107 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
-            assertThat(focusState.isFocused).isFalse()
+            when (inputModeManager.inputMode) {
+                Keyboard -> {
+                    // Focus is re-assigned to the initially focused item (default focus).
+                    assertThat(focusManager.rootFocusState).isEqualTo(ActiveParent)
+                    assertThat(focusStates).containsExactly(Inactive, Active).inOrder()
+                }
+                Touch -> {
+                    assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+                    assertThat(focusStates).containsExactly(Inactive)
+                }
+                else -> error("Invalid input mode")
+            }
         }
     }
 
     @Test
     fun clearFocus_whenRootIsInactive() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
+        rule.setTestContent {
             Box(
                 modifier = Modifier
                     .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
+        rule.runOnIdle { focusStates.clear() }
 
         // Act.
         rule.runOnIdle { focusManager.clearFocus() }
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
-            assertThat(focusState.isFocused).isFalse()
-        }
-    }
-
-    @Ignore("b/257499180")
-    @Test
-    fun clearFocus_whenRootIsActive() {
-        // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
-        val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
-            Box(
-                modifier = Modifier
-                    .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
-                    .focusTarget()
-            )
-        }
-        rule.runOnIdle { focusManager.setRootFocusState(Active) }
-
-        // Act.
-        rule.runOnIdle { focusManager.clearFocus() }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
-            assertThat(focusState.isFocused).isFalse()
+            when (inputModeManager.inputMode) {
+                Keyboard -> {
+                    assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+                    assertThat(focusStates).isEmpty()
+                }
+                Touch -> {
+                    assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+                    assertThat(focusStates).isEmpty()
+                }
+                else -> error("Invalid input mode")
+            }
         }
     }
 
     @Test
     fun clearFocus_whenRootIsActiveParent() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
+        rule.setTestContent(extraItemForInitialFocus = false) {
             Box(
                 modifier = Modifier
                     .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
-        rule.runOnIdle { focusRequester.requestFocus() }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+            focusStates.clear()
+        }
 
         // Act.
         rule.runOnIdle { focusManager.clearFocus() }
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/257499180): Compose should not hold focus state when clear focus is requested.
-            assertThat(focusManager.rootFocusState).isEqualTo(Active)
-            assertThat(focusState.isFocused).isFalse()
+            when (inputModeManager.inputMode) {
+                Keyboard -> {
+                    assertThat(focusManager.rootFocusState).isEqualTo(ActiveParent)
+                    assertThat(focusStates).containsExactly(Inactive, Active).inOrder()
+                }
+                Touch -> {
+                    assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+                    assertThat(focusStates).containsExactly(Inactive)
+                }
+                else -> error("Invalid input mode")
+            }
         }
     }
 
     @Test
     fun clearFocus_whenHierarchyHasCapturedFocus() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        rule.setFocusableContent {
+        rule.setTestContent {
             focusManager = LocalFocusManager.current
             Box(
                 modifier = Modifier
                     .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
         rule.runOnIdle {
             focusRequester.requestFocus()
             focusRequester.captureFocus()
+            focusStates.clear()
         }
 
         // Act.
@@ -389,28 +314,27 @@
         // Assert.
         rule.runOnIdle {
             assertThat(focusManager.rootFocusState).isEqualTo(ActiveParent)
-            assertThat(focusState.isFocused).isTrue()
+            assertThat(focusStates).isEmpty()
         }
     }
 
     @Test
     fun clearFocus_forced_whenHierarchyHasCapturedFocus() {
         // Arrange.
-        lateinit var focusManager: FocusManager
-        lateinit var focusState: FocusState
         val focusRequester = FocusRequester()
-        rule.setFocusableContent {
-            focusManager = LocalFocusManager.current
+        rule.setTestContent(extraItemForInitialFocus = false) {
+
             Box(
                 modifier = Modifier
                     .focusRequester(focusRequester)
-                    .onFocusChanged { focusState = it }
+                    .onFocusChanged { focusStates += it }
                     .focusTarget()
             )
         }
         rule.runOnIdle {
             focusRequester.requestFocus()
             focusRequester.captureFocus()
+            focusStates.clear()
         }
 
         // Act.
@@ -418,16 +342,32 @@
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/257499180): Compose should clear focus and send focus to the root view.
-            assertThat(focusManager.rootFocusState).isEqualTo(Active)
-            assertThat(focusState.isFocused).isFalse()
+            when (inputModeManager.inputMode) {
+                Keyboard -> {
+                    // Focus is re-assigned to the initially focused item (default focus).
+                    assertThat(focusManager.rootFocusState).isEqualTo(ActiveParent)
+                    assertThat(focusStates).containsExactly(Inactive, Active).inOrder()
+                }
+                Touch -> {
+                    assertThat(focusManager.rootFocusState).isEqualTo(Inactive)
+                    assertThat(focusStates).containsExactly(Inactive).inOrder()
+                }
+                else -> error("Invalid input mode")
+            }
         }
     }
 
     private val FocusManager.rootFocusState: FocusState
         get() = (this as FocusOwnerImpl).rootFocusNode.focusState
 
-    private fun FocusManager.setRootFocusState(focusState: FocusStateImpl) {
-        (this as FocusOwnerImpl).rootFocusNode.focusState = focusState
+    private fun ComposeContentTestRule.setTestContent(
+        extraItemForInitialFocus: Boolean = true,
+        content: @Composable () -> Unit
+    ) {
+        setFocusableContent(extraItemForInitialFocus) {
+            focusManager = LocalFocusManager.current
+            inputModeManager = LocalInputModeManager.current
+            content()
+        }
     }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
index 20fdc7a..f8be78e2 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTestUtils.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.focus
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.Composable
@@ -34,10 +35,25 @@
  * This function adds a parent composable which has size.
  * [View.requestFocus()][android.view.View.requestFocus] will not take focus if the view has no
  * size.
+ *
+ * @param extraItemForInitialFocus Includes an extra item that takes focus initially. This is
+ * useful in cases where we need tests that could be affected by initial focus. Eg. When there is
+ * only one focusable item and we clear focus, that item could end up being focused on again by the
+ * initial focus logic.
  */
-internal fun ComposeContentTestRule.setFocusableContent(content: @Composable () -> Unit) {
+internal fun ComposeContentTestRule.setFocusableContent(
+    extraItemForInitialFocus: Boolean = true,
+    content: @Composable () -> Unit
+) {
     setContent {
-        Box(modifier = Modifier.requiredSize(100.dp, 100.dp)) { content() }
+        if (extraItemForInitialFocus) {
+            Row {
+                Box(modifier = Modifier.requiredSize(10.dp, 10.dp).focusTarget())
+                Box(modifier = Modifier.requiredSize(100.dp, 100.dp)) { content() }
+            }
+        } else {
+            Box(modifier = Modifier.requiredSize(100.dp, 100.dp)) { content() }
+        }
     }
 }
 
@@ -66,9 +82,9 @@
             .focusProperties { canFocus = !deactivated }
             .focusTarget(),
         measurePolicy = remember(width, height) {
-            MeasurePolicy { measurables, constraint ->
+            MeasurePolicy { measurableList, constraint ->
                 layout(width, height) {
-                    measurables.forEach {
+                    measurableList.forEach {
                         val placeable = it.measure(constraint)
                         placeable.placeRelative(0, 0)
                     }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
index b7caf2c..cdb0b7d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTransactionsTest.kt
@@ -23,9 +23,14 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
 import androidx.compose.ui.focus.FocusStateImpl.Active
+import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
+import androidx.compose.ui.input.InputMode.Companion.Keyboard
+import androidx.compose.ui.input.InputMode.Companion.Touch
+import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.dp
@@ -91,33 +96,57 @@
     fun cancelTakeFocus_fromOnFocusChanged() {
         // Arrange.
         lateinit var focusManager: FocusManager
+        lateinit var inputModeManager: InputModeManager
         lateinit var view: View
+        lateinit var focusState1: FocusState
+        lateinit var focusState2: FocusState
+        lateinit var focusState3: FocusState
         val box = FocusRequester()
 
         rule.setFocusableContent {
             focusManager = LocalFocusManager.current
+            inputModeManager = LocalInputModeManager.current
             view = LocalView.current
             Box(
                 Modifier
                     .size(10.dp)
                     .focusRequester(box)
-                    .onFocusChanged { if (it.isFocused) focusManager.clearFocus() }
+                    .onFocusChanged { focusState1 = it }
+                    .onFocusChanged {
+                        focusState2 = it
+                        if (it.isFocused) focusManager.clearFocus()
+                    }
+                    .onFocusChanged { focusState3 = it }
                     .focusTarget()
             )
         }
 
         // Act.
-        rule.runOnIdle {
+        rule.runOnUiThread {
             box.requestFocus()
         }
 
         // Assert.
         rule.runOnIdle {
+            assertThat(focusState1).isEqualTo(Inactive)
+            // TODO(b/312524818): When a focus transaction is cancelled, we should re-notify
+            //  all the focus event modifiers that were called in the previous transaction.
+            assertThat(focusState2).isEqualTo(Active) // Should be Inactive.
+            assertThat(focusState3).isEqualTo(Active) // Should be Inactive.
+
             val root = view as AndroidComposeView
-            val focusOwner = root.focusOwner as FocusOwnerImpl
-            assertThat(focusOwner.rootFocusNode.focusState).isEqualTo(Inactive)
-            // TODO(b/288096244): Find out why this is flaky.
-            //  assertThat(view.isFocused()).isFalse()
+
+            when (inputModeManager.inputMode) {
+                Keyboard -> {
+                    assertThat(root.focusOwner.rootState).isEqualTo(ActiveParent)
+                    assertThat(view.isFocused).isTrue()
+                }
+                Touch -> {
+                    assertThat(root.focusOwner.rootState).isEqualTo(Inactive)
+                    assertThat(view.isFocused).isFalse()
+                }
+                else -> error("invalid input mode")
+            }
         }
     }
 
@@ -152,14 +181,13 @@
         // Assert.
         rule.runOnIdle {
             val root = view as AndroidComposeView
-            val focusOwner = root.focusOwner as FocusOwnerImpl
-            assertThat(focusOwner.rootFocusNode.focusState).isEqualTo(Inactive)
+            assertThat(root.focusOwner.rootState).isEqualTo(Inactive)
             assertThat(view.isFocused).isFalse()
         }
     }
 
     @Test
-    fun rootFocusNodeIsActiveWhenViewIsFocused() {
+    fun rootFocusNodeHasFocusWhenViewIsFocused() {
         lateinit var view: View
         val focusRequester = FocusRequester()
         rule.setFocusableContent {
@@ -174,9 +202,8 @@
 
         // Assert.
         val root = view as AndroidComposeView
-        val focusOwner = root.focusOwner as FocusOwnerImpl
         rule.runOnIdle {
-            assertThat(focusOwner.rootFocusNode.focusState).isEqualTo(Active)
+            assertThat(root.focusOwner.rootState).isEqualTo(ActiveParent)
             assertThat(view.isFocused).isTrue()
         }
 
@@ -190,7 +217,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(focusOwner.rootFocusNode.focusState).isEqualTo(Active)
+            assertThat(root.focusOwner.rootState.hasFocus).isEqualTo(true)
             assertThat(view.isFocused).isTrue()
         }
     }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusViewInteropTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusViewInteropTest.kt
index b2c2ef2..125adfd 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusViewInteropTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusViewInteropTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect as AndroidRect
 import android.view.View
+import androidx.compose.foundation.focusGroup
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.offset
@@ -25,6 +26,7 @@
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
@@ -101,6 +103,36 @@
         )
     }
 
+    @Test
+    fun requestFocus_returnsFalseWhenCancelled() {
+        // Arrange.
+        lateinit var view: View
+        rule.setContent {
+            view = LocalView.current
+            Box(
+                Modifier
+                    .size(10.dp)
+                    .focusProperties {
+                        @OptIn(ExperimentalComposeUiApi::class)
+                        enter = { FocusRequester.Cancel }
+                    }
+                    .focusGroup()
+            ) {
+                Box(
+                    Modifier
+                        .size(10.dp)
+                        .focusable()
+                )
+            }
+        }
+
+        // Act.
+        val success = rule.runOnIdle { view.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle { assertThat(success).isFalse() }
+    }
+
     private fun View.getFocusedRect() = AndroidRect().run {
         rule.runOnIdle {
             getFocusedRect(this)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
index a622899..78c6445 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
@@ -44,7 +44,7 @@
     @Test
     fun moveFocus_noFocusableItem() {
         // Arrange.
-        rule.setContentWithInitialRootFocus {}
+        rule.setContentForTest {}
 
         // Act.
         val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Next) }
@@ -57,7 +57,7 @@
     fun moveFocus_oneDisabledFocusableItem() {
         // Arrange.
         val isItemFocused = mutableStateOf(false)
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(isItemFocused, 0, 0, 10, 10, deactivated = true)
         }
 
@@ -72,7 +72,7 @@
     fun initialFocus_oneItem() {
         // Arrange.
         val isItemFocused = mutableStateOf(false)
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(isItemFocused, 0, 0, 10, 10)
         }
 
@@ -90,7 +90,7 @@
     fun initialFocus_skipsDeactivatedItem() {
         // Arrange.
         val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             Column {
                 FocusableBox(firstItem, 0, 0, 10, 10, deactivated = true)
                 FocusableBox(secondItem, 0, 0, 10, 10)
@@ -112,7 +112,7 @@
     fun initialFocus_firstItemInCompositionOrderGetsFocus() {
         // Arrange.
         val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(firstItem, 10, 10, 10, 10)
             FocusableBox(secondItem, 0, 0, 10, 10)
         }
@@ -131,7 +131,7 @@
     fun initialFocus_firstParentInCompositionOrderGetsFocus() {
         // Arrange.
         val (parent1, parent2, child1, child2) = List(4) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(parent1, 10, 10, 10, 10) {
                 FocusableBox(child1, 10, 10, 10, 10)
             }
@@ -154,7 +154,7 @@
     fun initialFocus_firstItemInCompositionOrderGetsFocus_evenIfAnotherNonParentIsPresent() {
         // Arrange.
         val (parent1, child1, item2) = List(3) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(parent1, 10, 10, 10, 10) {
                 FocusableBox(child1, 10, 10, 10, 10)
             }
@@ -175,7 +175,7 @@
     fun initialFocus_firstItemInCompositionOrderGetsFocus_evenIfThereIsAParentAtTheRoot() {
         // Arrange.
         val (parent1, child1, item1) = List(3) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(parent1, 10, 10, 10, 10) {
                 FocusableBox(child1, 10, 10, 10, 10)
@@ -196,7 +196,7 @@
     fun focusMovesToSecondItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10, initialFocus)
             FocusableBox(item2, 10, 0, 10, 10)
             FocusableBox(item3, 20, 0, 10, 10)
@@ -216,7 +216,7 @@
     fun focusMovesToThirdItem_skipsDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10, initialFocus)
             FocusableBox(item2, 10, 0, 10, 10, deactivated = true)
             FocusableBox(item3, 10, 0, 10, 10)
@@ -237,7 +237,7 @@
     fun focusMovesToThirdItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10, initialFocus)
             FocusableBox(item3, 20, 0, 10, 10)
@@ -257,7 +257,7 @@
     fun focusMovesToFourthItem() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 0, 0, 10, 10, deactivated = true)
             FocusableBox(item3, 10, 0, 10, 10, initialFocus)
@@ -278,7 +278,7 @@
     fun focusWrapsAroundToFirstItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10)
             FocusableBox(item3, 20, 0, 10, 10, initialFocus)
@@ -298,7 +298,7 @@
     fun focusWrapsAroundToFirstItem_skippingLastDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10)
             FocusableBox(item3, 20, 0, 10, 10, initialFocus)
@@ -319,7 +319,7 @@
     fun focusWrapsAroundToFirstItem_skippingFirstDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 10, 0, 10, 10, deactivated = true)
             FocusableBox(item2, 0, 0, 10, 10)
             FocusableBox(item3, 10, 0, 10, 10)
@@ -340,7 +340,7 @@
     fun focusMovesToChildOfDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, child) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10, initialFocus)
             FocusableBox(item2, 10, 0, 10, 10, deactivated = true) {
                 FocusableBox(child, 10, 0, 10, 10)
@@ -362,7 +362,7 @@
     fun focusMovesToGrandChildOfDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, child, grandchild) = List(5) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10, initialFocus)
             FocusableBox(item2, 10, 0, 10, 10, deactivated = true) {
                 FocusableBox(child, 10, 0, 10, 10, deactivated = true) {
@@ -386,7 +386,7 @@
     fun focusMovesToNextSiblingOfDeactivatedItem_evenThoughThereIsACloserNonSibling() {
         // Arrange.
         val (item1, item2, item3, child1, child2) = List(5) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10, deactivated = true) {
                 FocusableBox(child1, 10, 0, 10, 10, initialFocus)
@@ -409,7 +409,7 @@
     fun focusNextOrderAmongChildrenOfMultipleParents() {
         // Arrange.
         val focusState = List(12) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             Column {
                 Row {
                     FocusableBox(focusState[0], 0, 0, 10, 10, initialFocus)
@@ -446,7 +446,7 @@
     fun focusNextOrderAmongChildrenAtMultipleLevels() {
         // Arrange.
         val focusState = List(14) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             Column {
                 FocusableBox(focusState[0], 0, 0, 10, 10, initialFocus)
                 FocusableBox(focusState[1], 0, 10, 10, 10)
@@ -488,7 +488,7 @@
         val (parent3, child6) = List(2) { mutableStateOf(false) }
         val (parent4, child7, child8, child9, child10) = List(5) { mutableStateOf(false) }
         val (parent5, child11) = List(2) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(parent1, 0, 0, 10, 10) {
                 FocusableBox(child1, 0, 0, 10, 10)
                 FocusableBox(child2, 20, 0, 10, 10)
@@ -553,25 +553,16 @@
         rule.runOnIdle { assertThat(parent1.value).isTrue() }
     }
 
-    private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
-        setContent {
-            focusManager = LocalFocusManager.current
-            composable()
-        }
-        rule.runOnIdle { initialFocus.requestFocus() }
-    }
-
-    private fun ComposeContentTestRule.setContentWithInitialRootFocus(
+    private fun ComposeContentTestRule.setContentForTest(
+        initializeFocus: Boolean = false,
         composable: @Composable () -> Unit
     ) {
         setContent {
             focusManager = LocalFocusManager.current
             composable()
         }
-        rule.runOnIdle {
-            with(focusManager as FocusOwner) {
-                focusTransactionManager.withNewTransaction { takeFocus() }
-            }
+        if (initializeFocus) {
+            rule.runOnIdle { initialFocus.requestFocus() }
         }
     }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
index 613421e..9d248a0 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
@@ -44,7 +44,7 @@
     @Test
     fun moveFocus_noFocusableItem() {
         // Arrange.
-        rule.setContentWithInitialRootFocus {}
+        rule.setContentForTest {}
 
         // Act.
         val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Previous) }
@@ -57,7 +57,7 @@
     fun moveFocus_oneDisabledFocusableItem() {
         // Arrange.
         val isItemFocused = mutableStateOf(false)
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(isItemFocused, 0, 0, 10, 10, deactivated = true)
         }
 
@@ -72,7 +72,7 @@
     fun initialFocus_oneItem() {
         // Arrange.
         val isItemFocused = mutableStateOf(false)
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(isItemFocused, 0, 0, 10, 10)
         }
 
@@ -90,7 +90,7 @@
     fun initialFocus_skipsDeactivatedItem() {
         // Arrange.
         val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             Column {
                 FocusableBox(firstItem, 0, 0, 10, 10)
                 FocusableBox(secondItem, 0, 0, 10, 10, deactivated = true)
@@ -112,7 +112,7 @@
     fun initialFocus_lastItemInCompositionOrderGetsFocus() {
         // Arrange.
         val (firstItem, secondItem) = List(2) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(firstItem, 10, 10, 10, 10)
             FocusableBox(secondItem, 0, 0, 10, 10)
         }
@@ -131,7 +131,7 @@
     fun initialFocus_lastChildInCompositionOrderGetsFocus() {
         // Arrange.
         val (parent1, parent2, child1, child2) = List(4) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(parent1, 10, 10, 10, 10) {
                 FocusableBox(child1, 10, 10, 10, 10)
             }
@@ -154,7 +154,7 @@
     fun initialFocus_lastItemInCompositionOrderGetsFocus_evenIfAnotherNonParentIsPresent() {
         // Arrange.
         val (parent1, child1, item1) = List(3) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(parent1, 10, 10, 10, 10) {
                 FocusableBox(child1, 10, 10, 10, 10)
@@ -175,7 +175,7 @@
     fun initialFocus_lastItemInCompositionOrderGetsFocus_evenIfThereIsAParentAtTheRoot() {
         // Arrange.
         val (parent1, child1, item2) = List(3) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(parent1, 10, 10, 10, 10) {
                 FocusableBox(child1, 10, 10, 10, 10)
                 FocusableBox(item2, 0, 0, 10, 10)
@@ -196,7 +196,7 @@
     fun focusMovesToSecondItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10)
             FocusableBox(item3, 20, 0, 10, 10, initialFocus)
@@ -216,7 +216,7 @@
     fun focusMovesToSecondItem_skipsDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10)
             FocusableBox(item3, 10, 0, 10, 10, deactivated = true)
@@ -237,7 +237,7 @@
     fun focusMovesToFirstItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10, initialFocus)
             FocusableBox(item3, 20, 0, 10, 10)
@@ -257,7 +257,7 @@
     fun focusMovesToFirstItem_ignoresDeactivated() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10, initialFocus)
             FocusableBox(item3, 20, 0, 10, 10, deactivated = true)
@@ -278,7 +278,7 @@
     fun focusMovesToParent() {
         // Arrange.
         val (parent, child1, child2, child3) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(parent, 0, 0, 10, 10) {
                 FocusableBox(child1, 10, 0, 10, 10, initialFocus)
                 FocusableBox(child2, 20, 0, 10, 10)
@@ -300,7 +300,7 @@
     fun focusMovesToParent_ignoresDeactivated() {
         // Arrange.
         val (item, parent, child1, child2) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item, 0, 0, 10, 10)
             FocusableBox(parent, 0, 0, 10, 10, deactivated = true) {
                 FocusableBox(child1, 10, 0, 10, 10, initialFocus)
@@ -322,7 +322,7 @@
     fun focusMovesToParent_ignoresDeactivated_andWrapsAround() {
         // Arrange.
         val (parent, child1, child2, child3) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(parent, 0, 0, 10, 10, deactivated = true) {
                 FocusableBox(child1, 10, 0, 10, 10, initialFocus)
                 FocusableBox(child2, 20, 0, 10, 10)
@@ -344,7 +344,7 @@
     fun focusWrapsAroundToLastItem() {
         // Arrange.
         val (item1, item2, item3) = List(3) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10, initialFocus)
             FocusableBox(item2, 10, 0, 10, 10)
             FocusableBox(item3, 20, 0, 10, 10)
@@ -364,7 +364,7 @@
     fun focusWrapsAroundToLastItem_skippingFirstDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 20, 0, 10, 10, deactivated = true)
             FocusableBox(item2, 0, 0, 10, 10, initialFocus)
             FocusableBox(item3, 10, 0, 10, 10)
@@ -385,7 +385,7 @@
     fun focusWrapsAroundToLastItem_skippingLastDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, item4) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10, initialFocus)
             FocusableBox(item2, 10, 0, 10, 10)
             FocusableBox(item3, 20, 0, 10, 10)
@@ -406,7 +406,7 @@
     fun focusMovesToChildOfDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, child) = List(4) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10, deactivated = true) {
                 FocusableBox(child, 10, 0, 10, 10)
@@ -428,7 +428,7 @@
     fun focusMovesToGrandChildOfDeactivatedItem() {
         // Arrange.
         val (item1, item2, item3, child, grandchild) = List(5) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 0, 0, 10, 10)
             FocusableBox(item2, 10, 0, 10, 10, deactivated = true) {
                 FocusableBox(child, 10, 0, 10, 10, deactivated = true) {
@@ -452,7 +452,7 @@
     fun focusMovesToNextSiblingOfDeactivatedItem_evenThoughThereIsACloserNonSibling() {
         // Arrange.
         val (item1, item2, item3, child1, child2) = List(5) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             FocusableBox(item1, 10, 0, 10, 10)
             FocusableBox(item2, 0, 0, 10, 10, deactivated = true) {
                 FocusableBox(child1, 0, 0, 10, 10)
@@ -475,7 +475,7 @@
     fun focusNextOrderAmongChildrenOfMultipleParents() {
         // Arrange.
         val focusState = List(12) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             Column {
                 Row {
                     FocusableBox(focusState[0], 0, 0, 10, 10)
@@ -511,7 +511,7 @@
     fun focusNextOrderAmongChildrenAtMultipleLevels() {
         // Arrange.
         val focusState = List(14) { mutableStateOf(false) }
-        rule.setContentForTest {
+        rule.setContentForTest(initializeFocus = true) {
             Column {
                 FocusableBox(focusState[0], 0, 0, 10, 10)
                 FocusableBox(focusState[1], 0, 10, 10, 10)
@@ -554,7 +554,7 @@
         val (parent4, child7, child8, child11, child12) = List(5) { mutableStateOf(false) }
         val (child9, child10) = List(2) { mutableStateOf(false) }
         val (parent5, child13) = List(2) { mutableStateOf(false) }
-        rule.setContentWithInitialRootFocus {
+        rule.setContentForTest {
             FocusableBox(parent1, 0, 0, 10, 10) {
                 FocusableBox(child1, 0, 0, 10, 10)
                 FocusableBox(child2, 20, 0, 10, 10)
@@ -624,25 +624,16 @@
         rule.runOnIdle { assertThat(child11.value).isTrue() }
     }
 
-    private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
-        setContent {
-            focusManager = LocalFocusManager.current
-            composable()
-        }
-        rule.runOnIdle { initialFocus.requestFocus() }
-    }
-
-    private fun ComposeContentTestRule.setContentWithInitialRootFocus(
+    private fun ComposeContentTestRule.setContentForTest(
+        initializeFocus: Boolean = false,
         composable: @Composable () -> Unit
     ) {
         setContent {
             focusManager = LocalFocusManager.current
             composable()
         }
-        rule.runOnIdle {
-            with(focusManager as FocusOwner) {
-                focusTransactionManager.withNewTransaction { takeFocus() }
-            }
+        if (initializeFocus) {
+            rule.runOnIdle { initialFocus.requestFocus() }
         }
     }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OwnerFocusTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OwnerFocusTest.kt
index bfeff0b..e3843b0 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OwnerFocusTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/OwnerFocusTest.kt
@@ -17,13 +17,18 @@
 package androidx.compose.ui.focus
 
 import android.view.View
+import android.view.View.FOCUS_DOWN
+import android.view.View.FOCUS_UP
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -63,19 +68,16 @@
         }
     }
 
-    @Ignore("Enable this test after the owner propagates focus to the hierarchy (b/152535715)")
     @Test
     fun whenOwnerGainsFocus_focusModifiersAreUpdated() {
         // Arrange.
         lateinit var ownerView: View
         lateinit var focusState: FocusState
-        val focusRequester = FocusRequester()
-        rule.setFocusableContent {
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
             ownerView = LocalView.current
             Box(
                 modifier = Modifier
                     .onFocusChanged { focusState = it }
-                    .focusRequester(focusRequester)
                     .focusTarget()
             )
         }
@@ -91,6 +93,106 @@
         }
     }
 
+    @Test
+    fun callingRequestFocusDownWhenOwnerAlreadyHasFocus() {
+        // Arrange.
+        lateinit var ownerView: View
+        lateinit var focusState1: FocusState
+        lateinit var focusState2: FocusState
+        lateinit var focusState3: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            ownerView = LocalView.current
+            Column {
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .onFocusChanged { focusState1 = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .focusRequester(focusRequester)
+                        .onFocusChanged { focusState2 = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .onFocusChanged { focusState3 = it }
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Act.
+        val focusRequested = rule.runOnIdle {
+            ownerView.requestFocus(FOCUS_DOWN)
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusRequested).isTrue()
+            assertThat(focusState1.isFocused).isFalse()
+            assertThat(focusState2.isFocused).isTrue()
+            assertThat(focusState3.isFocused).isFalse()
+        }
+    }
+
+    @Test
+    fun callingRequestFocusUpWhenOwnerAlreadyHasFocus() {
+        // Arrange.
+        lateinit var ownerView: View
+        lateinit var focusState1: FocusState
+        lateinit var focusState2: FocusState
+        lateinit var focusState3: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            ownerView = LocalView.current
+            Column {
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .onFocusChanged { focusState1 = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .focusRequester(focusRequester)
+                        .onFocusChanged { focusState2 = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .onFocusChanged { focusState3 = it }
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Act.
+        val focusRequested = rule.runOnIdle {
+            ownerView.requestFocus(FOCUS_UP)
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(focusRequested).isTrue()
+            assertThat(focusState1.isFocused).isFalse()
+            assertThat(focusState2.isFocused).isTrue()
+            assertThat(focusState3.isFocused).isFalse()
+        }
+    }
+
     @Ignore("Enable this test after the owner propagates focus to the hierarchy (b/152535715)")
     @Test
     fun whenWindowGainsFocus_focusModifiersAreUpdated() {
@@ -129,6 +231,7 @@
             ownerView = LocalView.current
             Box(
                 modifier = Modifier
+                    .size(10.dp)
                     .onFocusChanged { focusState = it }
                     .focusRequester(focusRequester)
                     .focusTarget()
@@ -180,6 +283,25 @@
     }
 
     @Test
+    fun viewDoesNotTakeFocus_whenThereAreNoFocusableItems() {
+        // Arrange.
+        lateinit var ownerView: View
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            ownerView = LocalView.current
+            Box {}
+        }
+
+        // Act.
+        val success = rule.runOnIdle { ownerView.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(success).isFalse()
+            assertThat(ownerView.isFocused).isFalse()
+        }
+    }
+
+    @Test
     fun clickingOnNonClickableSpaceInAppWhenViewIsFocused_doesNotChangeViewFocus() {
         // Arrange.
         val nonClickable = "notClickable"
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
index cd43471..30f4e1a 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
@@ -152,7 +152,7 @@
      *                   |________|
      */
     @Test
-    fun moveFocusEnter_blockFocusChange() {
+    fun moveFocus_skipsItemWithCustomEnter() {
         // Arrange.
         val (up, down, left, right, parent) = List(5) { mutableStateOf(false) }
         val child = mutableStateOf(false)
@@ -185,15 +185,95 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(movedFocusSuccessfully).isFalse()
+            assertThat(movedFocusSuccessfully).isTrue()
             assertThat(directionSentToEnter).isEqualTo(focusDirection)
             assertThat(child.value).isFalse()
             assertThat(parent.value).isFalse()
             when (focusDirection) {
-                Left -> assertThat(right.value).isTrue()
-                Right -> assertThat(left.value).isTrue()
-                Up -> assertThat(down.value).isTrue()
-                Down -> assertThat(up.value).isTrue()
+                Left -> assertThat(left.value).isTrue()
+                Right -> assertThat(right.value).isTrue()
+                Up -> assertThat(left.value).isTrue()
+                Down -> assertThat(left.value).isTrue()
+            }
+        }
+    }
+
+    /**
+     *                  _________                   |                    _________
+     *                 |   Up   |                   |                   |   Up   |
+     *                 |________|                   |                   |________|
+     *               ________________               |                 ________________
+     *              |  parent       |               |                |  parent       |
+     *              |   _________   |   __________  |   __________   |   _________   |
+     *              |  | child0 |   |  | focused |  |  | focused |   |  | child0 |   |
+     *              |  |________|   |  |_________|  |  |_________|   |  |________|   |
+     *              |_______________|               |                |_______________|
+     *                  _________                   |                    _________
+     *                 |  Down  |                   |                   |  Down  |
+     *                 |________|                   |                   |________|
+     *                                              |
+     *               moveFocus(Left)                |                moveFocus(Right)
+     *                                              |
+     * ---------------------------------------------|--------------------------------------------
+     *                                              |                   __________
+     *                                              |                  | focused |
+     *                                              |                  |_________|
+     *               ________________               |                ________________
+     *              |  parent       |               |               |  parent       |
+     *   _________  |   _________   |   _________   |   _________   |   _________   |    _________
+     *  |  Left  |  |  | child0 |   |  |  Right |   |  |  Left  |   |  | child0 |   |   |  Right |
+     *  |________|  |  |________|   |  |________|   |  |________|   |  |________|   |   |________|
+     *              |_______________|               |               |_______________|
+     *                  __________                  |
+     *                 | focused |                  |
+     *                 |_________|                  |
+     *                                              |
+     *                moveFocus(Up)                 |                moveFocus(Down)
+     *                                              |
+     */
+    @Test
+    fun moveFocusEnter_blockFocusChange_appropriateOtherItemIsFocused() {
+        // Arrange.
+        val (up, down, left, right, parent) = List(5) { mutableStateOf(false) }
+        val child = mutableStateOf(false)
+        var (upItem, downItem, leftItem, rightItem, childItem) = FocusRequester.createRefs()
+        var directionSentToEnter: FocusDirection? = null
+        val customFocusEnter = Modifier.focusProperties {
+            enter = {
+                directionSentToEnter = it
+                Cancel
+            }
+        }
+        when (focusDirection) {
+            Left -> rightItem = initialFocus
+            Right -> leftItem = initialFocus
+            Up -> downItem = initialFocus
+            Down -> upItem = initialFocus
+        }
+        rule.setContentForTest {
+            if (focusDirection != Up) FocusableBox(up, 30, 0, 10, 10, upItem)
+            if (focusDirection != Left) FocusableBox(left, 0, 30, 10, 10, leftItem)
+            FocusableBox(parent, 20, 20, 30, 30, deactivated = true, modifier = customFocusEnter) {
+                FocusableBox(child, 10, 10, 10, 10, childItem)
+            }
+            if (focusDirection != Right) FocusableBox(right, 60, 30, 10, 10, rightItem)
+            if (focusDirection != Down) FocusableBox(down, 30, 60, 10, 10, downItem)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(directionSentToEnter).isEqualTo(focusDirection)
+            assertThat(child.value).isFalse()
+            assertThat(parent.value).isFalse()
+            when (focusDirection) {
+                Left -> assertThat(up.value).isTrue()
+                Right -> assertThat(up.value).isTrue()
+                Up -> assertThat(left.value).isTrue()
+                Down -> assertThat(left.value).isTrue()
             }
         }
     }
@@ -205,7 +285,7 @@
      *                 ________________
      *                |               |
      *   _________    |     empty     |    _________
-     *  |  Left  |    |   lazylist    |   |  Right |
+     *  |  Left  |    |   lazyList    |   |  Right |
      *  |________|    |               |   |________|
      *                |_______________|
      *                    _________
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
index bfccce6..a08a472 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.focus
 
-import android.view.View
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.text.BasicText
@@ -29,7 +28,6 @@
 import androidx.compose.ui.focus.FocusDirection.Companion.Right
 import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.filters.MediumTest
@@ -54,7 +52,6 @@
     }
 
     private val focusDirection = param.focusDirection
-    lateinit var view: View
     private lateinit var focusManager: FocusManager
 
     companion object {
@@ -214,11 +211,9 @@
 
     private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
         setContent {
-            view = LocalView.current
             focusManager = LocalFocusManager.current
             composable()
         }
-        rule.runOnIdle { view.requestFocus() }
     }
 }
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
index e350b81..07764c7 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
@@ -84,7 +84,6 @@
     @Test
     fun noFocusable_doesNotDeliverEvent() {
         // Arrange.
-        var error: IllegalStateException? = null
         rule.setContent {
             Box(
                 modifier = Modifier.onFocusAwareEvent {
@@ -95,25 +94,15 @@
         }
 
         // Act.
-        try {
-            rule.onRoot().performFocusAwareInput(sentEvent)
-        } catch (exception: IllegalStateException) {
-            error = exception
-        }
+        rule.onRoot().performFocusAwareInput(sentEvent)
 
         // Assert.
-        assertThat(receivedEvent).isNull()
-        when (nodeType) {
-            KeyInput, InterruptedSoftKeyboardInput ->
-                assertThat(error!!.message).contains("do not have an active focus target")
-            RotaryInput -> assertThat(error).isNull()
-        }
+        rule.runOnIdle { assertThat(receivedEvent).isNull() }
     }
 
     @Test
     fun unfocusedFocusable_doesNotDeliverEvent() {
         // Arrange.
-        var error: IllegalStateException? = null
         rule.setFocusableContent {
             Box(
                 modifier = Modifier
@@ -126,18 +115,10 @@
         }
 
         // Act.
-        try {
-            rule.onRoot().performFocusAwareInput(sentEvent)
-        } catch (exception: IllegalStateException) {
-            error = exception
-        }
+        rule.onRoot().performFocusAwareInput(sentEvent)
 
         // Assert.
-        assertThat(receivedEvent).isNull()
-        when (nodeType) {
-            KeyInput -> assertThat(error!!.message).contains("do not have an active focus target")
-            InterruptedSoftKeyboardInput, RotaryInput -> assertThat(receivedEvent).isNull()
-        }
+        rule.runOnIdle { assertThat(receivedEvent).isNull() }
     }
 
     @Test
@@ -526,7 +507,7 @@
     }
 
     private fun ComposeContentTestRule.setContentWithInitialFocus(content: @Composable () -> Unit) {
-        setFocusableContent(content)
+        setFocusableContent(content = content)
         runOnIdle { initialFocus.requestFocus() }
     }
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
index 70124ae..f87436d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package androidx.compose.ui.input.key
 
 import android.view.KeyEvent as AndroidKeyEvent
@@ -39,7 +38,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,44 +49,45 @@
     val rule = createComposeRule()
 
     @Test
-    fun noRootFocusTarget_throwsException() {
+    fun noFocusTarget_doesNotTriggerOnKeyEvent() {
         // Arrange.
-        rule.setContent {
-            Box(modifier = Modifier.onKeyEvent { false })
+        var receivedKeyEvent: KeyEvent? = null
+        rule.setFocusableContent {
+            Box(
+                Modifier.onKeyEvent {
+                    receivedKeyEvent = it
+                    true
+                }
+            )
         }
 
         // Act.
-        assertFailsWith<IllegalStateException> {
-            rule.onRoot().performKeyPress(keyEvent(KeyCodeA, KeyDown))
-        }
+        rule.onRoot().performKeyPress(keyEvent(KeyCodeA, KeyDown))
+
+        // Assert.
+        rule.runOnIdle { assertThat(receivedKeyEvent).isNull() }
     }
 
     @Test
-    fun noFocusTarget_throwsException() {
+    fun focusTargetNotFocused_doesNotTriggerOnKeyEvent() {
         // Arrange.
+        var receivedKeyEvent: KeyEvent? = null
         rule.setFocusableContent {
-            Box(modifier = Modifier.onKeyEvent { true })
+            Box(
+                Modifier
+                    .focusTarget()
+                    .onKeyEvent {
+                        receivedKeyEvent = it
+                        true
+                    }
+            )
         }
 
         // Act.
-        assertFailsWith<IllegalStateException> {
-            rule.onRoot().performKeyPress(keyEvent(KeyCodeA, KeyDown))
-        }
-    }
+        rule.onRoot().performKeyPress(keyEvent(KeyCodeA, KeyDown))
 
-    @Test
-    fun focusTargetNotFocused_throwsException() {
-        // Arrange.
-        rule.setFocusableContent {
-            Box(modifier = Modifier
-                .focusTarget()
-                .onKeyEvent { true })
-        }
-
-        // Act.
-        assertFailsWith<IllegalStateException> {
-            rule.onRoot().performKeyPress(keyEvent(KeyCodeA, KeyDown))
-        }
+        // Assert.
+        rule.runOnIdle { assertThat(receivedKeyEvent).isNull() }
     }
 
     @Test
@@ -681,7 +680,10 @@
      * The [KeyEvent] is usually created by the system. This function creates an instance of
      * [KeyEvent] that can be used in tests.
      */
-    private fun keyEvent(keycode: Int, keyEventType: KeyEventType): KeyEvent {
+    private fun keyEvent(
+        @Suppress("SameParameterValue") keycode: Int,
+        keyEventType: KeyEventType
+    ): KeyEvent {
         val action = when (keyEventType) {
             KeyDown -> ACTION_DOWN
             KeyUp -> ACTION_UP
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 69c453e..4de0f4c 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -19,7 +19,6 @@
 package androidx.compose.ui.layout
 
 import androidx.activity.ComponentActivity
-import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.animateContentSize
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector2D
@@ -53,7 +52,6 @@
 import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.layout.wrapContentWidth
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
@@ -64,7 +62,6 @@
 import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
@@ -103,7 +100,6 @@
 import kotlin.math.roundToInt
 import kotlin.random.Random
 import kotlin.test.assertNotNull
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import org.junit.Ignore
 import org.junit.Rule
@@ -2290,83 +2286,6 @@
         }
     }
 
-    @Test
-    fun forceMeasureLookaheadRootInParentsMeasurePass() {
-        var show by mutableStateOf(false)
-        var lookaheadOffset: Offset? = null
-        var offset: Offset? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                // Mutate this state in measure
-                Box(Modifier.fillMaxSize()) {
-                    val size by produceState(initialValue = 200) {
-                        delay(500)
-                        value = 600 - value
-                    }
-                    LazyColumn(Modifier.layout { measurable, _ ->
-                        // Mutate this state in measure. This state will later be used in descendant's
-                        // composition.
-                        show = size > 300
-                        measurable.measure(Constraints.fixed(size, size)).run {
-                            layout(width, height) { place(0, 0) }
-                        }
-                    }) {
-                        item {
-                            SubcomposeLayout(Modifier.fillMaxSize()) {
-                                val placeable = subcompose(Unit) {
-                                    // read the value to force a recomposition
-                                    Box(
-                                        Modifier.requiredSize(222.dp)
-                                    ) {
-                                        AnimatedContent(show, Modifier.requiredSize(200.dp)) {
-                                            if (it) {
-                                                Row(
-                                                    Modifier
-                                                        .fillMaxSize()
-                                                        .layout { measurable, constraints ->
-                                                            val p = measurable.measure(constraints)
-                                                            layout(p.width, p.height) {
-                                                                coordinates
-                                                                    ?.positionInRoot()
-                                                                    .let {
-                                                                        if (isLookingAhead) {
-                                                                            lookaheadOffset = it
-                                                                        } else {
-                                                                            offset = it
-                                                                        }
-                                                                    }
-                                                                p.place(0, 0)
-                                                            }
-                                                        }) {}
-                                            } else {
-                                                Row(
-                                                    Modifier.size(10.dp)
-                                                ) {}
-                                            }
-                                        }
-                                    }
-                                }[0].measure(Constraints(0, 2000, 0, 2000))
-                                // Measure with the same constraints to ensure the child (i.e. Box)
-                                // gets no constraints change and hence starts forceMeasureSubtree
-                                // from there
-                                layout(700, 800) {
-                                    placeable.place(0, 0)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        rule.waitUntil(2000) {
-            show
-        }
-        rule.waitForIdle()
-
-        assertEquals(Offset(-150f, 0f), lookaheadOffset)
-        assertEquals(Offset(-150f, 0f), offset)
-    }
-
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun lookaheadSizeTrackedWhenModifierChanges() {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/MeasureOnlyTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/MeasureOnlyTest.kt
index f95f492..c64186f 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/MeasureOnlyTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/MeasureOnlyTest.kt
@@ -18,14 +18,9 @@
 import android.view.View
 import android.view.View.MeasureSpec
 import androidx.activity.ComponentActivity
-import androidx.compose.animation.AnimatedContent
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.requiredSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -34,19 +29,15 @@
 import androidx.compose.ui.background
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertEquals
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -287,64 +278,4 @@
             assertThat(view.height).isEqualTo(10)
         }
     }
-
-    /**
-     * When a descendant affects the root size, the root should resize when the
-     * descendant changes size.
-     */
-    @Test
-    fun remeasureRootWithLookahead() {
-        var largeContent by mutableStateOf(false)
-        var lookaheadSize: IntSize? = null
-        rule.setContent {
-            // Forces the box to change size, so that the containing AndroidView will get a
-            // different set of measureSpec in `onMeasure`.
-            Box(Modifier.size(if (largeContent) 200.dp else 100.dp)) {
-                AndroidView(factory = { context ->
-                    ComposeView(context).apply {
-                        setContent {
-                            CompositionLocalProvider(LocalDensity provides Density(1f)) {
-                                Box(Modifier.requiredSize(300.dp)) {
-                                    LazyColumn(Modifier.size(300.dp)) {
-                                        item {
-                                            AnimatedContent(largeContent, Modifier.fillMaxSize()) {
-                                                if (it) {
-                                                    Box(
-                                                        Modifier
-                                                            .layout { measurable, constraints ->
-                                                                val placeable = measurable
-                                                                    .measure(constraints)
-                                                                if (isLookingAhead)
-                                                                    lookaheadSize = IntSize(
-                                                                        placeable.width,
-                                                                        placeable.height
-                                                                    )
-                                                                layout(
-                                                                    placeable.width,
-                                                                    placeable.height
-                                                                ) {
-                                                                    placeable.place(0, 0)
-                                                                }
-                                                            }
-                                                            .size(200.dp))
-                                                } else {
-                                                    Box(Modifier.size(100.dp))
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                })
-            }
-        }
-        rule.runOnIdle {
-            largeContent = true
-        }
-        rule.runOnIdle {
-            assertEquals(lookaheadSize, IntSize(300, 200))
-        }
-    }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeUtils.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeUtils.kt
index bec5a59..592ab66 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeUtils.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeUtils.kt
@@ -21,10 +21,10 @@
 
 /**
  * Remove the root modifier nodes as they are not relevant from the perspective of the tests.
- * There are 5 nodes:
- * KeyInputNode, FocusTargetNode, RotaryInputNode, SemanticsNode and DragAndDropNode.
+ * There are 5 nodes: FocusTargetNode, FocusPropertiesNode, KeyInputNode, RotaryInputNode,
+ * SemanticsNode and DragAndDropNode.
  */
-internal fun <T> List<T>.trimRootModifierNodes(): List<T> = dropLast(5)
+internal fun <T> List<T>.trimRootModifierNodes(): List<T> = dropLast(6)
 
 internal fun Modifier.elementOf(node: Modifier.Node): Modifier {
     return this.then(ElementOf { node })
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt
index 2a2d199..f539ca4 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/WindowInfoCompositionLocalTest.kt
@@ -18,9 +18,11 @@
 
 import android.view.KeyEvent
 import android.view.View
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.focus.setFocusableContent
 import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
 import androidx.compose.ui.test.junit4.AndroidComposeTestRule
@@ -53,7 +55,6 @@
         rule.setContent {
             BasicText("Main Window")
             windowInfo = LocalWindowInfo.current
-            @Suppress("DEPRECATION")
             WindowFocusObserver { if (it) windowFocusGain.countDown() }
         }
 
@@ -201,21 +202,16 @@
         assertThat(mainWindowInfo.isWindowFocused).isTrue()
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun windowInfo_providesKeyModifiers() {
-        lateinit var mainWindowInfo: WindowInfo
         lateinit var ownerView: View
-
         var keyModifiers = PointerKeyboardModifiers(0)
 
         rule.setFocusableContent {
             ownerView = LocalView.current
-            mainWindowInfo = LocalWindowInfo.current
-
-            keyModifiers = mainWindowInfo.keyboardModifiers
+            keyModifiers = LocalWindowInfo.current.keyboardModifiers
+            Box(Modifier.focusTarget())
         }
-
         assertThat(keyModifiers.packedValue).isEqualTo(0)
 
         (rule as AndroidComposeTestRule<*, *>).runOnUiThread {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/focus/FocusInteropUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/focus/FocusInteropUtils.android.kt
new file mode 100644
index 0000000..922d23c
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/focus/FocusInteropUtils.android.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.focus
+
+import android.view.ViewGroup
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * Converts an android focus direction to a compose [focus direction][FocusDirection].
+ */
+internal fun toFocusDirection(androidDirection: Int): FocusDirection? = when (androidDirection) {
+    ViewGroup.FOCUS_UP -> FocusDirection.Up
+    ViewGroup.FOCUS_DOWN -> FocusDirection.Down
+    ViewGroup.FOCUS_LEFT -> FocusDirection.Left
+    ViewGroup.FOCUS_RIGHT -> FocusDirection.Right
+    ViewGroup.FOCUS_FORWARD -> FocusDirection.Next
+    ViewGroup.FOCUS_BACKWARD -> FocusDirection.Previous
+    else -> null
+}
+
+/**
+ * Converts a compose [focus direction][FocusDirection] to an android focus direction.
+ */
+internal fun FocusDirection.toAndroidFocusDirection(): Int? = when (this) {
+        FocusDirection.Up -> ViewGroup.FOCUS_UP
+        FocusDirection.Down -> ViewGroup.FOCUS_DOWN
+        FocusDirection.Left -> ViewGroup.FOCUS_LEFT
+        FocusDirection.Right -> ViewGroup.FOCUS_RIGHT
+        FocusDirection.Next -> ViewGroup.FOCUS_FORWARD
+        FocusDirection.Previous -> ViewGroup.FOCUS_BACKWARD
+        else -> null
+    }
+
+/**
+ * Convert an Android layout direction to a compose [layout direction][LayoutDirection].
+ */
+internal fun toLayoutDirection(androidLayoutDirection: Int): LayoutDirection? {
+    return when (androidLayoutDirection) {
+        android.util.LayoutDirection.LTR -> LayoutDirection.Ltr
+        android.util.LayoutDirection.RTL -> LayoutDirection.Rtl
+        else -> null
+    }
+}
+
+/**
+ * focus search in the Android framework wraps around for 1D focus search, but not for 2D focus
+ * search. This is a helper function that can be used to determine whether we should wrap around.
+ */
+internal fun supportsWrapAroundFocus(androidDirection: Int): Boolean = when (androidDirection) {
+    ViewGroup.FOCUS_FORWARD, ViewGroup.FOCUS_BACKWARD -> true
+    else -> false
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index e2bd716..c47b2b9 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -22,7 +22,6 @@
 import android.os.Build
 import android.os.Looper
 import android.os.SystemClock
-import android.util.Log
 import android.util.LongSparseArray
 import android.util.SparseArray
 import android.view.DragEvent
@@ -80,6 +79,7 @@
 import androidx.compose.ui.draganddrop.DragAndDropTransferData
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Enter
 import androidx.compose.ui.focus.FocusDirection.Companion.Exit
 import androidx.compose.ui.focus.FocusDirection.Companion.Left
 import androidx.compose.ui.focus.FocusDirection.Companion.Next
@@ -88,6 +88,11 @@
 import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.focus.FocusOwner
 import androidx.compose.ui.focus.FocusOwnerImpl
+import androidx.compose.ui.focus.requestFocus
+import androidx.compose.ui.focus.supportsWrapAroundFocus
+import androidx.compose.ui.focus.toAndroidFocusDirection
+import androidx.compose.ui.focus.toFocusDirection
+import androidx.compose.ui.focus.toLayoutDirection
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
@@ -95,19 +100,21 @@
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.setFrom
+import androidx.compose.ui.graphics.toAndroidRect
+import androidx.compose.ui.graphics.toComposeRect
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.hapticfeedback.PlatformHapticFeedback
 import androidx.compose.ui.input.InputMode.Companion.Keyboard
 import androidx.compose.ui.input.InputMode.Companion.Touch
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.InputModeManagerImpl
+import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.key.Key.Companion.Back
 import androidx.compose.ui.input.key.Key.Companion.DirectionCenter
 import androidx.compose.ui.input.key.Key.Companion.DirectionDown
 import androidx.compose.ui.input.key.Key.Companion.DirectionLeft
 import androidx.compose.ui.input.key.Key.Companion.DirectionRight
 import androidx.compose.ui.input.key.Key.Companion.DirectionUp
-import androidx.compose.ui.input.key.Key.Companion.Enter
 import androidx.compose.ui.input.key.Key.Companion.Escape
 import androidx.compose.ui.input.key.Key.Companion.NumPadEnter
 import androidx.compose.ui.input.key.Key.Companion.PageDown
@@ -220,12 +227,15 @@
 
     private val semanticsModifier = EmptySemanticsElement
 
-    override val focusOwner: FocusOwner = FocusOwnerImpl { registerOnEndApplyChangesListener(it) }
-
-    private val dragAndDropModifierOnDragListener = DragAndDropModifierOnDragListener(
-        ::startDrag
+    override val focusOwner: FocusOwner = FocusOwnerImpl(
+        onRequestApplyChangesListener = ::registerOnEndApplyChangesListener,
+        onRequestFocusForOwner = ::onRequestFocusForOwner,
+        onClearFocusForOwner = ::onClearFocusForOwner,
+        layoutDirection = ::layoutDirection
     )
 
+    private val dragAndDropModifierOnDragListener = DragAndDropModifierOnDragListener(::startDrag)
+
     override val dragAndDropManager: DragAndDropManager = dragAndDropModifierOnDragListener
 
     private val _windowInfo: WindowInfoImpl = WindowInfoImpl()
@@ -238,8 +248,10 @@
         val focusDirection = getFocusDirection(it)
         if (focusDirection == null || it.type != KeyDown) return@onKeyEvent false
 
-        // Consume the key event if we moved focus.
-        focusOwner.moveFocus(focusDirection)
+        // Consume the key event if we moved focus or if focus search or requestFocus is cancelled.
+        focusOwner.focusSearch(focusDirection, null) { focusTargetNode ->
+            focusTargetNode.requestFocus(focusDirection) ?: true
+        } ?: true
     }
 
     private val rotaryInputModifier = Modifier.onRotaryScrollEvent {
@@ -256,8 +268,8 @@
         it.modifier = Modifier
             .then(semanticsModifier)
             .then(rotaryInputModifier)
-            .then(focusOwner.modifier)
             .then(keyInputModifier)
+            .then(focusOwner.modifier)
             .then(dragAndDropModifierOnDragListener.modifier)
     }
 
@@ -460,7 +472,11 @@
 
     // Backed by mutableStateOf so that the ambient provider recomposes when it changes
     override var layoutDirection by mutableStateOf(
-        context.resources.configuration.localeLayoutDirection
+        // We don't use the attached View's layout direction here since that layout direction may not
+        // be resolved since composables may be composed without attaching to the RootViewImpl.
+        // In Jetpack Compose, use the locale layout direction (i.e. layoutDirection came from
+        // configuration) as a default layout direction.
+        toLayoutDirection(context.resources.configuration.layoutDirection) ?: LayoutDirection.Ltr
     )
         private set
 
@@ -646,14 +662,65 @@
         showLayoutBounds = getIsShowingLayoutBounds()
     }
 
+    override fun focusSearch(direction: Int): View? = if (focusOwner.rootState.hasFocus) {
+        // When the compose hierarchy is focused, it intercepts the key events that trigger focus
+        // search. So focus search should never find a compose hierarchy that has focus.
+        //
+        // However there is a case where we don't consume the key events. When all the components
+        // have been visited, and/or focus can't be moved within the compose hierarchy, the key
+        // events are returned to the framework so it can perform a search among other views. This
+        // focus search could land back on this view.
+        //
+        // Ideally just returning "this" to focus search should cause it to call requestFocus with
+        // the previously focused rect, and we would find the next item. However the framework does
+        // not call request focus on this view because it already has focus.
+        //
+        // To fix this issue, we manually clear focus and return this. The view with default focus
+        // might be assigned focus for a while, but requestFocus will be called which will then
+        // transfer focus to this view.
+        //
+        // There is an additional special case here. Focus wraps around only for 1D focus search
+        // and not for 2D focus search. So we clear focus only if focus search was triggered by
+        // a 1D focus search.
+        if (supportsWrapAroundFocus(direction)) clearFocus()
+        this
+    } else {
+        // TODO(b/261190892) run a mixed focus search that searches between composables and
+        //  child views and chooses an appropriate result.
+        //  We give the embedded children a chance to take focus before the compose view.
+        super.focusSearch(direction) ?: this
+    }
+
+    override fun requestFocus(direction: Int, previouslyFocusedRect: Rect?): Boolean {
+        if (focusOwner.rootState.hasFocus) return true
+        return focusOwner.takeFocus(
+            focusDirection = toFocusDirection(direction) ?: Enter,
+            previouslyFocusedRect = previouslyFocusedRect?.toComposeRect()
+        )
+    }
+
+    private fun onRequestFocusForOwner(
+        focusDirection: FocusDirection?,
+        previouslyFocusedRect: androidx.compose.ui.geometry.Rect?
+    ): Boolean {
+        return super.requestFocus(
+            focusDirection?.toAndroidFocusDirection() ?: FOCUS_DOWN,
+            @Suppress("DEPRECATION")
+            previouslyFocusedRect?.toAndroidRect()
+        )
+    }
+
+    private fun onClearFocusForOwner() {
+        if (isFocused || hasFocus()) {
+            super.clearFocus()
+        }
+    }
+
     override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
-        Log.d(FocusTag, "Owner FocusChanged($gainFocus)")
-       focusOwner.focusTransactionManager.withExistingTransaction(
-           onCancelled = { if (gainFocus) clearFocus() else requestFocus() }
-       ) {
-           if (gainFocus) focusOwner.takeFocus() else focusOwner.releaseFocus()
-       }
+        if (!gainFocus) {
+            focusOwner.releaseFocus()
+        }
     }
 
     override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
@@ -1227,7 +1294,7 @@
             // focus.
             DirectionUp, PageUp -> Up
             DirectionDown, PageDown -> Down
-            DirectionCenter, Enter, NumPadEnter -> FocusDirection.Enter
+            DirectionCenter, Key.Enter, NumPadEnter -> Enter
             Back, Escape -> Exit
             else -> null
         }
@@ -1808,10 +1875,7 @@
         // If we get such a call, don't try to write to a property delegate
         // that hasn't been initialized yet.
         if (superclassInitComplete) {
-            layoutDirectionFromInt(layoutDirection).let {
-                this.layoutDirection = it
-                focusOwner.layoutDirection = it
-            }
+            this.layoutDirection = toLayoutDirection(layoutDirection) ?: LayoutDirection.Ltr
         }
     }
 
@@ -1975,7 +2039,6 @@
     override fun shouldDelayChildPressedState(): Boolean = false
 
     companion object {
-        private const val FocusTag = "Compose Focus"
         private const val MaximumLayerCacheSize = 10
         private var systemPropertiesClass: Class<*>? = null
         private var getBooleanMethod: Method? = null
@@ -2034,25 +2097,6 @@
 }
 
 /**
- * Return the layout direction set by the [Locale][java.util.Locale].
- *
- * A convenience getter that translates [Configuration.getLayoutDirection] result into
- * [LayoutDirection] instance.
- */
-internal val Configuration.localeLayoutDirection: LayoutDirection
-    // We don't use the attached View's layout direction here since that layout direction may not
-    // be resolved since the composables may be composed without attaching to the RootViewImpl.
-    // In Jetpack Compose, use the locale layout direction (i.e. layoutDirection came from
-    // configuration) as a default layout direction.
-    get() = layoutDirectionFromInt(layoutDirection)
-
-private fun layoutDirectionFromInt(layoutDirection: Int): LayoutDirection = when (layoutDirection) {
-    android.util.LayoutDirection.LTR -> LayoutDirection.Ltr
-    android.util.LayoutDirection.RTL -> LayoutDirection.Rtl
-    else -> LayoutDirection.Ltr
-}
-
-/**
  * These classes are here to ensure that the classes that use this API will get verified and can be
  * AOT compiled. It is expected that this class will soft-fail verification, but the classes
  * which use this method will pass.
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 a1df9ce..bce1f6e 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
@@ -49,9 +49,6 @@
 import androidx.collection.ArrayMap
 import androidx.collection.ArraySet
 import androidx.collection.SparseArrayCompat
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.R
 import androidx.compose.ui.geometry.Offset
@@ -314,7 +311,7 @@
     // traversal with granularity switches to the next node
     private var previousTraversedNode: Int? = null
     private val subtreeChangedLayoutNodes = ArraySet<LayoutNode>()
-    private val boundsUpdateChannel = Channel<Unit>(Channel.CONFLATED)
+    private val boundsUpdateChannel = Channel<Unit>(1)
     private var currentSemanticsNodesInvalidated = true
     @VisibleForTesting
     internal var contentCaptureForceEnabledForTesting = false
@@ -355,8 +352,10 @@
     internal var idToBeforeMap = HashMap<Int, Int>()
     internal var idToAfterMap = HashMap<Int, Int>()
     internal val ExtraDataTestTraversalBeforeVal =
+        @Suppress("SpellCheckingInspection")
         "android.view.accessibility.extra.EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL"
     internal val ExtraDataTestTraversalAfterVal =
+        @Suppress("SpellCheckingInspection")
         "android.view.accessibility.extra.EXTRA_DATA_TEST_TRAVERSALAFTER_VAL"
 
     private val urlSpanCache = URLSpanCache()
@@ -1847,7 +1846,11 @@
 
             AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS -> {
                 return if (node.unmergedConfig.getOrNull(SemanticsProperties.Focused) == true) {
-                    view.focusOwner.clearFocus()
+                    view.focusOwner.clearFocus(
+                        force = false,
+                        refreshFocusEvents = true,
+                        clearOwnerFocus = true
+                    )
                     true
                 } else {
                     false
@@ -2219,7 +2222,6 @@
      * recent layout changes and sends events to the accessibility and content capture framework in
      * batches separated by a 100ms delay.
      */
-    @OptIn(ExperimentalComposeUiApi::class)
     internal suspend fun boundsUpdatesEventLoop() {
         try {
             val subtreeChangedSemanticsNodesIds = ArraySet<Int>()
@@ -3793,4 +3795,4 @@
 @get:ExperimentalComposeUiApi
 @set:ExperimentalComposeUiApi
 @ExperimentalComposeUiApi
-var DisableContentCapture: Boolean by mutableStateOf(false)
+var DisableContentCapture: Boolean = false
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusInvalidationManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusInvalidationManager.kt
index 9843404f..c80645c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusInvalidationManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusInvalidationManager.kt
@@ -26,7 +26,8 @@
  * onApplyChangesListener when nodes are scheduled for invalidation.
  */
 internal class FocusInvalidationManager(
-    private val onRequestApplyChangesListener: (() -> Unit) -> Unit
+    private val onRequestApplyChangesListener: (() -> Unit) -> Unit,
+    private val invalidateOwnerFocusState: () -> Unit
 ) {
     private var focusTargetNodes = mutableSetOf<FocusTargetNode>()
     private var focusEventNodes = mutableSetOf<FocusEventModifierNode>()
@@ -138,6 +139,8 @@
         focusTargetNodes.clear()
         focusTargetsWithInvalidatedFocusEvents.clear()
 
+        invalidateOwnerFocusState()
+
          check(focusPropertiesNodes.isEmpty()) { "Unprocessed FocusProperties nodes" }
          check(focusEventNodes.isEmpty()) { "Unprocessed FocusEvent nodes" }
          check(focusTargetNodes.isEmpty()) { "Unprocessed FocusTarget nodes" }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
index 39a0722..f50a0ef 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwner.kt
@@ -20,7 +20,6 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.rotary.RotaryScrollEvent
-import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * The focus owner provides some internal APIs that are not exposed by focus manager.
@@ -34,11 +33,6 @@
     val modifier: Modifier
 
     /**
-     * The owner sets the layoutDirection that is then used during focus search.
-     */
-    var layoutDirection: LayoutDirection
-
-    /**
      * This manager provides a way to ensure that only one focus transaction is running at a time.
      * We use this to prevent re-entrant focus operations. Starting a new transaction automatically
      * cancels the previous transaction and reverts any focus state changes made during that
@@ -47,12 +41,52 @@
     val focusTransactionManager: FocusTransactionManager
 
     /**
+     * This function is called to ask the owner to request focus from the framework.
+     * eg. If a composable calls requestFocus and the root view does not have focus, this function
+     * can be used to request focus for the view.
+     *
+     * @param focusDirection If this focus request was triggered by a call to moveFocus or using the
+     * keyboard, provide the owner with the direction of focus change.
+     *
+     * @param previouslyFocusedRect The bounds of the currently focused item.
+     *
+     * @return true if the owner successfully requested focus from the framework. False otherwise.
+     */
+    fun requestFocusForOwner(focusDirection: FocusDirection?, previouslyFocusedRect: Rect?): Boolean
+
+    /**
+     * This function searches the compose hierarchy for the next focus target based on the supplied
+     * parameters.
+     *
+     * @param focusDirection the direction to search for the focus target.
+     *
+     * @param focusedRect the bounds of the currently focused item.
+     *
+     * @param onFound This lambda is called with the focus search result.
+     *
+     * @return true, if a suitable [FocusTargetNode] was found, false if no [FocusTargetNode] was
+     * found, and null if the focus search was cancelled.
+     */
+    fun focusSearch(
+        focusDirection: FocusDirection,
+        focusedRect: Rect?,
+        onFound: (FocusTargetNode) -> Boolean
+    ): Boolean?
+
+    /**
      * The [Owner][androidx.compose.ui.node.Owner] calls this function when it gains focus. This
      * informs the [focus manager][FocusOwnerImpl] that the
      * [Owner][androidx.compose.ui.node.Owner] gained focus, and that it should propagate this
      * focus to one of the focus modifiers in the component hierarchy.
+     *
+     * @param focusDirection the direction to search for the focus target.
+     *
+     * @param previouslyFocusedRect the bounds of the currently focused item.
+     *
+     * @return true, if a suitable [FocusTargetNode] was found and it took focus, false if no
+     * [FocusTargetNode] was found or if the focus search was cancelled.
      */
-    fun takeFocus()
+    fun takeFocus(focusDirection: FocusDirection, previouslyFocusedRect: Rect?): Boolean
 
     /**
      * The [Owner][androidx.compose.ui.node.Owner] calls this function when it loses focus. This
@@ -71,10 +105,13 @@
      * @param refreshFocusEvents: Whether we should send an event up the hierarchy to update
      * the associated onFocusEvent nodes.
      *
+     * @param clearOwnerFocus whether we should also clear focus from the owner. This is usually
+     * true, unless focus is being temporarily cleared (eg. to implement focus wrapping).
+     *
      * This could be used to clear focus when a user clicks on empty space outside a focusable
      * component.
      */
-    fun clearFocus(force: Boolean, refreshFocusEvents: Boolean)
+    fun clearFocus(force: Boolean, refreshFocusEvents: Boolean, clearOwnerFocus: Boolean)
 
     /**
      * Searches for the currently focused item, and returns its coordinates as a rect.
@@ -110,4 +147,9 @@
      * Schedule a FocusProperties node to be invalidated after onApplyChanges.
      */
     fun scheduleInvalidation(node: FocusPropertiesModifierNode)
+
+    /**
+     * The focus state of the root focus node.
+     */
+    val rootState: FocusState
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
index 0a7515b..c9e4fcf 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
@@ -28,10 +28,6 @@
 import androidx.compose.ui.focus.FocusDirection.Companion.Previous
 import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
 import androidx.compose.ui.focus.FocusRequester.Companion.Default
-import androidx.compose.ui.focus.FocusStateImpl.Active
-import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
-import androidx.compose.ui.focus.FocusStateImpl.Captured
-import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
@@ -56,11 +52,20 @@
  * The focus manager is used by different [Owner][androidx.compose.ui.node.Owner] implementations
  * to control focus.
  */
-internal class FocusOwnerImpl(onRequestApplyChangesListener: (() -> Unit) -> Unit) : FocusOwner {
+internal class FocusOwnerImpl(
+    onRequestApplyChangesListener: (() -> Unit) -> Unit,
+    private val onRequestFocusForOwner:
+        (focusDirection: FocusDirection?, previouslyFocusedRect: Rect?) -> Boolean,
+    private val onClearFocusForOwner: () -> Unit,
+    private val layoutDirection: (() -> LayoutDirection)
+) : FocusOwner {
 
     internal var rootFocusNode = FocusTargetNode()
 
-    private val focusInvalidationManager = FocusInvalidationManager(onRequestApplyChangesListener)
+    private val focusInvalidationManager = FocusInvalidationManager(
+        onRequestApplyChangesListener,
+        ::invalidateOwnerFocusState
+    )
 
     override val focusTransactionManager: FocusTransactionManager = FocusTransactionManager()
 
@@ -69,21 +74,38 @@
      * list that contains the modifiers required by the focus system. (Eg, a root focus modifier).
      */
     // TODO(b/168831247): return an empty Modifier when there are no focusable children.
-    override val modifier: Modifier = object : ModifierNodeElement<FocusTargetNode>() {
-        override fun create() = rootFocusNode
+    override val modifier: Modifier = Modifier
+        // The root focus target is not focusable, and acts like a focus group.
+        //  We could save an allocation here by making FocusTargetNode implement
+        //  FocusPropertiesModifierNode but to do that we would have to allocate
+        //  a focus properties object. This way only the root node has this extra allocation.
+        .focusProperties { canFocus = false }
+        .then(
+            object : ModifierNodeElement<FocusTargetNode>() {
+                override fun create() = rootFocusNode
+                override fun update(node: FocusTargetNode) {}
+                override fun InspectorInfo.inspectableProperties() { name = "RootFocusTarget" }
+                override fun hashCode(): Int = rootFocusNode.hashCode()
+                override fun equals(other: Any?) = other === this
+            }
+        )
 
-        override fun update(node: FocusTargetNode) {}
-
-        override fun InspectorInfo.inspectableProperties() {
-            name = "RootFocusTarget"
-        }
-
-        override fun hashCode(): Int = rootFocusNode.hashCode()
-
-        override fun equals(other: Any?) = other === this
-    }
-
-    override lateinit var layoutDirection: LayoutDirection
+    /**
+     * This function is called to ask the owner to request focus from the framework.
+     * eg. If a composable calls requestFocus and the root view does not have focus, this function
+     * can be used to request focus for the view.
+     *
+     * @param focusDirection If this focus request was triggered by a call to moveFocus or using the
+     * keyboard, provide the owner with the direction of focus change.
+     *
+     * @param previouslyFocusedRect The bounds of the currently focused item.
+     *
+     * @return true if the owner successfully requested focus from the framework. False otherwise.
+     */
+    override fun requestFocusForOwner(
+        focusDirection: FocusDirection?,
+        previouslyFocusedRect: Rect?
+    ): Boolean = onRequestFocusForOwner(focusDirection, previouslyFocusedRect)
 
     /**
      * Keeps track of which keys have received DOWN events without UP events – i.e. which keys are
@@ -99,14 +121,19 @@
      * informs the [focus manager][FocusOwnerImpl] that the
      * [Owner][androidx.compose.ui.node.Owner] gained focus, and that it should propagate this
      * focus to one of the focus modifiers in the component hierarchy.
+     *
+     * @param focusDirection the direction to search for the focus target.
+     *
+     * @param previouslyFocusedRect the bounds of the currently focused item.
+     *
+     * @return true, if a suitable [FocusTargetNode] was found and it took focus, false if no
+     * [FocusTargetNode] was found or if the focus search was cancelled.
      */
-    override fun takeFocus() {
-        // If the focus state is not Inactive, it indicates that the focus state is already
-        // set (possibly by dispatchWindowFocusChanged). So we don't update the state.
-        if (rootFocusNode.focusState == Inactive) {
-            rootFocusNode.focusState = Active
-            // TODO(b/152535715): propagate focus to children based on child focusability.
-            //  moveFocus(FocusDirection.Enter)
+    override fun takeFocus(focusDirection: FocusDirection, previouslyFocusedRect: Rect?): Boolean {
+        return focusTransactionManager.withExistingTransaction {
+            focusSearch(focusDirection, previouslyFocusedRect) {
+                it.requestFocus(focusDirection) ?: false
+            } ?: false
         }
     }
 
@@ -117,7 +144,9 @@
      * all the focus modifiers in the component hierarchy.
      */
     override fun releaseFocus() {
-        rootFocusNode.clearFocus(forced = true, refreshFocusEvents = true)
+        focusTransactionManager.withExistingTransaction {
+            rootFocusNode.clearFocus(forced = true, refreshFocusEvents = true)
+        }
     }
 
     /**
@@ -130,30 +159,26 @@
      * component.
      */
     override fun clearFocus(force: Boolean) {
-        clearFocus(force, refreshFocusEvents = true)
+        clearFocus(force, refreshFocusEvents = true, clearOwnerFocus = true)
     }
 
     @OptIn(ExperimentalComposeUiApi::class)
-    override fun clearFocus(force: Boolean, refreshFocusEvents: Boolean) {
-        focusTransactionManager.withNewTransaction {
+    override fun clearFocus(force: Boolean, refreshFocusEvents: Boolean, clearOwnerFocus: Boolean) {
+        val clearedFocusSuccessfully = focusTransactionManager.withNewTransaction(
+            onCancelled = { return@withNewTransaction }
+        ) {
             // Don't clear focus if an item on the focused path has a custom exit specified.
             if (!force) {
                 when (rootFocusNode.performCustomClearFocus(Exit)) {
-                    Redirected, Cancelled, RedirectCancelled -> return
+                    Redirected, Cancelled, RedirectCancelled -> return@withNewTransaction false
                     None -> { /* Do nothing. */ }
                 }
             }
+            return@withNewTransaction rootFocusNode.clearFocus(force, refreshFocusEvents)
+        }
 
-            // If this hierarchy had focus before clearing it, it indicates that the host view has
-            // focus. So after clearing focus within the compose hierarchy, we should restore focus
-            // to the root focus modifier to maintain consistency with the host view.
-            val rootInitialState = rootFocusNode.focusState
-            if (rootFocusNode.clearFocus(force, refreshFocusEvents)) {
-                rootFocusNode.focusState = when (rootInitialState) {
-                    Active, ActiveParent, Captured -> Active
-                    Inactive -> Inactive
-                }
-            }
+        if (clearedFocusSuccessfully && clearOwnerFocus) {
+            onClearFocusForOwner.invoke()
         }
     }
 
@@ -162,38 +187,45 @@
      *
      * @return true if focus was moved successfully. false if the focused item is unchanged.
      */
-    @OptIn(ExperimentalComposeUiApi::class)
     override fun moveFocus(focusDirection: FocusDirection): Boolean {
+        // moveFocus is an API that was added to compose, but isn't available in the classic view
+        // system, so for now we only search among compose items and don't support moveFocus for
+        // interop scenarios.
+        val movedFocus = focusSearch(focusDirection, null) {
+            it.requestFocus(focusDirection) ?: false
+        } ?: return false
 
-        // If there is no active node in this sub-hierarchy, we can't move focus.
-        val source = rootFocusNode.findActiveFocusNode() ?: return false
+        // To wrap focus around, we clear focus and request initial focus.
+        if (!movedFocus && focusDirection.supportsWrapAroundFocus()) {
+            clearFocus(force = false, refreshFocusEvents = true, clearOwnerFocus = false)
+            return takeFocus(focusDirection, previouslyFocusedRect = null)
+        }
 
-        // Check if a custom focus traversal order is specified.
-        source.customFocusSearch(focusDirection, layoutDirection).also {
-            if (it !== Default) {
-                return it !== Cancel && it.focus()
+        return movedFocus
+    }
+
+    override fun focusSearch(
+        focusDirection: FocusDirection,
+        focusedRect: Rect?,
+        onFound: (FocusTargetNode) -> Boolean
+    ): Boolean? {
+        val source = rootFocusNode.findActiveFocusNode()?.also {
+            // Check if a custom focus traversal order is specified.
+            when (val customDestination = it.customFocusSearch(focusDirection, layoutDirection())) {
+                @OptIn(ExperimentalComposeUiApi::class)
+                Cancel -> return null
+                Default -> { /* Do Nothing */ }
+                else -> return customDestination.findFocusTargetNode(onFound)
             }
         }
 
-        var isCancelled = false
-        val foundNextItem =
-            rootFocusNode.focusSearch(focusDirection, layoutDirection) { destination ->
-                if (destination == source) return@focusSearch false
-                checkNotNull(destination.nearestAncestor(Nodes.FocusTarget)) {
-                    "Focus search landed at the root."
-                }
-                // If we found a potential next item, move focus to it.
-                // Returning true ends focus search.
-                focusTransactionManager.withNewTransaction {
-                    when (destination.performCustomRequestFocus(focusDirection)) {
-                        Redirected -> true
-                        Cancelled, RedirectCancelled -> { isCancelled = true; true }
-                        None -> destination.performRequestFocus()
-                    }
-                }
+        return rootFocusNode.focusSearch(focusDirection, layoutDirection(), focusedRect) {
+            when (it) {
+                source -> false
+                rootFocusNode -> error("Focus search landed at the root.")
+                else -> onFound(it)
             }
-        // If we didn't find a potential next item, try to wrap around.
-        return !isCancelled && (foundNextItem || wrapAroundFocus(focusDirection))
+        }
     }
 
     /**
@@ -207,11 +239,9 @@
         if (!validateKeyEvent(keyEvent)) return false
 
         val activeFocusTarget = rootFocusNode.findActiveFocusNode()
-        checkNotNull(activeFocusTarget) {
-            "Event can't be processed because we do not have an active focus target."
-        }
-        val focusedKeyInputNode = activeFocusTarget.lastLocalKeyInputNode()
-            ?: activeFocusTarget.nearestAncestor(Nodes.KeyInput)?.node
+        val focusedKeyInputNode = activeFocusTarget?.lastLocalKeyInputNode()
+            ?: activeFocusTarget?.nearestAncestor(Nodes.KeyInput)?.node
+            ?: rootFocusNode.nearestAncestor(Nodes.KeyInput)?.node
 
         focusedKeyInputNode?.traverseAncestors(
             type = Nodes.KeyInput,
@@ -270,6 +300,18 @@
         focusInvalidationManager.scheduleInvalidation(node)
     }
 
+    /**
+     * At the end of the invalidations, we need to ensure that the focus system is in a valid state.
+     */
+    private fun invalidateOwnerFocusState() {
+        // If an active item is removed, we currently clear focus from the hierarchy. We don't
+        // clear focus from the root because that could cause initial focus logic to be re-run.
+        // Now that all the invalidations are complete, we run owner.clearFocus() if needed.
+        if (rootFocusNode.focusState == FocusStateImpl.Inactive) {
+            onClearFocusForOwner()
+        }
+    }
+
     private inline fun <reified T : DelegatableNode> DelegatableNode.traverseAncestors(
         type: NodeKind<T>,
         onPreVisit: (T) -> Unit,
@@ -289,6 +331,9 @@
         return rootFocusNode.findActiveFocusNode()?.focusRect()
     }
 
+    override val rootState: FocusState
+        get() = rootFocusNode.focusState
+
     private fun DelegatableNode.lastLocalKeyInputNode(): Modifier.Node? {
         var focusedKeyInputNode: Modifier.Node? = null
         visitLocalDescendants(Nodes.FocusTarget or Nodes.KeyInput) { modifierNode ->
@@ -299,26 +344,13 @@
         return focusedKeyInputNode
     }
 
-    // TODO(b/144116848): This is a hack to make Next/Previous wrap around. This must be
-    //  replaced by code that sends the move request back to the view system. The view system
-    //  will then pass focus to other views, and ultimately return back to this compose view.
-    private fun wrapAroundFocus(focusDirection: FocusDirection): Boolean {
-        // Wrap is not supported when this sub-hierarchy doesn't have focus.
-        if (!rootFocusNode.focusState.hasFocus || rootFocusNode.focusState.isFocused) return false
-
-        // Next and Previous wraps around.
-        when (focusDirection) {
-            Next, Previous -> {
-                // Clear Focus to send focus the root node.
-                clearFocus(force = false)
-                if (!rootFocusNode.focusState.isFocused) return false
-
-                // Wrap around by calling moveFocus after the root gains focus.
-                return moveFocus(focusDirection)
-            }
-            // We only wrap-around for 1D Focus search.
-            else -> return false
-        }
+    /**
+     * focus search in the Android framework wraps around for 1D focus search, but not for 2D focus
+     * search. This is a helper function that can be used to determine whether we should wrap around.
+     */
+    private fun FocusDirection.supportsWrapAroundFocus(): Boolean = when (this) {
+        Next, Previous -> true
+        else -> false
     }
 
     // TODO(b/307580000) Factor this out into a class to manage key inputs.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
index 97d7357..8c11a94 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
@@ -24,7 +24,6 @@
 import androidx.compose.ui.node.Nodes
 import androidx.compose.ui.node.visitChildren
 
-@Suppress("ConstPropertyName")
 private const val FocusRequesterNotInitialized = """
    FocusRequester is not initialized. Here are some possible fixes:
 
@@ -34,7 +33,6 @@
    response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
 """
 
-@Suppress("ConstPropertyName")
 private const val InvalidFocusRequesterInvocation = """
     Please check whether the focusRequester is FocusRequester.Cancel or FocusRequester.Default
     before invoking any functions on the focusRequester.
@@ -66,16 +64,15 @@
     }
 
     // TODO(b/245755256): Consider making this API Public.
-    internal fun focus(): Boolean {
+    internal fun focus(): Boolean = findFocusTargetNode { it.requestFocus() }
+
+    internal fun findFocusTargetNode(onFound: (FocusTargetNode) -> Boolean): Boolean {
         @OptIn(ExperimentalComposeUiApi::class)
         return findFocusTarget { focusTarget ->
-            val focusProperties = focusTarget.fetchFocusProperties()
-            if (focusProperties.canFocus) {
-                focusTarget.requestFocus()
+            if (focusTarget.fetchFocusProperties().canFocus) {
+                onFound(focusTarget)
             } else {
-                focusTarget.findChildCorrespondingToFocusEnter(Enter) {
-                    it.requestFocus()
-                }
+                focusTarget.findChildCorrespondingToFocusEnter(Enter, onFound)
             }
         }
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifierNode.kt
index 4d95db3..cb36d0e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifierNode.kt
@@ -38,8 +38,7 @@
 @OptIn(ExperimentalComposeUiApi::class)
 fun FocusRequesterModifierNode.requestFocus(): Boolean {
     visitSelfAndChildren(Nodes.FocusTarget) { focusTarget ->
-        val focusProperties = focusTarget.fetchFocusProperties()
-        return if (focusProperties.canFocus) {
+        return if (focusTarget.fetchFocusProperties().canFocus) {
             focusTarget.requestFocus()
         } else {
             focusTarget.findChildCorrespondingToFocusEnter(Enter) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt
index ea6ba20..489b51e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt
@@ -84,13 +84,22 @@
             // Clear focus from the current FocusTarget.
             // This currently clears focus from the entire hierarchy, but we can change the
             // implementation so that focus is sent to the immediate focus parent.
-            Active, Captured -> requireOwner().focusOwner.clearFocus(force = true)
-
-            // If an ActiveParent is deactivated, the entire subtree containing focus is
-            // deactivated, which means the Active node will also receive an onReset() call.
-            // This triggers a clearFocus call, which will notify all the focus event nodes
-            // associated with this FocusTargetNode.
-            ActiveParent, Inactive -> {}
+            Active, Captured -> {
+                requireOwner().focusOwner.clearFocus(
+                    force = true,
+                    refreshFocusEvents = true,
+                    clearOwnerFocus = false
+                )
+                // We don't clear the owner's focus yet, because this could trigger an initial
+                // focus scenario after the focus is cleared. Instead, we schedule invalidation
+                // after onApplyChanges. The FocusInvalidationManager contains the invalidation
+                // logic and calls clearFocus() on the owner after all the nodes in the hierarchy
+                // are invalidated.
+                invalidateFocusTarget()
+            }
+            // This node might be reused, so reset the state to Inactive.
+            ActiveParent -> requireTransactionManager().withNewTransaction { focusState = Inactive }
+            Inactive -> {}
         }
         // This node might be reused, so we reset its state.
         committedFocusState = null
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
index 88cc1ee..d585d28 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.node.Nodes.FocusTarget
 import androidx.compose.ui.node.nearestAncestor
 import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.node.requireOwner
 
 /**
  * Request focus for this node.
@@ -39,12 +40,14 @@
  * [FocusNode][FocusTargetNode]'s parent [FocusNode][FocusTargetNode].
  */
 @OptIn(ExperimentalComposeUiApi::class)
-internal fun FocusTargetNode.requestFocus(): Boolean {
+internal fun FocusTargetNode.requestFocus(): Boolean = requestFocus(Enter) ?: false
+
+internal fun FocusTargetNode.requestFocus(focusDirection: FocusDirection): Boolean? {
     return requireTransactionManager().withNewTransaction {
-        when (performCustomRequestFocus(Enter)) {
+        when (performCustomRequestFocus(focusDirection)) {
             None -> performRequestFocus()
             Redirected -> true
-            Cancelled, RedirectCancelled -> false
+            Cancelled, RedirectCancelled -> null
         }
     }
 }
@@ -244,7 +247,7 @@
 }
 
 private fun FocusTargetNode.requestFocusForOwner(): Boolean {
-    return coordinator?.layoutNode?.owner?.requestFocus() ?: error("Owner not initialized.")
+    return requireOwner().focusOwner.requestFocusForOwner(null, null)
 }
 
 private fun FocusTargetNode.requireActiveChild(): FocusTargetNode {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
index e0f38c5..7660e54 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
@@ -91,6 +91,7 @@
  *
  * @param focusDirection The requested direction to move focus.
  * @param layoutDirection Whether the layout is RTL or LTR.
+ * @param previouslyFocusedRect The bounds of the previously focused item.
  * @param onFound This lambda is invoked if focus search finds the next focus node.
  * @return if no focus node is found, we return false. If we receive a cancel, we return null
  * otherwise we return the result of [onFound].
@@ -99,16 +100,19 @@
 internal fun FocusTargetNode.focusSearch(
     focusDirection: FocusDirection,
     layoutDirection: LayoutDirection,
+    previouslyFocusedRect: Rect?,
     onFound: (FocusTargetNode) -> Boolean
-): Boolean {
+): Boolean? {
     return when (focusDirection) {
         Next, Previous -> oneDimensionalFocusSearch(focusDirection, onFound)
-        Left, Right, Up, Down -> twoDimensionalFocusSearch(focusDirection, onFound) ?: false
+        Left, Right, Up, Down ->
+            twoDimensionalFocusSearch(focusDirection, previouslyFocusedRect, onFound)
         @OptIn(ExperimentalComposeUiApi::class)
         Enter -> {
             // we search among the children of the active item.
             val direction = when (layoutDirection) { Rtl -> Left; Ltr -> Right }
-            findActiveFocusNode()?.twoDimensionalFocusSearch(direction, onFound) ?: false
+            findActiveFocusNode()
+                ?.twoDimensionalFocusSearch(direction, previouslyFocusedRect, onFound)
         }
         @OptIn(ExperimentalComposeUiApi::class)
         Exit -> findActiveFocusNode()?.findNonDeactivatedParent().let {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
index 987cc9a..d883a49 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
@@ -51,10 +51,17 @@
  */
 internal fun FocusTargetNode.twoDimensionalFocusSearch(
     direction: FocusDirection,
+    previouslyFocusedRect: Rect?,
     onFound: (FocusTargetNode) -> Boolean
 ): Boolean? {
     when (focusState) {
-        Inactive -> return if (fetchFocusProperties().canFocus) onFound.invoke(this) else false
+        Inactive -> return if (fetchFocusProperties().canFocus) {
+            onFound.invoke(this)
+        } else if (previouslyFocusedRect == null) {
+            findChildCorrespondingToFocusEnter(direction, onFound)
+        } else {
+            searchChildren(previouslyFocusedRect, direction, onFound)
+        }
         ActiveParent -> {
             val focusedChild = activeChild ?: error(NoActiveChild)
             // For 2D focus search we only search among siblings. You have to use DPad Center or
@@ -66,15 +73,20 @@
 
                 ActiveParent -> {
                     // If the focusedChild is an intermediate parent, we search among its children.
-                    val found = focusedChild.twoDimensionalFocusSearch(direction, onFound)
+                    val found = focusedChild
+                        .twoDimensionalFocusSearch(direction, previouslyFocusedRect, onFound)
                     if (found != false) return found
 
                     // We search among the siblings of the parent.
-                    return generateAndSearchChildren(focusedChild.activeNode(), direction, onFound)
+                    return generateAndSearchChildren(
+                        focusedChild.activeNode().focusRect(),
+                        direction,
+                        onFound
+                    )
                 }
                 // Search for the next eligible sibling.
                 Active, Captured ->
-                    return generateAndSearchChildren(focusedChild, direction, onFound)
+                    return generateAndSearchChildren(focusedChild.focusRect(), direction, onFound)
                 Inactive -> error(NoActiveChild)
             }
         }
@@ -132,7 +144,7 @@
 // Search among your children for the next child.
 // If the next child is not found, generate more children by requesting a beyondBoundsLayout.
 private fun FocusTargetNode.generateAndSearchChildren(
-    focusedItem: FocusTargetNode,
+    focusedItem: Rect,
     direction: FocusDirection,
     onFound: (FocusTargetNode) -> Boolean
 ): Boolean {
@@ -152,7 +164,7 @@
 }
 
 private fun FocusTargetNode.searchChildren(
-    focusedItem: FocusTargetNode,
+    focusedItem: Rect,
     direction: FocusDirection,
     onFound: (FocusTargetNode) -> Boolean
 ): Boolean {
@@ -162,7 +174,7 @@
         }
     }
     while (children.isNotEmpty()) {
-        val nextItem = children.findBestCandidate(focusedItem.focusRect(), direction)
+        val nextItem = children.findBestCandidate(focusedItem, direction)
             ?: return false
 
         // If the result is not deactivated, this is a valid next item.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 223a4012..9df114d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -64,7 +64,6 @@
 /**
  * Enable to log changes to the LayoutNode tree.  This logging is quite chatty.
  */
-@Suppress("ConstPropertyName")
 private const val DebugChanges = false
 
 private val DefaultDensity = Density(1f)
@@ -1061,7 +1060,11 @@
     private fun invalidateFocusOnDetach() {
         nodes.tailToHead(FocusTarget) {
             if (it.focusState.isFocused) {
-                requireOwner().focusOwner.clearFocus(force = true, refreshFocusEvents = false)
+                requireOwner().focusOwner.clearFocus(
+                    force = true,
+                    refreshFocusEvents = false,
+                    clearOwnerFocus = true
+                )
                 it.scheduleInvalidationForFocusEvents()
             }
         }
@@ -1373,6 +1376,7 @@
         /**
          * Constant used by [placeOrder].
          */
+        @Suppress("ConstPropertyName")
         internal const val NotPlacedPlaceOrder = Int.MAX_VALUE
 
         /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index 34ee53c3..4e6de06 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -1093,9 +1093,13 @@
 
     fun shouldSharePointerInputWithSiblings(): Boolean {
         val start = headNode(Nodes.PointerInput.includeSelfInTraversal) ?: return false
-        start.visitLocalDescendants(Nodes.PointerInput) {
-            if (it.sharePointerInputWithSiblings()) return true
+
+        if (start.isAttached) {
+            start.visitLocalDescendants(Nodes.PointerInput) {
+                if (it.sharePointerInputWithSiblings()) return true
+            }
         }
+
         return false
     }
 
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
index f0c5115..596a478 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
@@ -76,7 +76,6 @@
 import androidx.compose.ui.node.RootForTest
 import androidx.compose.ui.semantics.EmptySemanticsElement
 import androidx.compose.ui.semantics.SemanticsOwner
-import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.font.createFontFamilyResolver
 import androidx.compose.ui.text.input.TextInputService
 import androidx.compose.ui.text.platform.FontLoader
@@ -92,7 +91,6 @@
 
 @OptIn(
     ExperimentalComposeUiApi::class,
-    ExperimentalTextApi::class,
     InternalCoreApi::class,
     InternalComposeUiApi::class
 )
@@ -100,7 +98,7 @@
     private val platformInputService: PlatformInput,
     private val component: PlatformComponent,
     density: Density = Density(1f, 1f),
-    coroutineContext: CoroutineContext,
+    override val coroutineContext: CoroutineContext,
     val isPopup: Boolean = false,
     val isFocusable: Boolean = true,
     val onDismissRequest: (() -> Unit)? = null,
@@ -126,12 +124,12 @@
 
     private val semanticsModifier = EmptySemanticsElement
 
-    override val focusOwner: FocusOwner = FocusOwnerImpl {
-        registerOnEndApplyChangesListener(it)
-    }.apply {
-        // TODO(demin): support RTL [onRtlPropertiesChanged]
-        layoutDirection = LayoutDirection.Ltr
-    }
+    override val focusOwner: FocusOwner = FocusOwnerImpl(
+        onRequestApplyChangesListener = ::registerOnEndApplyChangesListener,
+        onRequestFocusForOwner = { _, _ -> true }, // TODO request focus from framework.
+        onClearFocusForOwner = {}, // TODO clear focus from framework.
+        layoutDirection = { layoutDirection } // TODO(demin): support RTL [onRtlPropertiesChanged].
+    )
 
     // TODO: Set the input mode. For now we don't support touch mode, (always in Key mode).
     private val _inputModeManager = InputModeManagerImpl(
@@ -188,8 +186,6 @@
             .onKeyEvent(onKeyEvent)
     }
 
-    override val coroutineContext: CoroutineContext = coroutineContext
-
     override val rootForTest = this
 
     override val snapshotObserver = OwnerSnapshotObserver { command ->
@@ -204,7 +200,8 @@
         snapshotObserver.startObserving()
         root.attach(this)
         focusOwner.focusTransactionManager.withNewTransaction {
-            focusOwner.takeFocus()
+            // TODO instead of taking focus here, call this when the owner gets focused.
+            focusOwner.takeFocus(Enter, previouslyFocusedRect = null)
         }
     }
 
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Barrier.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Barrier.java
index 2007e11..15d7e02 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Barrier.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Barrier.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.view.View;
@@ -158,29 +157,19 @@
 
     private void updateType(ConstraintWidget widget, int type, boolean isRtl) {
         mResolvedType = type;
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            // Pre JB MR1, left/right should take precedence, unless they are
-            // not defined and somehow a corresponding start/end constraint exists
+
+        if (isRtl) {
+            if (mIndicatedType == START) {
+                mResolvedType = RIGHT;
+            } else if (mIndicatedType == END) {
+                mResolvedType = LEFT;
+            }
+        } else {
             if (mIndicatedType == START) {
                 mResolvedType = LEFT;
             } else if (mIndicatedType == END) {
                 mResolvedType = RIGHT;
             }
-        } else {
-            // Post JB MR1, if start/end are defined, they take precedence over left/right
-            if (isRtl) {
-                if (mIndicatedType == START) {
-                    mResolvedType = RIGHT;
-                } else if (mIndicatedType == END) {
-                    mResolvedType = LEFT;
-                }
-            } else {
-                if (mIndicatedType == START) {
-                    mResolvedType = LEFT;
-                } else if (mIndicatedType == END) {
-                    mResolvedType = RIGHT;
-                }
-            }
         }
         if (widget instanceof androidx.constraintlayout.core.widgets.Barrier) {
             androidx.constraintlayout.core.widgets.Barrier barrier =
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
index 24710a1..bfc5c8b 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
@@ -1327,11 +1327,6 @@
             int resolvedGuideBegin = layoutParams.mResolvedGuideBegin;
             int resolvedGuideEnd = layoutParams.mResolvedGuideEnd;
             float resolvedGuidePercent = layoutParams.mResolvedGuidePercent;
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-                resolvedGuideBegin = layoutParams.guideBegin;
-                resolvedGuideEnd = layoutParams.guideEnd;
-                resolvedGuidePercent = layoutParams.guidePercent;
-            }
             if (resolvedGuidePercent != UNSET) {
                 guideline.setGuidePercent(resolvedGuidePercent);
             } else if (resolvedGuideBegin != UNSET) {
@@ -1349,33 +1344,6 @@
             int resolveGoneRightMargin = layoutParams.mResolveGoneRightMargin;
             float resolvedHorizontalBias = layoutParams.mResolvedHorizontalBias;
 
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-                // Pre JB MR1, left/right should take precedence, unless they are
-                // not defined and somehow a corresponding start/end constraint exists
-                resolvedLeftToLeft = layoutParams.leftToLeft;
-                resolvedLeftToRight = layoutParams.leftToRight;
-                resolvedRightToLeft = layoutParams.rightToLeft;
-                resolvedRightToRight = layoutParams.rightToRight;
-                resolveGoneLeftMargin = layoutParams.goneLeftMargin;
-                resolveGoneRightMargin = layoutParams.goneRightMargin;
-                resolvedHorizontalBias = layoutParams.horizontalBias;
-
-                if (resolvedLeftToLeft == UNSET && resolvedLeftToRight == UNSET) {
-                    if (layoutParams.startToStart != UNSET) {
-                        resolvedLeftToLeft = layoutParams.startToStart;
-                    } else if (layoutParams.startToEnd != UNSET) {
-                        resolvedLeftToRight = layoutParams.startToEnd;
-                    }
-                }
-                if (resolvedRightToLeft == UNSET && resolvedRightToRight == UNSET) {
-                    if (layoutParams.endToStart != UNSET) {
-                        resolvedRightToLeft = layoutParams.endToStart;
-                    } else if (layoutParams.endToEnd != UNSET) {
-                        resolvedRightToRight = layoutParams.endToEnd;
-                    }
-                }
-            }
-
             // Circular constraint
             if (layoutParams.circleConstraint != UNSET) {
                 ConstraintWidget target = idToWidget.get(layoutParams.circleConstraint);
diff --git a/core/core-ktx/src/main/java/androidx/core/os/Bundle.kt b/core/core-ktx/src/main/java/androidx/core/os/Bundle.kt
index 9bda3bb..601fbf8 100644
--- a/core/core-ktx/src/main/java/androidx/core/os/Bundle.kt
+++ b/core/core-ktx/src/main/java/androidx/core/os/Bundle.kt
@@ -91,8 +91,8 @@
             is Serializable -> putSerializable(key, value)
 
             else -> {
-                if (Build.VERSION.SDK_INT >= 18 && value is IBinder) {
-                    BundleApi18ImplKt.putBinder(this, key, value)
+                if (value is IBinder) {
+                    this.putBinder(key, value)
                 } else if (Build.VERSION.SDK_INT >= 21 && value is Size) {
                     BundleApi21ImplKt.putSize(this, key, value)
                 } else if (Build.VERSION.SDK_INT >= 21 && value is SizeF) {
@@ -111,13 +111,6 @@
  */
 public fun bundleOf(): Bundle = Bundle(0)
 
-@RequiresApi(18)
-private object BundleApi18ImplKt {
-    @DoNotInline
-    @JvmStatic
-    fun putBinder(bundle: Bundle, key: String, value: IBinder?) = bundle.putBinder(key, value)
-}
-
 @RequiresApi(21)
 private object BundleApi21ImplKt {
     @DoNotInline
diff --git a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
index 454cfcd..c5a78b2 100644
--- a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
@@ -686,12 +686,6 @@
         // Add an action so that we start getting the view
         builder.addAction(new NotificationCompat.Action(null, "action", null));
 
-        // Before Jellybean, there was no big view; expect null
-        if (Build.VERSION.SDK_INT < 16) {
-            assertNull(builder.createHeadsUpContentView());
-            return;
-        }
-
         // Expect the standard big notification template
         RemoteViews standardView = builder.createBigContentView();
         assertNotNull(standardView);
diff --git a/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java
index 16c5ef7..e877a26 100644
--- a/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java
@@ -179,8 +179,7 @@
         // For pre-v15 devices we should get a drawable that corresponds to the density of the
         // current device. For v15+ devices we should get a drawable that corresponds to the
         // density requested in the API call.
-        final int expectedSizeForMediumDensity = (SDK_INT < 15) ?
-                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 12;
+        final int expectedSizeForMediumDensity = 12;
         assertEquals("Unthemed density-aware drawable load: medium width",
                 expectedSizeForMediumDensity, unthemedDrawableForMediumDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: medium height",
@@ -190,11 +189,8 @@
         final Drawable unthemedDrawableForHighDensity =
                 ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                         DisplayMetrics.DENSITY_HIGH, null);
-        // For pre-v15 devices we should get a drawable that corresponds to the density of the
-        // current device. For v15+ devices we should get a drawable that corresponds to the
-        // density requested in the API call.
-        final int expectedSizeForHighDensity = (SDK_INT < 15) ?
-                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 21;
+
+        final int expectedSizeForHighDensity = 21;
         assertEquals("Unthemed density-aware drawable load: high width",
                 expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: high height",
@@ -203,11 +199,8 @@
         final Drawable unthemedDrawableForXHighDensity =
                 ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                         DisplayMetrics.DENSITY_XHIGH, null);
-        // For pre-v15 devices we should get a drawable that corresponds to the density of the
-        // current device. For v15+ devices we should get a drawable that corresponds to the
-        // density requested in the API call.
-        final int expectedSizeForXHighDensity = (SDK_INT < 15) ?
-                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 32;
+
+        final int expectedSizeForXHighDensity = 32;
         assertEquals("Unthemed density-aware drawable load: xhigh width",
                 expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: xhigh height",
@@ -216,11 +209,8 @@
         final Drawable unthemedDrawableForXXHighDensity =
                 ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                         DisplayMetrics.DENSITY_XXHIGH, null);
-        // For pre-v15 devices we should get a drawable that corresponds to the density of the
-        // current device. For v15+ devices we should get a drawable that corresponds to the
-        // density requested in the API call.
-        final int expectedSizeForXXHighDensity = (SDK_INT < 15) ?
-                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 54;
+
+        final int expectedSizeForXXHighDensity = 54;
         assertEquals("Unthemed density-aware drawable load: xxhigh width",
                 expectedSizeForXXHighDensity, unthemedDrawableForXXHighDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: xxhigh height",
diff --git a/core/core/src/androidTest/java/androidx/core/graphics/TypefaceCompatUtilTest.java b/core/core/src/androidTest/java/androidx/core/graphics/TypefaceCompatUtilTest.java
index 245f6c5..e282798 100644
--- a/core/core/src/androidTest/java/androidx/core/graphics/TypefaceCompatUtilTest.java
+++ b/core/core/src/androidTest/java/androidx/core/graphics/TypefaceCompatUtilTest.java
@@ -20,7 +20,6 @@
 import android.content.ContentUris;
 import android.content.Context;
 import android.net.Uri;
-import android.os.Build;
 
 import androidx.annotation.RequiresApi;
 import androidx.core.provider.MockFontProvider;
@@ -43,10 +42,6 @@
     @Test
     @RequiresApi(19)
     public void testMmapNullPfd() {
-        if (Build.VERSION.SDK_INT < 19) {
-            // The API tested here requires SDK level 19.
-            return;
-        }
         final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(MockFontProvider.AUTHORITY).build();
         final Uri fileUri = ContentUris.withAppendedId(uri, MockFontProvider.INVALID_FONT_FILE_ID);
diff --git a/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java b/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java
index 2c9770b..6cf5ee3 100644
--- a/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java
@@ -25,7 +25,6 @@
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 import android.location.Location;
-import android.os.Build;
 import android.os.SystemClock;
 
 import androidx.test.filters.SmallTest;
@@ -40,16 +39,10 @@
     @Test
     public void testGetElapsedRealtimeNanos() {
         long locationElapsedRealtimeNs;
-        if (Build.VERSION.SDK_INT >= 17) {
-            locationElapsedRealtimeNs = SystemClock.elapsedRealtimeNanos();
-        } else {
-            locationElapsedRealtimeNs = MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
-        }
+        locationElapsedRealtimeNs = SystemClock.elapsedRealtimeNanos();
 
         Location location = new Location("");
-        if (Build.VERSION.SDK_INT >= 17) {
-            location.setElapsedRealtimeNanos(locationElapsedRealtimeNs);
-        }
+        location.setElapsedRealtimeNanos(locationElapsedRealtimeNs);
         location.setTime(System.currentTimeMillis());
 
         assertTrue(NANOSECONDS.toMillis(Math.abs(
@@ -62,9 +55,7 @@
         long locationElapsedRealtimeMs = SystemClock.elapsedRealtime();
 
         Location location = new Location("");
-        if (Build.VERSION.SDK_INT >= 17) {
-            location.setElapsedRealtimeNanos(MILLISECONDS.toNanos(locationElapsedRealtimeMs));
-        }
+        location.setElapsedRealtimeNanos(MILLISECONDS.toNanos(locationElapsedRealtimeMs));
         location.setTime(System.currentTimeMillis());
 
         assertTrue(Math.abs(
diff --git a/core/core/src/androidTest/java/androidx/core/view/MarginLayoutParamsCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/MarginLayoutParamsCompatTest.java
index 77676e7..7289810 100644
--- a/core/core/src/androidTest/java/androidx/core/view/MarginLayoutParamsCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/MarginLayoutParamsCompatTest.java
@@ -37,14 +37,8 @@
                 MarginLayoutParamsCompat.getLayoutDirection(mlp));
 
         MarginLayoutParamsCompat.setLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_RTL);
-        if (Build.VERSION.SDK_INT >= 17) {
-            assertEquals("RTL layout direction", ViewCompat.LAYOUT_DIRECTION_RTL,
-                    MarginLayoutParamsCompat.getLayoutDirection(mlp));
-        } else {
-            assertEquals("Still LTR layout direction on older devices",
-                    ViewCompat.LAYOUT_DIRECTION_LTR,
-                    MarginLayoutParamsCompat.getLayoutDirection(mlp));
-        }
+        assertEquals("RTL layout direction", ViewCompat.LAYOUT_DIRECTION_RTL,
+                MarginLayoutParamsCompat.getLayoutDirection(mlp));
 
         MarginLayoutParamsCompat.setLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_LTR);
         assertEquals("Back to LTR layout direction", ViewCompat.LAYOUT_DIRECTION_LTR,
diff --git a/core/core/src/androidTest/res/values-hdpi/dimens.xml b/core/core/src/androidTest/res/values-hdpi/dimens.xml
deleted file mode 100755
index 3126a6e..0000000
--- a/core/core/src/androidTest/res/values-hdpi/dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-<resources>
-    <dimen name="density_aware_size">14dip</dimen>
-</resources>
\ No newline at end of file
diff --git a/core/core/src/androidTest/res/values-mdpi/dimens.xml b/core/core/src/androidTest/res/values-mdpi/dimens.xml
deleted file mode 100755
index ada2cae..0000000
--- a/core/core/src/androidTest/res/values-mdpi/dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-<resources>
-    <dimen name="density_aware_size">12dip</dimen>
-</resources>
\ No newline at end of file
diff --git a/core/core/src/androidTest/res/values-xhdpi/dimens.xml b/core/core/src/androidTest/res/values-xhdpi/dimens.xml
deleted file mode 100755
index 21125b8..0000000
--- a/core/core/src/androidTest/res/values-xhdpi/dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-<resources>
-    <dimen name="density_aware_size">16dip</dimen>
-</resources>
\ No newline at end of file
diff --git a/core/core/src/androidTest/res/values-xxhdpi/dimens.xml b/core/core/src/androidTest/res/values-xxhdpi/dimens.xml
deleted file mode 100755
index aaee862..0000000
--- a/core/core/src/androidTest/res/values-xxhdpi/dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-<resources>
-    <dimen name="density_aware_size">18dip</dimen>
-</resources>
\ No newline at end of file
diff --git a/core/core/src/main/java/androidx/core/accessibilityservice/AccessibilityServiceInfoCompat.java b/core/core/src/main/java/androidx/core/accessibilityservice/AccessibilityServiceInfoCompat.java
index 9f4cd4e..d1dc409 100644
--- a/core/core/src/main/java/androidx/core/accessibilityservice/AccessibilityServiceInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/accessibilityservice/AccessibilityServiceInfoCompat.java
@@ -280,14 +280,7 @@
      */
     @SuppressWarnings("deprecation")
     public static int getCapabilities(@NonNull AccessibilityServiceInfo info) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return info.getCapabilities();
-        } else {
-            if (info.getCanRetrieveWindowContent()) {
-                return CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
-            }
-            return 0;
-        }
+        return info.getCapabilities();
     }
 
     /**
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompat.java b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
index 0cd62f5..bf3668fc 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompat.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
@@ -2219,10 +2219,6 @@
          */
         @SuppressLint("BuilderSetStyle")  // This API is copied from Notification.Builder
         public @Nullable RemoteViews createBigContentView() {
-            // Before Jellybean, there was no "big" notification view
-            if (Build.VERSION.SDK_INT < 16) {
-                return null;
-            }
             // If the user setCustomBigContentView(), return it if appropriate for the style.
             if (mBigContentView != null && useExistingRemoteView()) {
                 return mBigContentView;
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
index 927e008..9f30326 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
@@ -157,9 +157,7 @@
             mContentView = b.mContentView;
             mBigContentView = b.mBigContentView;
         }
-        if (Build.VERSION.SDK_INT >= 17) {
-            Api17Impl.setShowWhen(mBuilder, b.mShowWhen);
-        }
+        mBuilder.setShowWhen(b.mShowWhen);
         if (Build.VERSION.SDK_INT >= 19) {
             if (Build.VERSION.SDK_INT < 21) {
                 final List<String> people = combineLists(getPeople(b.mPersonList), b.mPeople);
@@ -597,23 +595,6 @@
 
     /**
      * A class for wrapping calls to {@link NotificationCompatBuilder} methods which
-     * were added in API 17; these calls must be wrapped to avoid performance issues.
-     * See the UnsafeNewApiCall lint rule for more details.
-     */
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-        }
-
-        @DoNotInline
-        static Notification.Builder setShowWhen(Notification.Builder builder, boolean show) {
-            return builder.setShowWhen(show);
-        }
-
-    }
-
-    /**
-     * A class for wrapping calls to {@link NotificationCompatBuilder} methods which
      * were added in API 19; these calls must be wrapped to avoid performance issues.
      * See the UnsafeNewApiCall lint rule for more details.
      */
diff --git a/core/core/src/main/java/androidx/core/app/ShareCompat.java b/core/core/src/main/java/androidx/core/app/ShareCompat.java
index 7f4b1d0..818210a 100644
--- a/core/core/src/main/java/androidx/core/app/ShareCompat.java
+++ b/core/core/src/main/java/androidx/core/app/ShareCompat.java
@@ -16,8 +16,6 @@
 
 package androidx.core.app;
 
-import static android.os.Build.VERSION.SDK_INT;
-
 import static androidx.core.util.Preconditions.checkNotNull;
 
 import android.app.Activity;
@@ -246,12 +244,6 @@
                 + shareIntent.getContext().getClass().getName());
         provider.setShareIntent(shareIntent.getIntent());
         item.setActionProvider(provider);
-
-        if (SDK_INT < 16) {
-            if (!item.hasSubMenu()) {
-                item.setIntent(shareIntent.createChooserIntent());
-            }
-        }
     }
 
     /**
diff --git a/core/core/src/main/java/androidx/core/content/ContextCompat.java b/core/core/src/main/java/androidx/core/content/ContextCompat.java
index 61c464a..8464b94 100644
--- a/core/core/src/main/java/androidx/core/content/ContextCompat.java
+++ b/core/core/src/main/java/androidx/core/content/ContextCompat.java
@@ -907,12 +907,12 @@
 
         // The Android framework supports per-app locales on API 33, so we assume the
         // configuration has been updated after API 32.
-        if (Build.VERSION.SDK_INT <= 32 && Build.VERSION.SDK_INT >= 17) {
+        if (Build.VERSION.SDK_INT <= 32) {
             if (!locales.isEmpty()) {
                 Configuration newConfig = new Configuration(
                         context.getResources().getConfiguration());
                 ConfigurationCompat.setLocales(newConfig, locales);
-                return Api17Impl.createConfigurationContext(context, newConfig);
+                return context.createConfigurationContext(newConfig);
             }
         }
         return context;
@@ -1006,24 +1006,18 @@
                 SERVICES.put(TelecomManager.class, TELECOM_SERVICE);
                 SERVICES.put(TvInputManager.class, TV_INPUT_SERVICE);
             }
-            if (Build.VERSION.SDK_INT >= 19) {
-                SERVICES.put(AppOpsManager.class, APP_OPS_SERVICE);
-                SERVICES.put(CaptioningManager.class, CAPTIONING_SERVICE);
-                SERVICES.put(ConsumerIrManager.class, CONSUMER_IR_SERVICE);
-                SERVICES.put(PrintManager.class, PRINT_SERVICE);
-            }
-            if (Build.VERSION.SDK_INT >= 18) {
-                SERVICES.put(BluetoothManager.class, BLUETOOTH_SERVICE);
-            }
-            if (Build.VERSION.SDK_INT >= 17) {
-                SERVICES.put(DisplayManager.class, DISPLAY_SERVICE);
-                SERVICES.put(UserManager.class, USER_SERVICE);
-            }
-            if (Build.VERSION.SDK_INT >= 16) {
-                SERVICES.put(InputManager.class, INPUT_SERVICE);
-                SERVICES.put(MediaRouter.class, MEDIA_ROUTER_SERVICE);
-                SERVICES.put(NsdManager.class, NSD_SERVICE);
-            }
+
+            SERVICES.put(AppOpsManager.class, APP_OPS_SERVICE);
+            SERVICES.put(CaptioningManager.class, CAPTIONING_SERVICE);
+            SERVICES.put(ConsumerIrManager.class, CONSUMER_IR_SERVICE);
+            SERVICES.put(PrintManager.class, PRINT_SERVICE);
+            SERVICES.put(BluetoothManager.class, BLUETOOTH_SERVICE);
+            SERVICES.put(DisplayManager.class, DISPLAY_SERVICE);
+            SERVICES.put(UserManager.class, USER_SERVICE);
+
+            SERVICES.put(InputManager.class, INPUT_SERVICE);
+            SERVICES.put(MediaRouter.class, MEDIA_ROUTER_SERVICE);
+            SERVICES.put(NsdManager.class, NSD_SERVICE);
             SERVICES.put(AccessibilityManager.class, ACCESSIBILITY_SERVICE);
             SERVICES.put(AccountManager.class, ACCOUNT_SERVICE);
             SERVICES.put(ActivityManager.class, ACTIVITY_SERVICE);
@@ -1056,18 +1050,6 @@
         }
     }
 
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static Context createConfigurationContext(Context obj, Configuration config) {
-            return obj.createConfigurationContext(config);
-        }
-    }
-
     @RequiresApi(19)
     static class Api19Impl {
         private Api19Impl() {
diff --git a/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java b/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
index f8d72df..03e4741 100644
--- a/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
+++ b/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
@@ -831,8 +831,7 @@
             final boolean isHorizontal) {
         final ActivityManager am = (ActivityManager)
                 context.getSystemService(Context.ACTIVITY_SERVICE);
-        final boolean isLowRamDevice =
-                Build.VERSION.SDK_INT < 19 || am == null || am.isLowRamDevice();
+        final boolean isLowRamDevice = am == null || am.isLowRamDevice();
         final int iconDimensionDp = Math.max(1, isLowRamDevice
                 ? DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP : DEFAULT_MAX_ICON_DIMENSION_DP);
         final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
diff --git a/core/core/src/main/java/androidx/core/content/res/ConfigurationHelper.java b/core/core/src/main/java/androidx/core/content/res/ConfigurationHelper.java
index ca5094d..dcfb64c 100644
--- a/core/core/src/main/java/androidx/core/content/res/ConfigurationHelper.java
+++ b/core/core/src/main/java/androidx/core/content/res/ConfigurationHelper.java
@@ -16,8 +16,6 @@
 
 package androidx.core.content.res;
 
-import static android.os.Build.VERSION.SDK_INT;
-
 import android.content.res.Configuration;
 import android.content.res.Resources;
 
@@ -38,10 +36,6 @@
      * is computed and returned.</p>
      */
     public static int getDensityDpi(@NonNull Resources resources) {
-        if (SDK_INT >= 17) {
-            return resources.getConfiguration().densityDpi;
-        } else {
-            return resources.getDisplayMetrics().densityDpi;
-        }
+        return resources.getConfiguration().densityDpi;
     }
 }
diff --git a/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java b/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java
index b10563e..74ad5c7 100644
--- a/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java
+++ b/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java
@@ -54,10 +54,7 @@
      * @see Bitmap#hasMipMap()
      */
     public static boolean hasMipMap(@NonNull Bitmap bitmap) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            return Api17Impl.hasMipMap(bitmap);
-        }
-        return false;
+        return bitmap.hasMipMap();
     }
 
     /**
@@ -81,9 +78,7 @@
      * @see Bitmap#setHasMipMap(boolean)
      */
     public static void setHasMipMap(@NonNull Bitmap bitmap, boolean hasMipMap) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            Api17Impl.setHasMipMap(bitmap, hasMipMap);
-        }
+        bitmap.setHasMipMap(hasMipMap);
     }
 
     /**
@@ -334,23 +329,6 @@
         // This class is not instantiable.
     }
 
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static boolean hasMipMap(Bitmap bitmap) {
-            return bitmap.hasMipMap();
-        }
-
-        @DoNotInline
-        static void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
-            bitmap.setHasMipMap(hasMipMap);
-        }
-    }
-
     @RequiresApi(19)
     static class Api19Impl {
         private Api19Impl() {
diff --git a/core/core/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java b/core/core/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java
index 4f6a511..396cadf 100644
--- a/core/core/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java
+++ b/core/core/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java
@@ -18,14 +18,10 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
-import android.os.Build;
 import android.view.Display;
-import android.view.WindowManager;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 /**
  * Helper for accessing features in {@link android.hardware.display.DisplayManager}.
@@ -73,17 +69,9 @@
     @Nullable
     @SuppressWarnings("deprecation")
     public Display getDisplay(int displayId) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            return Api17Impl.getDisplay(
-                    (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE), displayId);
-        }
-
-        Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
-                .getDefaultDisplay();
-        if (display.getDisplayId() == displayId) {
-            return display;
-        }
-        return null;
+        DisplayManager displayManager =
+                (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+        return displayManager.getDisplay(displayId);
     }
 
     /**
@@ -94,14 +82,7 @@
     @SuppressWarnings("deprecation")
     @NonNull
     public Display[] getDisplays() {
-        if (Build.VERSION.SDK_INT >= 17) {
-            return Api17Impl.getDisplays(
-                    (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE));
-        }
-
-        Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
-                .getDefaultDisplay();
-        return new Display[] { display };
+        return ((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE)).getDisplays();
     }
 
     /**
@@ -123,33 +104,6 @@
     @NonNull
     @SuppressWarnings("deprecation")
     public Display[] getDisplays(@Nullable String category) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            return Api17Impl.getDisplays(
-                    (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE));
-        }
-        if (category == null) {
-            return new Display[0];
-        }
-
-        Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
-                .getDefaultDisplay();
-        return new Display[]{display};
-    }
-
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static Display getDisplay(DisplayManager displayManager, int displayId) {
-            return displayManager.getDisplay(displayId);
-        }
-
-        @DoNotInline
-        static Display[] getDisplays(DisplayManager displayManager) {
-            return displayManager.getDisplays();
-        }
+        return ((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE)).getDisplays();
     }
 }
diff --git a/core/core/src/main/java/androidx/core/location/LocationCompat.java b/core/core/src/main/java/androidx/core/location/LocationCompat.java
index 634dea0..883ff8a 100644
--- a/core/core/src/main/java/androidx/core/location/LocationCompat.java
+++ b/core/core/src/main/java/androidx/core/location/LocationCompat.java
@@ -16,14 +16,12 @@
 
 package androidx.core.location;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 import android.annotation.SuppressLint;
 import android.location.Location;
 import android.os.Build.VERSION;
 import android.os.Bundle;
-import android.os.SystemClock;
 
 import androidx.annotation.DoNotInline;
 import androidx.annotation.FloatRange;
@@ -111,11 +109,7 @@
      * location derivation is different from the system clock, the results may be inaccurate.
      */
     public static long getElapsedRealtimeNanos(@NonNull Location location) {
-        if (VERSION.SDK_INT >= 17) {
-            return Api17Impl.getElapsedRealtimeNanos(location);
-        } else {
-            return MILLISECONDS.toNanos(getElapsedRealtimeMillis(location));
-        }
+        return location.getElapsedRealtimeNanos();
     }
 
     /**
@@ -124,21 +118,7 @@
      * @see #getElapsedRealtimeNanos(Location)
      */
     public static long getElapsedRealtimeMillis(@NonNull Location location) {
-        if (VERSION.SDK_INT >= 17) {
-            return NANOSECONDS.toMillis(Api17Impl.getElapsedRealtimeNanos(location));
-        } else {
-            long timeDeltaMs = System.currentTimeMillis() - location.getTime();
-            long elapsedRealtimeMs = SystemClock.elapsedRealtime();
-            if (timeDeltaMs < 0) {
-                // don't return an elapsed realtime from the future
-                return elapsedRealtimeMs;
-            } else if (timeDeltaMs > elapsedRealtimeMs) {
-                // don't return an elapsed realtime from before boot
-                return 0;
-            } else {
-                return elapsedRealtimeMs - timeDeltaMs;
-            }
-        }
+        return NANOSECONDS.toMillis(location.getElapsedRealtimeNanos());
     }
 
     /**
@@ -517,16 +497,7 @@
      * @see android.location.LocationManager#addTestProvider
      */
     public static boolean isMock(@NonNull Location location) {
-        if (VERSION.SDK_INT >= 18) {
-            return Api18Impl.isMock(location);
-        } else {
-            Bundle extras = location.getExtras();
-            if (extras == null) {
-                return false;
-            }
-
-            return extras.getBoolean(EXTRA_IS_MOCK, false);
-        }
+        return location.isFromMockProvider();
     }
 
     /**
@@ -537,9 +508,9 @@
      * boolean extra with the key {@link #EXTRA_IS_MOCK} to mark the location as mock. Be aware that
      * this will overwrite any prior extra value under the same key.
      */
+    @SuppressLint("BanUncheckedReflection")
     public static void setMock(@NonNull Location location, boolean mock) {
-        if (VERSION.SDK_INT >= 18) {
-            try {
+        try {
                 getSetIsFromMockProviderMethod().invoke(location, mock);
             } catch (NoSuchMethodException e) {
                 Error error = new NoSuchMethodError();
@@ -552,25 +523,6 @@
             } catch (InvocationTargetException e) {
                 throw new RuntimeException(e);
             }
-        } else {
-            Bundle extras = location.getExtras();
-            if (extras == null) {
-                if (mock) {
-                    extras = new Bundle();
-                    extras.putBoolean(EXTRA_IS_MOCK, true);
-                    location.setExtras(extras);
-                }
-            } else {
-                if (mock) {
-                    extras.putBoolean(EXTRA_IS_MOCK, true);
-                } else {
-                    extras.remove(EXTRA_IS_MOCK);
-                    if (extras.isEmpty()) {
-                        location.setExtras(null);
-                    }
-                }
-            }
-        }
     }
 
     @RequiresApi(34)
@@ -960,30 +912,6 @@
         }
     }
 
-    @RequiresApi(18)
-    private static class Api18Impl {
-
-        private Api18Impl() {
-        }
-
-        @DoNotInline
-        static boolean isMock(Location location) {
-            return location.isFromMockProvider();
-        }
-    }
-
-    @RequiresApi(17)
-    private static class Api17Impl {
-
-        private Api17Impl() {
-        }
-
-        @DoNotInline
-        static long getElapsedRealtimeNanos(Location location) {
-            return location.getElapsedRealtimeNanos();
-        }
-    }
-
     private static Method getSetIsFromMockProviderMethod() throws NoSuchMethodException {
         if (sSetIsFromMockProviderMethod == null) {
             sSetIsFromMockProviderMethod = Location.class.getDeclaredMethod("setIsFromMockProvider",
diff --git a/core/core/src/main/java/androidx/core/os/BundleCompat.java b/core/core/src/main/java/androidx/core/os/BundleCompat.java
index 94555cf..3aea8a8 100644
--- a/core/core/src/main/java/androidx/core/os/BundleCompat.java
+++ b/core/core/src/main/java/androidx/core/os/BundleCompat.java
@@ -21,7 +21,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcelable;
-import android.util.Log;
 import android.util.SparseArray;
 
 import androidx.annotation.DoNotInline;
@@ -29,8 +28,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 
 /**
@@ -192,11 +189,7 @@
      */
     @Nullable
     public static IBinder getBinder(@NonNull Bundle bundle, @Nullable String key) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return Api18Impl.getBinder(bundle, key);
-        } else {
-            return BeforeApi18Impl.getBinder(bundle, key);
-        }
+        return bundle.getBinder(key);
     }
 
     /**
@@ -209,11 +202,7 @@
      */
     public static void putBinder(@NonNull Bundle bundle, @Nullable String key,
             @Nullable IBinder binder) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api18Impl.putBinder(bundle, key, binder);
-        } else {
-            BeforeApi18Impl.putBinder(bundle, key, binder);
-        }
+        bundle.putBinder(key, binder);
     }
 
     @RequiresApi(33)
@@ -246,84 +235,4 @@
             return in.getSparseParcelableArray(key, clazz);
         }
     }
-
-    @RequiresApi(18)
-    static class Api18Impl {
-        private Api18Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static IBinder getBinder(Bundle bundle, String key) {
-            return bundle.getBinder(key);
-        }
-
-        @DoNotInline
-        static void putBinder(Bundle bundle, String key, IBinder value) {
-            bundle.putBinder(key, value);
-        }
-    }
-
-    @SuppressLint("BanUncheckedReflection") // Only called prior to API 18
-    static class BeforeApi18Impl {
-        private static final String TAG = "BundleCompat";
-
-        private static Method sGetIBinderMethod;
-        private static boolean sGetIBinderMethodFetched;
-
-        private static Method sPutIBinderMethod;
-        private static boolean sPutIBinderMethodFetched;
-
-        private BeforeApi18Impl() {
-            // This class is not instantiable.
-        }
-
-        @SuppressWarnings("JavaReflectionMemberAccess")
-        public static IBinder getBinder(Bundle bundle, String key) {
-            if (!sGetIBinderMethodFetched) {
-                try {
-                    sGetIBinderMethod = Bundle.class.getMethod("getIBinder", String.class);
-                    sGetIBinderMethod.setAccessible(true);
-                } catch (NoSuchMethodException e) {
-                    Log.i(TAG, "Failed to retrieve getIBinder method", e);
-                }
-                sGetIBinderMethodFetched = true;
-            }
-
-            if (sGetIBinderMethod != null) {
-                try {
-                    return (IBinder) sGetIBinderMethod.invoke(bundle, key);
-                } catch (InvocationTargetException | IllegalAccessException
-                         | IllegalArgumentException e) {
-                    Log.i(TAG, "Failed to invoke getIBinder via reflection", e);
-                    sGetIBinderMethod = null;
-                }
-            }
-            return null;
-        }
-
-        @SuppressWarnings("JavaReflectionMemberAccess")
-        public static void putBinder(Bundle bundle, String key, IBinder binder) {
-            if (!sPutIBinderMethodFetched) {
-                try {
-                    sPutIBinderMethod =
-                            Bundle.class.getMethod("putIBinder", String.class, IBinder.class);
-                    sPutIBinderMethod.setAccessible(true);
-                } catch (NoSuchMethodException e) {
-                    Log.i(TAG, "Failed to retrieve putIBinder method", e);
-                }
-                sPutIBinderMethodFetched = true;
-            }
-
-            if (sPutIBinderMethod != null) {
-                try {
-                    sPutIBinderMethod.invoke(bundle, key, binder);
-                } catch (InvocationTargetException | IllegalAccessException
-                         | IllegalArgumentException e) {
-                    Log.i(TAG, "Failed to invoke putIBinder via reflection", e);
-                    sPutIBinderMethod = null;
-                }
-            }
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/os/ConfigurationCompat.java b/core/core/src/main/java/androidx/core/os/ConfigurationCompat.java
index e5e80dc..d4438ce 100644
--- a/core/core/src/main/java/androidx/core/os/ConfigurationCompat.java
+++ b/core/core/src/main/java/androidx/core/os/ConfigurationCompat.java
@@ -57,20 +57,7 @@
             @NonNull Configuration configuration, @NonNull LocaleListCompat locales) {
         if (SDK_INT >= 24) {
             Api24Impl.setLocales(configuration, locales);
-        } else if (SDK_INT >= 17) {
-            Api17Impl.setLocale(configuration, locales);
-        }
-    }
-
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static void setLocale(
-                @NonNull Configuration configuration, @NonNull LocaleListCompat locales) {
+        } else {
             if (!locales.isEmpty()) {
                 configuration.setLocale(locales.get(0));
             }
diff --git a/core/core/src/main/java/androidx/core/os/ProcessCompat.java b/core/core/src/main/java/androidx/core/os/ProcessCompat.java
index 6343cf3..f2c38d0 100644
--- a/core/core/src/main/java/androidx/core/os/ProcessCompat.java
+++ b/core/core/src/main/java/androidx/core/os/ProcessCompat.java
@@ -54,12 +54,8 @@
     public static boolean isApplicationUid(int uid) {
         if (Build.VERSION.SDK_INT >= 24) {
             return Api24Impl.isApplicationUid(uid);
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            return Api17Impl.isApplicationUid(uid);
-        } else if (Build.VERSION.SDK_INT == 16) {
-            return Api16Impl.isApplicationUid(uid);
         } else {
-            return true;
+            return Api17Impl.isApplicationUid(uid);
         }
     }
 
@@ -76,7 +72,6 @@
         }
     }
 
-    @RequiresApi(17)
     static class Api17Impl {
         private static final Object sResolvedLock = new Object();
 
@@ -114,43 +109,4 @@
             return true;
         }
     }
-
-    @RequiresApi(16)
-    static class Api16Impl {
-        private static final Object sResolvedLock = new Object();
-
-        private static Method sMethodUserIdIsAppMethod;
-        private static boolean sResolved;
-
-        private Api16Impl() {
-            // This class is non-instantiable.
-        }
-
-        @SuppressLint("PrivateApi")
-        @SuppressWarnings("CatchAndPrintStackTrace")
-        static boolean isApplicationUid(int uid) {
-            // In JELLY_BEAN_MR1, the equivalent isApp(int) hidden method was available on hidden
-            // class android.os.UserId.
-            try {
-                synchronized (sResolvedLock) {
-                    if (!sResolved) {
-                        sResolved = true;
-                        sMethodUserIdIsAppMethod = Class.forName("android.os.UserId")
-                                .getDeclaredMethod("isApp", int.class);
-                    }
-                }
-                if (sMethodUserIdIsAppMethod != null) {
-                    Boolean result = (Boolean) sMethodUserIdIsAppMethod.invoke(null, uid);
-                    if (result == null) {
-                        // This should never happen, as the method returns a boolean primitive.
-                        throw new NullPointerException();
-                    }
-                    return result;
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            return true;
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/os/TraceCompat.java b/core/core/src/main/java/androidx/core/os/TraceCompat.java
index f5022c6..7689534 100644
--- a/core/core/src/main/java/androidx/core/os/TraceCompat.java
+++ b/core/core/src/main/java/androidx/core/os/TraceCompat.java
@@ -81,7 +81,7 @@
     public static boolean isEnabled() {
         if (Build.VERSION.SDK_INT >= 29) {
             return Api29Impl.isEnabled();
-        } else if (Build.VERSION.SDK_INT >= 18) {
+        } else {
             try {
                 return (boolean) sIsTagEnabledMethod.invoke(null, sTraceTagApp);
             } catch (Exception e) {
@@ -105,9 +105,7 @@
      * most 127 Unicode code units long.
      */
     public static void beginSection(@NonNull String sectionName) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api18Impl.beginSection(sectionName);
-        }
+        Trace.beginSection(sectionName);
     }
 
     /**
@@ -118,9 +116,7 @@
      * thread.
      */
     public static void endSection() {
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api18Impl.endSection();
-        }
+        Trace.endSection();
     }
 
     /**
@@ -136,7 +132,7 @@
     public static void beginAsyncSection(@NonNull String methodName, int cookie) {
         if (Build.VERSION.SDK_INT >= 29) {
             Api29Impl.beginAsyncSection(methodName, cookie);
-        } else if (Build.VERSION.SDK_INT >= 18) {
+        } else {
             try {
                 sAsyncTraceBeginMethod.invoke(null, sTraceTagApp, methodName, cookie);
             } catch (Exception e) {
@@ -156,7 +152,7 @@
     public static void endAsyncSection(@NonNull String methodName, int cookie) {
         if (Build.VERSION.SDK_INT >= 29) {
             Api29Impl.endAsyncSection(methodName, cookie);
-        } else if (Build.VERSION.SDK_INT >= 18) {
+        } else {
             try {
                 sAsyncTraceEndMethod.invoke(null, sTraceTagApp, methodName, cookie);
             } catch (Exception e) {
@@ -175,7 +171,7 @@
     public static void setCounter(@NonNull String counterName, int counterValue) {
         if (Build.VERSION.SDK_INT >= 29) {
             Api29Impl.setCounter(counterName, counterValue);
-        } else if (Build.VERSION.SDK_INT >= 18) {
+        } else {
             try {
                 sTraceCounterMethod.invoke(null, sTraceTagApp, counterName, counterValue);
             } catch (Exception e) {
@@ -213,21 +209,4 @@
             Trace.setCounter(counterName, counterValue);
         }
     }
-
-    @RequiresApi(18)
-    static class Api18Impl {
-        private Api18Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static void beginSection(String sectionName) {
-            Trace.beginSection(sectionName);
-        }
-
-        @DoNotInline
-        static void endSection() {
-            Trace.endSection();
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/provider/FontProvider.java b/core/core/src/main/java/androidx/core/provider/FontProvider.java
index 6b2101b..1559dff 100644
--- a/core/core/src/main/java/androidx/core/provider/FontProvider.java
+++ b/core/core/src/main/java/androidx/core/provider/FontProvider.java
@@ -238,9 +238,7 @@
         void close();
 
         static ContentQueryWrapper make(Context context, Uri uri) {
-            if (Build.VERSION.SDK_INT < 16) {
-                return new ContentQueryWrapperBaseImpl(context);
-            } else if (Build.VERSION.SDK_INT < 24) {
+            if (Build.VERSION.SDK_INT < 24) {
                 return new ContentQueryWrapperApi16Impl(context, uri);
             } else {
                 return new ContentQueryWrapperApi24Impl(context, uri);
@@ -248,25 +246,6 @@
         }
     }
 
-    private static class ContentQueryWrapperBaseImpl implements ContentQueryWrapper {
-        private ContentResolver mResolver;
-        ContentQueryWrapperBaseImpl(Context context) {
-            mResolver = context.getContentResolver();
-        }
-
-        @Override
-        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-                String sortOrder, CancellationSignal cancellationSignal) {
-            return mResolver.query(uri, projection, selection, selectionArgs, sortOrder);
-        }
-
-        @Override
-        public void close() {
-            mResolver = null;
-        }
-    }
-
-    @RequiresApi(16)
     private static class ContentQueryWrapperApi16Impl implements ContentQueryWrapper {
         private final ContentProviderClient mClient;
         ContentQueryWrapperApi16Impl(Context context, Uri uri) {
diff --git a/core/core/src/main/java/androidx/core/text/TextUtilsCompat.java b/core/core/src/main/java/androidx/core/text/TextUtilsCompat.java
index 3085998..bdcd81ee 100644
--- a/core/core/src/main/java/androidx/core/text/TextUtilsCompat.java
+++ b/core/core/src/main/java/androidx/core/text/TextUtilsCompat.java
@@ -16,14 +16,10 @@
 
 package androidx.core.text;
 
-import static android.os.Build.VERSION.SDK_INT;
-
 import android.text.TextUtils;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.core.view.ViewCompat;
 
 import java.util.Locale;
@@ -32,9 +28,6 @@
  * Backwards compatible version of {@link TextUtils}.
  */
 public final class TextUtilsCompat {
-    private static final Locale ROOT = new Locale("", "");
-    private static final String ARAB_SCRIPT_SUBTAG = "Arab";
-    private static final String HEBR_SCRIPT_SUBTAG = "Hebr";
 
     /**
      * Html-encode the string.
@@ -44,40 +37,7 @@
      */
     @NonNull
     public static String htmlEncode(@NonNull String s) {
-        if (SDK_INT >= 17) {
-            return TextUtils.htmlEncode(s);
-        } else {
-            StringBuilder sb = new StringBuilder();
-            char c;
-            for (int i = 0; i < s.length(); i++) {
-                c = s.charAt(i);
-                switch (c) {
-                    case '<':
-                        sb.append("&lt;"); //$NON-NLS-1$
-                        break;
-                    case '>':
-                        sb.append("&gt;"); //$NON-NLS-1$
-                        break;
-                    case '&':
-                        sb.append("&amp;"); //$NON-NLS-1$
-                        break;
-                    case '\'':
-                        //http://www.w3.org/TR/xhtml1
-                        // The named character reference &apos; (the apostrophe, U+0027) was
-                        // introduced in XML 1.0 but does not appear in HTML. Authors should
-                        // therefore use &#39; instead of &apos; to work as expected in HTML 4
-                        // user agents.
-                        sb.append("&#39;"); //$NON-NLS-1$
-                        break;
-                    case '"':
-                        sb.append("&quot;"); //$NON-NLS-1$
-                        break;
-                    default:
-                        sb.append(c);
-                }
-            }
-            return sb.toString();
-        }
+        return TextUtils.htmlEncode(s);
     }
 
     /**
@@ -89,58 +49,9 @@
      *         {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
      */
     public static int getLayoutDirectionFromLocale(@Nullable Locale locale) {
-        if (SDK_INT >= 17) {
-            return Api17Impl.getLayoutDirectionFromLocale(locale);
-        } else {
-            if (locale != null && !locale.equals(ROOT)) {
-                final String scriptSubtag = ICUCompat.maximizeAndGetScript(locale);
-                if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale);
-
-                // This is intentionally limited to Arabic and Hebrew scripts, since older
-                // versions of Android platform only considered those scripts to be right-to-left.
-                if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG)
-                        || scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) {
-                    return ViewCompat.LAYOUT_DIRECTION_RTL;
-                }
-            }
-            return ViewCompat.LAYOUT_DIRECTION_LTR;
-        }
-    }
-
-    /**
-     * Fallback algorithm to detect the locale direction. Rely on the first char of the
-     * localized locale name. This will not work if the localized locale name is in English
-     * (this is the case for ICU 4.4 and "Urdu" script)
-     *
-     * @param locale the {@link Locale} for which we want the layout direction, maybe be
-     *               {@code null}.
-     * @return the layout direction, either {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
-     *         {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
-     */
-    private static int getLayoutDirectionFromFirstChar(@NonNull Locale locale) {
-        switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
-            case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
-            case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
-                return ViewCompat.LAYOUT_DIRECTION_RTL;
-
-            case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
-            default:
-                return ViewCompat.LAYOUT_DIRECTION_LTR;
-        }
+        return TextUtils.getLayoutDirectionFromLocale(locale);
     }
 
     private TextUtilsCompat() {
     }
-
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static int getLayoutDirectionFromLocale(Locale locale) {
-            return TextUtils.getLayoutDirectionFromLocale(locale);
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/view/GravityCompat.java b/core/core/src/main/java/androidx/core/view/GravityCompat.java
index 5935241..b7e1215 100644
--- a/core/core/src/main/java/androidx/core/view/GravityCompat.java
+++ b/core/core/src/main/java/androidx/core/view/GravityCompat.java
@@ -17,14 +17,10 @@
 
 package androidx.core.view;
 
-import static android.os.Build.VERSION.SDK_INT;
-
 import android.graphics.Rect;
 import android.view.Gravity;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
 /**
  * Compatibility shim for accessing newer functionality from {@link Gravity}.
@@ -65,11 +61,7 @@
      */
     public static void apply(int gravity, int w, int h, @NonNull Rect container,
             @NonNull Rect outRect, int layoutDirection) {
-        if (SDK_INT >= 17) {
-            Api17Impl.apply(gravity, w, h, container, outRect, layoutDirection);
-        } else {
-            Gravity.apply(gravity, w, h, container, outRect);
-        }
+        Gravity.apply(gravity, w, h, container, outRect, layoutDirection);
     }
 
     /**
@@ -99,11 +91,7 @@
      */
     public static void apply(int gravity, int w, int h, @NonNull Rect container,
             int xAdj, int yAdj, @NonNull Rect outRect, int layoutDirection) {
-        if (SDK_INT >= 17) {
-            Api17Impl.apply(gravity, w, h, container, xAdj, yAdj, outRect, layoutDirection);
-        } else {
-            Gravity.apply(gravity, w, h, container, xAdj, yAdj, outRect);
-        }
+        Gravity.apply(gravity, w, h, container, xAdj, yAdj, outRect, layoutDirection);
     }
 
     /**
@@ -128,11 +116,7 @@
      */
     public static void applyDisplay(int gravity, @NonNull Rect display, @NonNull Rect inoutObj,
             int layoutDirection) {
-        if (SDK_INT >= 17) {
-            Api17Impl.applyDisplay(gravity, display, inoutObj, layoutDirection);
-        } else {
-            Gravity.applyDisplay(gravity, display, inoutObj);
-        }
+        Gravity.applyDisplay(gravity, display, inoutObj, layoutDirection);
     }
 
     /**
@@ -147,38 +131,9 @@
      * @return gravity converted to absolute (horizontal) values.
      */
     public static int getAbsoluteGravity(int gravity, int layoutDirection) {
-        if (SDK_INT >= 17) {
-            return Gravity.getAbsoluteGravity(gravity, layoutDirection);
-        } else {
-            // Just strip off the relative bit to get LEFT/RIGHT.
-            return gravity & ~RELATIVE_LAYOUT_DIRECTION;
-        }
+        return Gravity.getAbsoluteGravity(gravity, layoutDirection);
     }
 
     private GravityCompat() {
     }
-
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static void apply(int gravity, int w, int h, Rect container, Rect outRect,
-                int layoutDirection) {
-            Gravity.apply(gravity, w, h, container, outRect, layoutDirection);
-        }
-
-        @DoNotInline
-        static void apply(int gravity, int w, int h, Rect container, int xAdj, int yAdj,
-                Rect outRect, int layoutDirection) {
-            Gravity.apply(gravity, w, h, container, xAdj, yAdj, outRect, layoutDirection);
-        }
-
-        @DoNotInline
-        static void applyDisplay(int gravity, Rect display, Rect inoutObj, int layoutDirection) {
-            Gravity.applyDisplay(gravity, display, inoutObj, layoutDirection);
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/view/MarginLayoutParamsCompat.java b/core/core/src/main/java/androidx/core/view/MarginLayoutParamsCompat.java
index 4237d43..8039d09 100644
--- a/core/core/src/main/java/androidx/core/view/MarginLayoutParamsCompat.java
+++ b/core/core/src/main/java/androidx/core/view/MarginLayoutParamsCompat.java
@@ -17,14 +17,10 @@
 
 package androidx.core.view;
 
-import static android.os.Build.VERSION.SDK_INT;
-
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
 /**
  * Helper for accessing API features in
@@ -44,11 +40,7 @@
      * @return the margin along the starting edge in pixels
      */
     public static int getMarginStart(@NonNull ViewGroup.MarginLayoutParams lp) {
-        if (SDK_INT >= 17) {
-            return Api17Impl.getMarginStart(lp);
-        } else {
-            return lp.leftMargin;
-        }
+        return lp.getMarginStart();
     }
 
     /**
@@ -63,11 +55,7 @@
      * @return the margin along the ending edge in pixels
      */
     public static int getMarginEnd(@NonNull ViewGroup.MarginLayoutParams lp) {
-        if (SDK_INT >= 17) {
-            return Api17Impl.getMarginEnd(lp);
-        } else {
-            return lp.rightMargin;
-        }
+        return lp.getMarginEnd();
     }
 
     /**
@@ -82,11 +70,7 @@
      * @param marginStart the desired start margin in pixels
      */
     public static void setMarginStart(@NonNull ViewGroup.MarginLayoutParams lp, int marginStart) {
-        if (SDK_INT >= 17) {
-            Api17Impl.setMarginStart(lp, marginStart);
-        } else {
-            lp.leftMargin = marginStart;
-        }
+        lp.setMarginStart(marginStart);
     }
 
     /**
@@ -101,11 +85,7 @@
      * @param marginEnd the desired end margin in pixels
      */
     public static void setMarginEnd(@NonNull ViewGroup.MarginLayoutParams lp, int marginEnd) {
-        if (SDK_INT >= 17) {
-            Api17Impl.setMarginEnd(lp, marginEnd);
-        } else {
-            lp.rightMargin = marginEnd;
-        }
+        lp.setMarginEnd(marginEnd);
     }
 
     /**
@@ -114,11 +94,7 @@
      * @return true if either marginStart or marginEnd has been set.
      */
     public static boolean isMarginRelative(@NonNull ViewGroup.MarginLayoutParams lp) {
-        if (SDK_INT >= 17) {
-            return Api17Impl.isMarginRelative(lp);
-        } else {
-            return false;
-        }
+        return lp.isMarginRelative();
     }
 
     /**
@@ -129,11 +105,7 @@
      */
     public static int getLayoutDirection(@NonNull ViewGroup.MarginLayoutParams lp) {
         int result;
-        if (SDK_INT >= 17) {
-            result = Api17Impl.getLayoutDirection(lp);
-        } else {
-            result = ViewCompat.LAYOUT_DIRECTION_LTR;
-        }
+        result = lp.getLayoutDirection();
 
         if ((result != ViewCompat.LAYOUT_DIRECTION_LTR)
                 && (result != ViewCompat.LAYOUT_DIRECTION_RTL)) {
@@ -154,9 +126,7 @@
      */
     public static void setLayoutDirection(@NonNull ViewGroup.MarginLayoutParams lp,
             int layoutDirection) {
-        if (SDK_INT >= 17) {
-            Api17Impl.setLayoutDirection(lp, layoutDirection);
-        }
+        lp.setLayoutDirection(layoutDirection);
     }
 
     /**
@@ -165,60 +135,10 @@
      */
     public static void resolveLayoutDirection(@NonNull ViewGroup.MarginLayoutParams lp,
             int layoutDirection) {
-        if (SDK_INT >= 17) {
-            Api17Impl.resolveLayoutDirection(lp, layoutDirection);
-        }
+        lp.resolveLayoutDirection(layoutDirection);
     }
 
     private MarginLayoutParamsCompat() {
     }
 
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static int getMarginStart(ViewGroup.MarginLayoutParams marginLayoutParams) {
-            return marginLayoutParams.getMarginStart();
-        }
-
-        @DoNotInline
-        static int getMarginEnd(ViewGroup.MarginLayoutParams marginLayoutParams) {
-            return marginLayoutParams.getMarginEnd();
-        }
-
-        @DoNotInline
-        static void setMarginStart(ViewGroup.MarginLayoutParams marginLayoutParams, int start) {
-            marginLayoutParams.setMarginStart(start);
-        }
-
-        @DoNotInline
-        static void setMarginEnd(ViewGroup.MarginLayoutParams marginLayoutParams, int end) {
-            marginLayoutParams.setMarginEnd(end);
-        }
-
-        @DoNotInline
-        static boolean isMarginRelative(ViewGroup.MarginLayoutParams marginLayoutParams) {
-            return marginLayoutParams.isMarginRelative();
-        }
-
-        @DoNotInline
-        static int getLayoutDirection(ViewGroup.MarginLayoutParams marginLayoutParams) {
-            return marginLayoutParams.getLayoutDirection();
-        }
-
-        @DoNotInline
-        static void setLayoutDirection(ViewGroup.MarginLayoutParams marginLayoutParams,
-                int layoutDirection) {
-            marginLayoutParams.setLayoutDirection(layoutDirection);
-        }
-
-        @DoNotInline
-        static void resolveLayoutDirection(ViewGroup.MarginLayoutParams marginLayoutParams,
-                int layoutDirection) {
-            marginLayoutParams.resolveLayoutDirection(layoutDirection);
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/view/ViewGroupCompat.java b/core/core/src/main/java/androidx/core/view/ViewGroupCompat.java
index 207e55f..3034440 100644
--- a/core/core/src/main/java/androidx/core/view/ViewGroupCompat.java
+++ b/core/core/src/main/java/androidx/core/view/ViewGroupCompat.java
@@ -113,10 +113,7 @@
      * @see #setLayoutMode(ViewGroup, int)
      */
     public static int getLayoutMode(@NonNull ViewGroup group) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return Api18Impl.getLayoutMode(group);
-        }
-        return LAYOUT_MODE_CLIP_BOUNDS;
+        return group.getLayoutMode();
     }
 
     /**
@@ -130,9 +127,7 @@
      * @see #getLayoutMode(ViewGroup)
      */
     public static void setLayoutMode(@NonNull ViewGroup group, int mode) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api18Impl.setLayoutMode(group, mode);
-        }
+        group.setLayoutMode(mode);
     }
 
     /**
@@ -191,23 +186,6 @@
         return ViewCompat.SCROLL_AXIS_NONE;
     }
 
-    @RequiresApi(18)
-    static class Api18Impl {
-        private Api18Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static int getLayoutMode(ViewGroup viewGroup) {
-            return viewGroup.getLayoutMode();
-        }
-
-        @DoNotInline
-        static void setLayoutMode(ViewGroup viewGroup, int layoutMode) {
-            viewGroup.setLayoutMode(layoutMode);
-        }
-    }
-
     @RequiresApi(21)
     static class Api21Impl {
         private Api21Impl() {
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java
index fec6650..26c0417 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java
@@ -18,6 +18,7 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
 
+import android.annotation.SuppressLint;
 import android.os.Build;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
@@ -450,6 +451,7 @@
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
      *         </ul>
      */
+    @SuppressLint("WrongConstant")
     @ContentChangeType
     public static int getContentChangeTypes(@NonNull AccessibilityEvent event) {
         if (Build.VERSION.SDK_INT >= 19) {
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
index f8d31ba..f980b7b 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -2607,9 +2607,6 @@
     }
 
     private List<Integer> extrasIntList(String key) {
-        if (Build.VERSION.SDK_INT < 19) {
-            return new ArrayList<Integer>();
-        }
         ArrayList<Integer> list = Api19Impl.getExtras(mInfo)
                 .getIntegerArrayList(key);
         if (list == null) {
@@ -3792,9 +3789,7 @@
      * @param viewId The id resource name.
      */
     public void setViewIdResourceName(String viewId) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            mInfo.setViewIdResourceName(viewId);
-        }
+        mInfo.setViewIdResourceName(viewId);
     }
 
     /**
@@ -3810,11 +3805,7 @@
      * @return The id resource name.
      */
     public String getViewIdResourceName() {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return mInfo.getViewIdResourceName();
-        } else {
-            return null;
-        }
+        return mInfo.getViewIdResourceName();
     }
 
     /**
@@ -4158,9 +4149,7 @@
      * @param labeled The view for which this info serves as a label.
      */
     public void setLabelFor(View labeled) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            mInfo.setLabelFor(labeled);
-        }
+        mInfo.setLabelFor(labeled);
     }
 
     /**
@@ -4178,9 +4167,7 @@
      * @param virtualDescendantId The id of the virtual descendant.
      */
     public void setLabelFor(View root, int virtualDescendantId) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            mInfo.setLabelFor(root, virtualDescendantId);
-        }
+        mInfo.setLabelFor(root, virtualDescendantId);
     }
 
     /**
@@ -4190,11 +4177,7 @@
      * @return The labeled info.
      */
     public AccessibilityNodeInfoCompat getLabelFor() {
-        if (Build.VERSION.SDK_INT >= 17) {
-            return AccessibilityNodeInfoCompat.wrapNonNullInstance(mInfo.getLabelFor());
-        } else {
-            return null;
-        }
+        return AccessibilityNodeInfoCompat.wrapNonNullInstance(mInfo.getLabelFor());
     }
 
     /**
@@ -4204,9 +4187,7 @@
      * @param label The view that labels this node's source.
      */
     public void setLabeledBy(View label) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            mInfo.setLabeledBy(label);
-        }
+        mInfo.setLabeledBy(label);
     }
 
     /**
@@ -4229,9 +4210,7 @@
      * @param virtualDescendantId The id of the virtual descendant.
      */
     public void setLabeledBy(View root, int virtualDescendantId) {
-        if (Build.VERSION.SDK_INT >= 17) {
-            mInfo.setLabeledBy(root, virtualDescendantId);
-        }
+        mInfo.setLabeledBy(root, virtualDescendantId);
     }
 
     /**
@@ -4241,11 +4220,7 @@
      * @return The label.
      */
     public AccessibilityNodeInfoCompat getLabeledBy() {
-        if (Build.VERSION.SDK_INT >= 17) {
-            return AccessibilityNodeInfoCompat.wrapNonNullInstance(mInfo.getLabeledBy());
-        } else {
-            return null;
-        }
+        return AccessibilityNodeInfoCompat.wrapNonNullInstance(mInfo.getLabeledBy());
     }
 
     /**
@@ -4452,9 +4427,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setTextSelection(int start, int end) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            mInfo.setTextSelection(start, end);
-        }
+        mInfo.setTextSelection(start, end);
     }
 
     /**
@@ -4463,11 +4436,7 @@
      * @return The text selection start if there is selection or -1.
      */
     public int getTextSelectionStart() {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return mInfo.getTextSelectionStart();
-        } else {
-            return -1;
-        }
+        return mInfo.getTextSelectionStart();
     }
 
     /**
@@ -4476,11 +4445,7 @@
      * @return The text selection end if there is selection or -1.
      */
     public int getTextSelectionEnd() {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return mInfo.getTextSelectionEnd();
-        } else {
-            return -1;
-        }
+        return mInfo.getTextSelectionEnd();
     }
 
     /**
@@ -4661,11 +4626,7 @@
      * @return True if the node is editable, false otherwise.
      */
     public boolean isEditable() {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return mInfo.isEditable();
-        } else {
-            return false;
-        }
+        return mInfo.isEditable();
     }
 
     /**
@@ -4681,9 +4642,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setEditable(boolean editable) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            mInfo.setEditable(editable);
-        }
+        mInfo.setEditable(editable);
     }
 
     /**
@@ -4977,11 +4936,7 @@
      * @return Whether the refresh succeeded.
      */
     public boolean refresh() {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return mInfo.refresh();
-        } else {
-            return false;
-        }
+        return mInfo.refresh();
     }
 
     /**
diff --git a/core/core/src/main/java/androidx/core/widget/TextViewCompat.java b/core/core/src/main/java/androidx/core/widget/TextViewCompat.java
index b63d1df..df34fd0 100644
--- a/core/core/src/main/java/androidx/core/widget/TextViewCompat.java
+++ b/core/core/src/main/java/androidx/core/widget/TextViewCompat.java
@@ -120,14 +120,7 @@
     public static void setCompoundDrawablesRelative(@NonNull TextView textView,
             @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
             @Nullable Drawable bottom) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api17Impl.setCompoundDrawablesRelative(textView, start, top, end, bottom);
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            boolean rtl = Api17Impl.getLayoutDirection(textView) == View.LAYOUT_DIRECTION_RTL;
-            textView.setCompoundDrawables(rtl ? end : start, top, rtl ? start : end, bottom);
-        } else {
-            textView.setCompoundDrawables(start, top, end, bottom);
-        }
+        textView.setCompoundDrawablesRelative(start, top, end, bottom);
     }
 
     /**
@@ -152,16 +145,7 @@
     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
             @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
             @Nullable Drawable bottom) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api17Impl.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end,
-                    bottom);
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            boolean rtl = Api17Impl.getLayoutDirection(textView) == View.LAYOUT_DIRECTION_RTL;
-            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
-                    rtl ? start : end,  bottom);
-        } else {
-            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
-        }
+        textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
     }
 
     /**
@@ -185,16 +169,7 @@
     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
             @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
             @DrawableRes int bottom) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            Api17Impl.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end,
-                    bottom);
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            boolean rtl = Api17Impl.getLayoutDirection(textView) == View.LAYOUT_DIRECTION_RTL;
-            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
-                    rtl ? start : end, bottom);
-        } else {
-            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
-        }
+        textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
     }
 
     /**
@@ -235,22 +210,7 @@
      */
     @NonNull
     public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return Api17Impl.getCompoundDrawablesRelative(textView);
-        }
-        if (Build.VERSION.SDK_INT >= 17) {
-            final boolean rtl = Api17Impl.getLayoutDirection(textView) == View.LAYOUT_DIRECTION_RTL;
-            final Drawable[] compounds = textView.getCompoundDrawables();
-            if (rtl) {
-                // If we're on RTL, we need to invert the horizontal result like above
-                final Drawable start = compounds[2];
-                final Drawable end = compounds[0];
-                compounds[0] = start;
-                compounds[2] = end;
-            }
-            return compounds;
-        }
-        return textView.getCompoundDrawables();
+        return textView.getCompoundDrawablesRelative();
     }
 
     /**
@@ -815,9 +775,7 @@
                 builder.setBreakStrategy(Api23Impl.getBreakStrategy(textView));
                 builder.setHyphenationFrequency(Api23Impl.getHyphenationFrequency(textView));
             }
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-                builder.setTextDirection(getTextDirectionHeuristic(textView));
-            }
+            builder.setTextDirection(getTextDirectionHeuristic(textView));
             return builder.build();
         }
     }
@@ -833,9 +791,7 @@
 
         // There is no way of setting text direction heuristics to TextView.
         // Convert to the View's text direction int values.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-            Api17Impl.setTextDirection(textView, getTextDirection(params.getTextDirection()));
-        }
+        textView.setTextDirection(getTextDirection(params.getTextDirection()));
 
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
             float paintTextScaleX = params.getTextPaint().getTextScaleX();
@@ -910,7 +866,7 @@
                 // have LTR digits, but some locales, such as those written in the Adlam or N'Ko
                 // scripts, have RTL digits.
                 final DecimalFormatSymbols symbols =
-                        Api24Impl.getInstance(Api17Impl.getTextLocale(textView));
+                        Api24Impl.getInstance(textView.getTextLocale());
                 final String zero = Api28Impl.getDigitStrings(symbols)[0];
                 // In case the zero digit is multi-codepoint, just use the first codepoint to
                 // determine direction.
@@ -927,10 +883,10 @@
 
         // Always need to resolve layout direction first
         final boolean defaultIsRtl =
-                (Api17Impl.getLayoutDirection(textView) == View.LAYOUT_DIRECTION_RTL);
+                (textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
 
         // Now, we can select the heuristic
-        switch (Api17Impl.getTextDirection(textView)) {
+        switch (textView.getTextDirection()) {
             default:
             case TEXT_DIRECTION_FIRST_STRONG:
                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
@@ -953,7 +909,6 @@
     /**
      * Convert TextDirectionHeuristic to TextDirection int values
      */
-    @RequiresApi(18)
     private static int getTextDirection(@NonNull  TextDirectionHeuristic heuristic) {
         if (heuristic == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
             return TEXT_DIRECTION_FIRST_STRONG;
@@ -1045,56 +1000,6 @@
         return null;
     }
 
-    @RequiresApi(17)
-    static class Api17Impl {
-        private Api17Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static void setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top,
-                Drawable end, Drawable bottom) {
-            textView.setCompoundDrawablesRelative(start, top, end, bottom);
-        }
-
-        @DoNotInline
-        static int getLayoutDirection(View view) {
-            return view.getLayoutDirection();
-        }
-
-        @DoNotInline
-        static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView,
-                Drawable start, Drawable top, Drawable end, Drawable bottom) {
-            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
-        }
-
-        @DoNotInline
-        static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView, int start,
-                int top, int end, int bottom) {
-            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
-        }
-
-        @DoNotInline
-        static Drawable[] getCompoundDrawablesRelative(TextView textView) {
-            return textView.getCompoundDrawablesRelative();
-        }
-
-        @DoNotInline
-        static void setTextDirection(View view, int textDirection) {
-            view.setTextDirection(textDirection);
-        }
-
-        @DoNotInline
-        static Locale getTextLocale(TextView textView) {
-            return textView.getTextLocale();
-        }
-
-        @DoNotInline
-        static int getTextDirection(View view) {
-            return view.getTextDirection();
-        }
-    }
-
     @RequiresApi(26)
     static class Api26Impl {
         private Api26Impl() {
diff --git a/emoji/emoji/src/main/java/androidx/emoji/text/EmojiCompat.java b/emoji/emoji/src/main/java/androidx/emoji/text/EmojiCompat.java
index da9477f..b42c6a3 100644
--- a/emoji/emoji/src/main/java/androidx/emoji/text/EmojiCompat.java
+++ b/emoji/emoji/src/main/java/androidx/emoji/text/EmojiCompat.java
@@ -281,8 +281,7 @@
         if (config.mInitCallbacks != null && !config.mInitCallbacks.isEmpty()) {
             mInitCallbacks.addAll(config.mInitCallbacks);
         }
-        mHelper = Build.VERSION.SDK_INT < 19 ? new CompatInternal(this) : new CompatInternal19(
-                this);
+        mHelper = new CompatInternal(this);
         loadMetadata();
     }
 
@@ -1201,49 +1200,7 @@
         }
     }
 
-    /**
-     * Internal helper class to behave no-op for certain functions.
-     */
-    private static class CompatInternal {
-        final EmojiCompat mEmojiCompat;
-
-        CompatInternal(EmojiCompat emojiCompat) {
-            mEmojiCompat = emojiCompat;
-        }
-
-        void loadMetadata() {
-            // Moves into LOAD_STATE_SUCCESS state immediately.
-            mEmojiCompat.onMetadataLoadSuccess();
-        }
-
-        boolean hasEmojiGlyph(@NonNull final CharSequence sequence) {
-            // Since no metadata is loaded, EmojiCompat cannot detect or render any emojis.
-            return false;
-        }
-
-        boolean hasEmojiGlyph(@NonNull final CharSequence sequence, final int metadataVersion) {
-            // Since no metadata is loaded, EmojiCompat cannot detect or render any emojis.
-            return false;
-        }
-
-        CharSequence process(@NonNull final CharSequence charSequence,
-                @IntRange(from = 0) final int start, @IntRange(from = 0) final int end,
-                @IntRange(from = 0) final int maxEmojiCount, boolean replaceAll) {
-            // Returns the given charSequence as it is.
-            return charSequence;
-        }
-
-        void updateEditorInfoAttrs(@NonNull final EditorInfo outAttrs) {
-            // Does not add any EditorInfo attributes.
-        }
-
-        String getAssetSignature() {
-            return "";
-        }
-    }
-
-    @RequiresApi(19)
-    private static final class CompatInternal19 extends CompatInternal {
+    private static final class CompatInternal {
         /**
          * Responsible to process a CharSequence and add the spans. @{code Null} until the time the
          * metadata is loaded.
@@ -1254,13 +1211,13 @@
          * Keeps the information about emojis. Null until the time the data is loaded.
          */
         private volatile MetadataRepo mMetadataRepo;
+        final EmojiCompat mEmojiCompat;
 
 
-        CompatInternal19(EmojiCompat emojiCompat) {
-            super(emojiCompat);
+        CompatInternal(EmojiCompat emojiCompat) {
+            mEmojiCompat = emojiCompat;
         }
 
-        @Override
         void loadMetadata() {
             try {
                 final MetadataRepoLoaderCallback callback = new MetadataRepoLoaderCallback() {
@@ -1299,30 +1256,25 @@
             mEmojiCompat.onMetadataLoadSuccess();
         }
 
-        @Override
         boolean hasEmojiGlyph(@NonNull CharSequence sequence) {
             return mProcessor.getEmojiMetadata(sequence) != null;
         }
 
-        @Override
         boolean hasEmojiGlyph(@NonNull CharSequence sequence, int metadataVersion) {
             final EmojiMetadata emojiMetadata = mProcessor.getEmojiMetadata(sequence);
             return emojiMetadata != null && emojiMetadata.getCompatAdded() <= metadataVersion;
         }
 
-        @Override
         CharSequence process(@NonNull CharSequence charSequence, int start, int end,
                 int maxEmojiCount, boolean replaceAll) {
             return mProcessor.process(charSequence, start, end, maxEmojiCount, replaceAll);
         }
 
-        @Override
         void updateEditorInfoAttrs(@NonNull EditorInfo outAttrs) {
             outAttrs.extras.putInt(EDITOR_INFO_METAVERSION_KEY, mMetadataRepo.getMetadataVersion());
             outAttrs.extras.putBoolean(EDITOR_INFO_REPLACE_ALL_KEY, mEmojiCompat.mReplaceAll);
         }
 
-        @Override
         String getAssetSignature() {
             final String sha = mMetadataRepo.getMetadataList().sourceSha();
             return sha == null ? "" : sha;
diff --git a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java
index de5847c..c41335f 100644
--- a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiEditTextHelper.java
@@ -15,7 +15,6 @@
  */
 package androidx.emoji2.viewsintegration;
 
-import android.os.Build;
 import android.text.method.KeyListener;
 import android.text.method.NumberKeyListener;
 import android.view.inputmethod.EditorInfo;
@@ -104,11 +103,7 @@
     public EmojiEditTextHelper(@NonNull EditText editText,
             boolean expectInitializedEmojiCompat) {
         Preconditions.checkNotNull(editText, "editText cannot be null");
-        if (Build.VERSION.SDK_INT < 19) {
-            mHelper = new HelperInternal();
-        } else {
-            mHelper = new HelperInternal19(editText, expectInitializedEmojiCompat);
-        }
+        mHelper = new HelperInternal19(editText, expectInitializedEmojiCompat);
     }
 
     /**
diff --git a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextViewHelper.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextViewHelper.java
index 7748ba2..6ac961b 100644
--- a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextViewHelper.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextViewHelper.java
@@ -15,7 +15,6 @@
  */
 package androidx.emoji2.viewsintegration;
 
-import android.os.Build;
 import android.text.InputFilter;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.TransformationMethod;
@@ -95,9 +94,7 @@
      */
     public EmojiTextViewHelper(@NonNull TextView textView, boolean expectInitializedEmojiCompat) {
         Preconditions.checkNotNull(textView, "textView cannot be null");
-        if (Build.VERSION.SDK_INT < 19) {
-            mHelper = new HelperInternal();
-        } else if (!expectInitializedEmojiCompat) {
+        if (!expectInitializedEmojiCompat) {
             mHelper = new SkippingHelper19(textView);
         } else {
             mHelper = new HelperInternal19(textView);
diff --git a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java
index eda1b9d..c565086 100644
--- a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java
+++ b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java
@@ -72,9 +72,6 @@
 
     @SuppressWarnings("deprecation")
     private boolean providerOnSystem() {
-        if (Build.VERSION.SDK_INT < 19) {
-            return false;
-        }
         List<ResolveInfo> result = ApplicationProvider.getApplicationContext()
                 .getPackageManager().queryIntentContentProviders(generateIntent(), 0);
         for (ResolveInfo resolveInfo : result) {
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
index 3f6f2af..a593eb4 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
@@ -463,8 +463,7 @@
         if (config.mInitCallbacks != null && !config.mInitCallbacks.isEmpty()) {
             mInitCallbacks.addAll(config.mInitCallbacks);
         }
-        mHelper = Build.VERSION.SDK_INT < 19 ? new CompatInternal(this) : new CompatInternal19(
-                this);
+        mHelper = new CompatInternal(this);
         loadMetadata();
     }
 
@@ -1646,64 +1645,7 @@
         }
     }
 
-    /**
-     * Internal helper class to behave no-op for certain functions.
-     */
-    private static class CompatInternal {
-        final EmojiCompat mEmojiCompat;
-
-        CompatInternal(EmojiCompat emojiCompat) {
-            mEmojiCompat = emojiCompat;
-        }
-
-        void loadMetadata() {
-            // Moves into LOAD_STATE_SUCCESS state immediately.
-            mEmojiCompat.onMetadataLoadSuccess();
-        }
-
-        boolean hasEmojiGlyph(@NonNull final CharSequence sequence) {
-            // Since no metadata is loaded, EmojiCompat cannot detect or render any emojis.
-            return false;
-        }
-
-        boolean hasEmojiGlyph(@NonNull final CharSequence sequence, final int metadataVersion) {
-            // Since no metadata is loaded, EmojiCompat cannot detect or render any emojis.
-            return false;
-        }
-
-        int getEmojiStart(@NonNull final CharSequence cs, @IntRange(from = 0) final int offset) {
-            // Since no metadata is loaded, EmojiCompat cannot detect any emojis.
-            return -1;
-        }
-
-        int getEmojiEnd(@NonNull final CharSequence cs, @IntRange(from = 0) final int offset) {
-            // Since no metadata is loaded, EmojiCompat cannot detect any emojis.
-            return -1;
-        }
-
-        CharSequence process(@NonNull final CharSequence charSequence,
-                @IntRange(from = 0) final int start, @IntRange(from = 0) final int end,
-                @IntRange(from = 0) final int maxEmojiCount, boolean replaceAll) {
-            // Returns the given charSequence as it is.
-            return charSequence;
-        }
-
-        void updateEditorInfoAttrs(@NonNull final EditorInfo outAttrs) {
-            // Does not add any EditorInfo attributes.
-        }
-
-        String getAssetSignature() {
-            return "";
-        }
-
-        @CodepointSequenceMatchResult
-        public int getEmojiMatch(CharSequence sequence, int metadataVersion) {
-            return EMOJI_UNSUPPORTED;
-        }
-    }
-
-    @RequiresApi(19)
-    private static final class CompatInternal19 extends CompatInternal {
+    private static final class CompatInternal {
         /**
          * Responsible to process a CharSequence and add the spans. @{code Null} until the time the
          * metadata is loaded.
@@ -1714,13 +1656,12 @@
          * Keeps the information about emojis. Null until the time the data is loaded.
          */
         private volatile MetadataRepo mMetadataRepo;
+        private final EmojiCompat mEmojiCompat;
 
-
-        CompatInternal19(EmojiCompat emojiCompat) {
-            super(emojiCompat);
+        CompatInternal(EmojiCompat emojiCompat) {
+            mEmojiCompat = emojiCompat;
         }
 
-        @Override
         void loadMetadata() {
             try {
                 final MetadataRepoLoaderCallback callback = new MetadataRepoLoaderCallback() {
@@ -1761,45 +1702,37 @@
             mEmojiCompat.onMetadataLoadSuccess();
         }
 
-        @Override
         boolean hasEmojiGlyph(@NonNull CharSequence sequence) {
             return mProcessor.getEmojiMatch(sequence) == EMOJI_SUPPORTED;
         }
 
-        @Override
         boolean hasEmojiGlyph(@NonNull CharSequence sequence, int metadataVersion) {
             int emojiMatch = mProcessor.getEmojiMatch(sequence, metadataVersion);
             return emojiMatch == EMOJI_SUPPORTED;
         }
 
-        @Override
         public int getEmojiMatch(CharSequence sequence, int metadataVersion) {
             return mProcessor.getEmojiMatch(sequence, metadataVersion);
         }
 
-        @Override
         int getEmojiStart(@NonNull final CharSequence sequence, final int offset) {
             return mProcessor.getEmojiStart(sequence, offset);
         }
 
-        @Override
         int getEmojiEnd(@NonNull final CharSequence sequence, final int offset) {
             return mProcessor.getEmojiEnd(sequence, offset);
         }
 
-        @Override
         CharSequence process(@NonNull CharSequence charSequence, int start, int end,
                 int maxEmojiCount, boolean replaceAll) {
             return mProcessor.process(charSequence, start, end, maxEmojiCount, replaceAll);
         }
 
-        @Override
         void updateEditorInfoAttrs(@NonNull EditorInfo outAttrs) {
             outAttrs.extras.putInt(EDITOR_INFO_METAVERSION_KEY, mMetadataRepo.getMetadataVersion());
             outAttrs.extras.putBoolean(EDITOR_INFO_REPLACE_ALL_KEY, mEmojiCompat.mReplaceAll);
         }
 
-        @Override
         String getAssetSignature() {
             final String sha = mMetadataRepo.getMetadataList().sourceSha();
             return sha == null ? "" : sha;
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index ca2a19f..4381628 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -50,7 +50,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -1443,10 +1442,6 @@
      * <p>This does not check the image itself for similarity/equality.
      */
     private void assertBitmapsEquivalent(File expectedImageFile, File actualImageFile) {
-        if (Build.VERSION.SDK_INT < 16 && expectedImageFile.getName().endsWith("webp")) {
-            // BitmapFactory can't parse WebP files on API levels before 16: b/254571189
-            return;
-        }
         if (Build.VERSION.SDK_INT < 26
                 && expectedImageFile.getName().equals(WEBP_WITHOUT_EXIF_WITH_ANIM_DATA)) {
             // BitmapFactory can't parse animated WebP files on API levels before 26: b/259964971
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.kt b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.kt
index 43f6429..e9dd1a8 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.kt
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.kt
@@ -18,7 +18,6 @@
 import android.animation.LayoutTransition
 import android.content.Context
 import android.graphics.Canvas
-import android.os.Build
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewGroup
@@ -178,13 +177,6 @@
      * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
      */
     public override fun setLayoutTransition(transition: LayoutTransition?) {
-        if (Build.VERSION.SDK_INT < 18) {
-            // Transitions on APIs below 18 are using an empty LayoutTransition as a replacement
-            // for suppressLayout(true) and null LayoutTransition to then unsuppress it. If the
-            // API is below 18, we should allow FrameLayout to handle this call.
-            super.setLayoutTransition(transition)
-            return
-        }
         throw UnsupportedOperationException(
             "FragmentContainerView does not support Layout Transitions or " +
                 "animateLayoutChanges=\"true\"."
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
index fdc700d..09be273 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
@@ -45,7 +45,8 @@
 import androidx.glance.state.GlanceState
 import androidx.glance.state.GlanceStateDefinition
 import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.CompletableJob
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /**
@@ -100,6 +101,7 @@
     private var glanceState by mutableStateOf(initialGlanceState, neverEqualPolicy())
     private var options by mutableStateOf(initialOptions, neverEqualPolicy())
     private var lambdas = mapOf<String, List<LambdaAction>>()
+    private val parentJob = Job()
 
     internal val lastRemoteViews = MutableStateFlow<RemoteViews?>(null)
 
@@ -226,7 +228,9 @@
                     lambdas[event.key]?.forEach { it.block() }
                 } ?: Log.w(TAG, "Triggering Action(${event.key}) for session($key) failed")
             }
-            is WaitForReady -> event.resume.send(Unit)
+            is WaitForReady -> {
+                event.job.apply { if (isActive) complete() }
+            }
             else -> {
                 throw IllegalArgumentException(
                     "Sent unrecognized event type ${event.javaClass} to AppWidgetSession"
@@ -235,6 +239,16 @@
         }
     }
 
+    override fun onClosed() {
+        // Normally when we are closed, any pending events are processed before the channel is
+        // shutdown. However, it is possible that the Worker for this session will die before
+        // processing the remaining events. So when this session is closed, we will immediately
+        // resume all waiters without waiting for their events to be processed. If the Worker lives
+        // long enough to process their events, it will have no effect because their Jobs are no
+        // longer active.
+        parentJob.cancel()
+    }
+
     suspend fun updateGlance() {
         sendEvent(UpdateGlanceState)
     }
@@ -247,11 +261,17 @@
         sendEvent(RunLambda(key))
     }
 
-    suspend fun waitForReady() {
-        WaitForReady().let {
-            sendEvent(it)
-            it.resume.receive()
-        }
+    /**
+     * Returns a Job that can be used to wait until the session is ready (i.e. has finished
+     * processEmittableTree for the first time and is now receiving events). You can wait on the
+     * session to be ready by calling [Job.join] on the returned [Job]. When the session is ready,
+     * join will resume successfully (Job is completed). If the session is closed before it is
+     * ready, we call [Job.cancel] and the call to join resumes with [CancellationException].
+     */
+    suspend fun waitForReady(): Job {
+        val event = WaitForReady(Job(parentJob))
+        sendEvent(event)
+        return event.job
     }
 
     private fun notifyWidgetOfError(context: Context, throwable: Throwable) {
@@ -276,7 +296,5 @@
     @VisibleForTesting
     internal class RunLambda(val key: String)
     @VisibleForTesting
-    internal class WaitForReady(
-        val resume: Channel<Unit> = Channel(Channel.CONFLATED)
-    )
+    internal class WaitForReady(val job: CompletableJob)
 }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
index dac5826..1ce033a 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -31,6 +31,7 @@
 import androidx.glance.appwidget.state.getAppWidgetState
 import androidx.glance.session.GlanceSessionManager
 import androidx.glance.session.SessionManager
+import androidx.glance.session.SessionManagerScope
 import androidx.glance.state.GlanceState
 import androidx.glance.state.GlanceStateDefinition
 import androidx.glance.state.PreferencesGlanceStateDefinition
@@ -120,7 +121,9 @@
      */
     internal suspend fun deleted(context: Context, appWidgetId: Int) {
         val glanceId = AppWidgetId(appWidgetId)
-        sessionManager.closeSession(glanceId.toSessionKey())
+        sessionManager.runWithLock {
+            closeSession(glanceId.toSessionKey())
+        }
         try {
             onDelete(context, glanceId)
         } catch (cancelled: CancellationException) {
@@ -144,10 +147,12 @@
     ) {
         Tracing.beginGlanceAppWidgetUpdate()
         val glanceId = AppWidgetId(appWidgetId)
-        if (!sessionManager.isSessionRunning(context, glanceId.toSessionKey())) {
-            sessionManager.startSession(context, AppWidgetSession(this, glanceId, options))
-        } else {
-            val session = sessionManager.getSession(glanceId.toSessionKey()) as AppWidgetSession
+        sessionManager.runWithLock {
+            if (!isSessionRunning(context, glanceId.toSessionKey())) {
+                startSession(context, AppWidgetSession(this@GlanceAppWidget, glanceId, options))
+                return@runWithLock
+            }
+            val session = getSession(glanceId.toSessionKey()) as AppWidgetSession
             session.updateGlance()
         }
     }
@@ -163,14 +168,9 @@
         options: Bundle? = null,
     ) {
         val glanceId = AppWidgetId(appWidgetId)
-        val session = if (!sessionManager.isSessionRunning(context, glanceId.toSessionKey())) {
-            AppWidgetSession(this, glanceId, options).also { session ->
-                sessionManager.startSession(context, session)
-            }
-        } else {
-            sessionManager.getSession(glanceId.toSessionKey()) as AppWidgetSession
+        sessionManager.getOrCreateAppWidgetSession(context, glanceId, options) { session ->
+            session.runLambda(actionKey)
         }
-        session.runLambda(actionKey)
     }
 
     /**
@@ -189,10 +189,7 @@
             return
         }
         val glanceId = AppWidgetId(appWidgetId)
-        if (!sessionManager.isSessionRunning(context, glanceId.toSessionKey())) {
-            sessionManager.startSession(context, AppWidgetSession(this, glanceId, options))
-        } else {
-            val session = sessionManager.getSession(glanceId.toSessionKey()) as AppWidgetSession
+        sessionManager.getOrCreateAppWidgetSession(context, glanceId, options) { session ->
             session.updateAppWidgetOptions(options)
         }
     }
@@ -230,6 +227,19 @@
             AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, rv)
         }
     }
+
+    private suspend fun SessionManager.getOrCreateAppWidgetSession(
+        context: Context,
+        glanceId: AppWidgetId,
+        options: Bundle? = null,
+        block: suspend SessionManagerScope.(AppWidgetSession) -> Unit
+    ) = runWithLock {
+        if (!isSessionRunning(context, glanceId.toSessionKey())) {
+            startSession(context, AppWidgetSession(this@GlanceAppWidget, glanceId, options))
+        }
+        val session = getSession(glanceId.toSessionKey()) as AppWidgetSession
+        block(session)
+    }
 }
 
 @RestrictTo(Scope.LIBRARY_GROUP)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt
index 1e91d1c..52058e4 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt
@@ -21,12 +21,14 @@
 import android.content.Intent
 import android.net.Uri
 import android.os.Build
+import android.util.Log
 import android.widget.RemoteViews
 import android.widget.RemoteViewsService
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.glance.session.GlanceSessionManager
+import kotlinx.coroutines.channels.ClosedSendChannelException
 import kotlinx.coroutines.runBlocking
 
 /**
@@ -51,6 +53,7 @@
     companion object {
         const val EXTRA_VIEW_ID = "androidx.glance.widget.extra.view_id"
         const val EXTRA_SIZE_INFO = "androidx.glance.widget.extra.size_info"
+        const val TAG = "GlanceRemoteViewService"
 
         // An in-memory store containing items to be returned via the adapter when requested.
         private val InMemoryStore = RemoteCollectionItemsInMemoryStore()
@@ -107,26 +110,49 @@
         private fun loadData() {
             runBlocking {
                 val glanceId = AppWidgetId(appWidgetId)
-                // If session is already running, data must have already been loaded into the store
-                // during composition.
-                if (!GlanceSessionManager.isSessionRunning(context, glanceId.toSessionKey())) {
-                    startSessionAndWaitUntilReady(glanceId)
+                try {
+                    startSessionIfNeededAndWaitUntilReady(glanceId)
+                } catch (e: ClosedSendChannelException) {
+                    // This catch should no longer be necessary.
+                    // Because we use SessionManager.runWithLock, we are guaranteed that the session
+                    // we create won't be closed by concurrent calls to SessionManager. Currently,
+                    // the only way a session would be closed is if there is an error in the
+                    // composition that happens between the call to `startSession` and
+                    // `waitForReady()` In that case, the composition error will be logged by
+                    // GlanceAppWidget.onCompositionError, but could still cause
+                    // ClosedSendChannelException. This is pretty unlikely, however keeping this
+                    // here to avoid crashes in that scenario.
+                    Log.e(TAG, "Error when trying to start session for list items", e)
                 }
             }
         }
 
-        private suspend fun startSessionAndWaitUntilReady(glanceId: AppWidgetId) {
+        private suspend fun startSessionIfNeededAndWaitUntilReady(glanceId: AppWidgetId) {
+            val job = getGlanceAppWidget()?.let { widget ->
+                GlanceSessionManager.runWithLock {
+                    if (isSessionRunning(context, glanceId.toSessionKey())) {
+                        // If session is already running, data must have already been loaded into
+                        // the store during composition.
+                        return@runWithLock null
+                    }
+                    startSession(context, AppWidgetSession(widget, glanceId))
+                    val session = getSession(glanceId.toSessionKey()) as AppWidgetSession
+                    session.waitForReady()
+                }
+            } ?: UnmanagedSessionReceiver.getSession(appWidgetId)?.waitForReady()
+            // The following join() may throw CancellationException if the session is closed before
+            // it is ready. This will have the effect of cancelling the runBlocking scope.
+            job?.join()
+        }
+
+        private fun getGlanceAppWidget(): GlanceAppWidget? {
             val appWidgetManager = AppWidgetManager.getInstance(context)
             val providerInfo = appWidgetManager.getAppWidgetInfo(appWidgetId)
-            providerInfo?.provider?.className?.let { className ->
+            return providerInfo?.provider?.className?.let { className ->
                 val receiverClass = Class.forName(className)
-                val glanceAppWidget =
-                    (receiverClass.getDeclaredConstructor()
-                        .newInstance() as GlanceAppWidgetReceiver).glanceAppWidget
-                AppWidgetSession(glanceAppWidget, glanceId)
-                    .also { GlanceSessionManager.startSession(context, it) }
-                    .waitForReady()
-            } ?: UnmanagedSessionReceiver.getSession(appWidgetId)?.waitForReady()
+                (receiverClass.getDeclaredConstructor()
+                    .newInstance() as GlanceAppWidgetReceiver).glanceAppWidget
+            }
         }
 
         override fun onDestroy() {
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
index 6eaa1a5..4c62efd 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
@@ -219,6 +219,25 @@
         assertThat(caught).isEqualTo(null)
     }
 
+    @Test
+    fun waitForReadyResumesWhenEventIsReceived() = runTest {
+        launch {
+            session.waitForReady().join()
+            session.close()
+        }
+        session.receiveEvents(context) {}
+    }
+
+    @Test
+    fun waitForReadyResumesWhenSessionIsClosed() = runTest {
+        launch {
+            session.waitForReady().join()
+        }
+        // Advance until waitForReady suspends.
+        this.testScheduler.advanceUntilIdle()
+        session.close()
+    }
+
     private class TestGlanceState : ConfigManager {
 
         val getValueCalls = mutableListOf<String>()
diff --git a/glance/glance/src/androidTest/kotlin/androidx/glance/session/GlanceSessionManagerTest.kt b/glance/glance/src/androidTest/kotlin/androidx/glance/session/GlanceSessionManagerTest.kt
index 3f4fa05..474e759 100644
--- a/glance/glance/src/androidTest/kotlin/androidx/glance/session/GlanceSessionManagerTest.kt
+++ b/glance/glance/src/androidTest/kotlin/androidx/glance/session/GlanceSessionManagerTest.kt
@@ -127,7 +127,9 @@
         val text = assertIs<EmittableText>(testSession.uiTree.receive().children.single())
         assertThat(text.text).isEqualTo("Hello World")
 
-        assertNotNull(GlanceSessionManager.getSession(testSession.key)).close()
+        GlanceSessionManager.runWithLock {
+            assertNotNull(getSession(testSession.key)).close()
+        }
         waitForWorkerSuccess()
     }
 
@@ -150,7 +152,9 @@
         // The session is not subject to a timeout before the composition has been processed
         // successfully for the first time.
         delay(initialTimeout * 5)
-        assertThat(GlanceSessionManager.isSessionRunning(context, testSession.key)).isTrue()
+        GlanceSessionManager.runWithLock {
+            assertThat(isSessionRunning(context, testSession.key)).isTrue()
+        }
 
         testSession.uiTree.receive()
         val timeout = testTimeSource.measureTime {
@@ -189,9 +193,11 @@
     }
 
     private suspend fun startSession() {
-        GlanceSessionManager.startSession(context, testSession)
-        waitForWorkerStart()
-        assertThat(GlanceSessionManager.isSessionRunning(context, testSession.key)).isTrue()
+        GlanceSessionManager.runWithLock {
+            startSession(context, testSession)
+            waitForWorkerStart()
+            assertThat(isSessionRunning(context, testSession.key)).isTrue()
+        }
     }
 
     private suspend fun waitForWorkerState(vararg state: State) = workerState.first {
diff --git a/glance/glance/src/main/java/androidx/glance/session/Session.kt b/glance/glance/src/main/java/androidx/glance/session/Session.kt
index c7aec1e..614aff6 100644
--- a/glance/glance/src/main/java/androidx/glance/session/Session.kt
+++ b/glance/glance/src/main/java/androidx/glance/session/Session.kt
@@ -22,6 +22,7 @@
 import androidx.compose.runtime.Composable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceComposable
+import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.ClosedReceiveChannelException
 
@@ -32,6 +33,12 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 abstract class Session(val key: String) {
+    // _isOpen/isOpen is used to check whether this Session's event channel is still open and
+    // accepting events (close has not been called). It may be checked or set from different
+    // threads, so we use an AtomicBoolean so that the value is updated atomically.
+    private val _isOpen = AtomicBoolean(true)
+    internal val isOpen: Boolean
+        get() = _isOpen.get()
     private val eventChannel = Channel<Any>(Channel.UNLIMITED)
 
     /**
@@ -85,11 +92,22 @@
         }
     }
 
+    /**
+     * Close the session. Any events sent before [close] will be processed unless the Worker for
+     * this session is cancelled.
+     */
     fun close() {
         eventChannel.close()
+        _isOpen.set(false)
+        onClosed()
     }
 
     /**
+     * Called after the session is closed. Can be used by implementers to clean up any resources.
+     */
+    open fun onClosed() {}
+
+    /**
      * Called when there is an error in the composition. The session will be closed immediately
      * after this.
      */
diff --git a/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt b/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt
index 5b83da2..4099d5e 100644
--- a/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt
+++ b/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt
@@ -16,6 +16,7 @@
 
 package androidx.glance.session
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.util.Log
 import androidx.annotation.RestrictTo
@@ -28,6 +29,8 @@
 import androidx.work.await
 import androidx.work.workDataOf
 import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 
 @JvmDefaultWithCompatibility
 /**
@@ -38,6 +41,27 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 interface SessionManager {
     /**
+     * [runWithLock] provides a scope in which to run operations on SessionManager.
+     *
+     * The implementation must ensure that concurrent calls to [runWithLock] are mutually exclusive.
+     * Because this function holds a lock while running [block], clients should not run any
+     * long-running operations in [block]. The client should not maintain a reference to the
+     * [SessionManagerScope] after [block] returns.
+     */
+    suspend fun <T> runWithLock(block: suspend SessionManagerScope.() -> T): T
+
+    /**
+     * The name of the session key parameter, which is used to set the session key in the Worker's
+     * input data.
+     * TODO: consider using a typealias instead
+     */
+    val keyParam: String
+        get() = "KEY"
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+interface SessionManagerScope {
+    /**
      * Start a session for the Glance in [session].
      */
     suspend fun startSession(context: Context, session: Session)
@@ -56,9 +80,6 @@
      * Gets the session corresponding to [key] if it exists
      */
     fun getSession(key: String): Session?
-
-    val keyParam: String
-        get() = "KEY"
 }
 
 @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -67,49 +88,65 @@
 internal class SessionManagerImpl(
     private val workerClass: Class<out ListenableWorker>
 ) : SessionManager {
-    private val sessions = mutableMapOf<String, Session>()
-    companion object {
-        private const val TAG = "GlanceSessionManager"
-        private const val DEBUG = false
+    private companion object {
+        const val TAG = "GlanceSessionManager"
+        const val DEBUG = false
     }
 
-    override suspend fun startSession(context: Context, session: Session) {
-        if (DEBUG) Log.d(TAG, "startSession(${session.key})")
-        synchronized(sessions) {
-            sessions.put(session.key, session)
-        }?.close()
-        val workRequest = OneTimeWorkRequest.Builder(workerClass)
-            .setInputData(
-                workDataOf(
-                    keyParam to session.key
+    // This mutex guards access to the SessionManagerScope, to prevent multiple clients from
+    // performing SessionManagerScope operations at the same time.
+    private val mutex = Mutex()
+
+    // All external access to this object is protected with a mutex, so there is no need for any
+    // internal synchronization.
+    private val scope = object : SessionManagerScope {
+        private val sessions = mutableMapOf<String, Session>()
+
+        override suspend fun startSession(context: Context, session: Session) {
+            if (DEBUG) Log.d(TAG, "startSession(${session.key})")
+            sessions.put(session.key, session)?.let { previousSession ->
+                previousSession.close()
+            }
+            val workRequest = OneTimeWorkRequest.Builder(workerClass)
+                .setInputData(
+                    workDataOf(
+                        keyParam to session.key
+                    )
                 )
-            )
-            .build()
-        WorkManager.getInstance(context)
-            .enqueueUniqueWork(session.key, ExistingWorkPolicy.REPLACE, workRequest)
-            .result.await()
-        enqueueDelayedWorker(context)
-    }
-
-    override fun getSession(key: String): Session? = synchronized(sessions) {
-        sessions[key]
-    }
-
-    override suspend fun isSessionRunning(context: Context, key: String) =
-        (WorkManager.getInstance(context).getWorkInfosForUniqueWork(key).await()
-            .any { it.state == WorkInfo.State.RUNNING } && synchronized(sessions) {
-            sessions.containsKey(key)
-        }).also {
-            if (DEBUG) Log.d(TAG, "isSessionRunning($key) == $it")
+                .build()
+            WorkManager.getInstance(context)
+                .enqueueUniqueWork(session.key, ExistingWorkPolicy.REPLACE, workRequest)
+                .result.await()
+            enqueueDelayedWorker(context)
         }
 
-    override suspend fun closeSession(key: String) {
-        if (DEBUG) Log.d(TAG, "closeSession($key)")
-        synchronized(sessions) {
-            sessions.remove(key)
-        }?.close()
+        override fun getSession(key: String): Session? = sessions[key]
+
+        @SuppressLint("ListIterator")
+        override suspend fun isSessionRunning(
+            context: Context,
+            key: String
+        ): Boolean {
+            val workerIsRunningOrEnqueued = WorkManager.getInstance(context)
+                .getWorkInfosForUniqueWork(key)
+                .await()
+                .any { it.state in listOf(WorkInfo.State.RUNNING, WorkInfo.State.ENQUEUED) }
+            val hasOpenSession = sessions[key]?.isOpen ?: false
+            val isRunning = hasOpenSession && workerIsRunningOrEnqueued
+            if (DEBUG) Log.d(TAG, "isSessionRunning($key) == $isRunning")
+            return isRunning
+        }
+
+        override suspend fun closeSession(key: String) {
+            if (DEBUG) Log.d(TAG, "closeSession($key)")
+            sessions.remove(key)?.close()
+        }
     }
 
+    override suspend fun <T> runWithLock(
+        block: suspend SessionManagerScope.() -> T
+    ): T = mutex.withLock { scope.block() }
+
     /**
      * Workaround worker to fix b/119920965
      */
diff --git a/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt b/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt
index 2cff47b..544dd2d 100644
--- a/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt
+++ b/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt
@@ -101,12 +101,14 @@
                     if (DEBUG) Log.d(TAG, "Received idle event, session timeout $timeLeft")
                 }
             ) {
-                val session = sessionManager.getSession(key) ?: if (params.runAttemptCount == 0) {
+                val session = sessionManager.runWithLock {
+                    getSession(key)
+                } ?: if (params.runAttemptCount == 0) {
                     error("No session available for key $key")
                 } else {
-                    // If this is a retry because the process was restarted (e.g. on app upgrade or
-                    // reinstall), the Session object won't be available because it's not persistable
-                    // at the moment.
+                    // If this is a retry because the process was restarted (e.g. on app upgrade
+                    // or reinstall), the Session object won't be available because it's not
+                    // persistable.
                     Log.w(
                         TAG,
                         "SessionWorker attempted restart but Session is not available for $key"
@@ -114,14 +116,18 @@
                     return@observeIdleEvents Result.success()
                 }
 
-                runSession(
-                    applicationContext,
-                    session,
-                    timeouts,
-                    effectJobFactory = {
-                        Job().also { effectJob = it }
-                    }
-                )
+                try {
+                    runSession(
+                        applicationContext,
+                        session,
+                        timeouts,
+                        effectJobFactory = {
+                            Job().also { effectJob = it }
+                        }
+                    )
+                } finally {
+                    session.close()
+                }
                 Result.success()
             }
         } ?: Result.success(Data.Builder().putBoolean(TimeoutExitReason, true).build())
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
index 9a05800..b47fa4f 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
@@ -29,9 +29,12 @@
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.testing.WorkManagerTestInitHelper
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -84,19 +87,55 @@
 
     @Test
     fun startSession() = runTest {
-        assertThat(sessionManager.isSessionRunning(context, key)).isFalse()
-        sessionManager.startSession(context, session)
-        assertThat(sessionManager.isSessionRunning(context, key)).isTrue()
-        assertThat(sessionManager.getSession(key)).isSameInstanceAs(session)
+        sessionManager.runWithLock {
+            assertThat(isSessionRunning(context, key)).isFalse()
+            startSession(context, session)
+            assertThat(isSessionRunning(context, key)).isTrue()
+            assertThat(getSession(key)).isSameInstanceAs(session)
+        }
     }
 
     @Test
     fun closeSession() = runTest {
-        sessionManager.startSession(context, session)
-        assertThat(sessionManager.isSessionRunning(context, key)).isTrue()
-        sessionManager.closeSession(key)
-        assertThat(sessionManager.isSessionRunning(context, key)).isFalse()
-        assertThat(sessionManager.getSession(key)).isNull()
+        sessionManager.runWithLock {
+            startSession(context, session)
+            assertThat(isSessionRunning(context, key)).isTrue()
+            closeSession(key)
+            assertThat(isSessionRunning(context, key)).isFalse()
+            assertThat(getSession(key)).isNull()
+        }
+    }
+
+    @Test
+    fun closedSessionIsNotRunning() = runTest {
+        sessionManager.runWithLock {
+            assertThat(isSessionRunning(context, key)).isFalse()
+            startSession(context, session)
+            assertThat(isSessionRunning(context, key)).isTrue()
+            session.close()
+            assertThat(isSessionRunning(context, key)).isFalse()
+        }
+    }
+
+    @Test
+    fun runWithLockIsMutuallyExclusive() = runTest {
+        val firstRan = AtomicBoolean(false)
+        launch {
+            sessionManager.runWithLock {
+                yield()
+                firstRan.set(true)
+            }
+        }
+        // Because test dispatchers are single threaded and do not run background `launch`es until
+        // we suspend, we yield here to allow the first transaction to run. This resumes after the
+        // yield above, while the first transaction still has the lock but is suspended.
+        yield()
+        // This call to runWithLock should suspend until the first transaction finishes, then run
+        // the block. If it is not mutually exclusive, it will run right away and firstRan will not
+        // be true.
+        sessionManager.runWithLock {
+            assertThat(firstRan.get()).isTrue()
+        }
     }
 }
 
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
index c8275e6..693218f 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
@@ -75,8 +75,8 @@
             val result = worker.doWork()
             assertThat(result).isEqualTo(Result.success())
         }
-        sessionManager.startSession(context)
-        sessionManager.closeSession()
+        sessionManager.scope.startSession(context)
+        sessionManager.scope.closeSession()
     }
 
     @Test
@@ -86,7 +86,7 @@
             assertThat(result).isEqualTo(Result.success())
         }
 
-        val root = sessionManager.startSession(context) {
+        val root = sessionManager.scope.startSession(context) {
             Box {
                 Text("Hello World")
             }
@@ -94,7 +94,7 @@
         val box = assertIs<EmittableBox>(root.children.single())
         val text = assertIs<EmittableText>(box.children.single())
         assertThat(text.text).isEqualTo("Hello World")
-        sessionManager.closeSession()
+        sessionManager.scope.closeSession()
     }
 
     @Test
@@ -103,10 +103,10 @@
             val result = worker.doWork()
             assertThat(result).isEqualTo(Result.success())
         }
-        sessionManager.startSession(context).first()
-        val session = assertIs<TestSession>(sessionManager.getSession(SESSION_KEY))
+        sessionManager.scope.startSession(context).first()
+        val session = assertIs<TestSession>(sessionManager.scope.getSession(SESSION_KEY))
         assertThat(session.provideGlanceCalled).isEqualTo(1)
-        sessionManager.closeSession()
+        sessionManager.scope.closeSession()
     }
 
     @Test
@@ -117,7 +117,7 @@
         }
 
         val state = mutableStateOf("Hello World")
-        val uiFlow = sessionManager.startSession(context) {
+        val uiFlow = sessionManager.scope.startSession(context) {
                 Text(state.value)
         }
         uiFlow.first().getOrThrow().let { root ->
@@ -130,7 +130,7 @@
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello Earth")
         }
-        sessionManager.closeSession()
+        sessionManager.scope.closeSession()
     }
 
     @Test
@@ -141,14 +141,14 @@
         }
 
         val state = mutableStateOf("Hello World")
-        val uiFlow = sessionManager.startSession(context) {
+        val uiFlow = sessionManager.scope.startSession(context) {
             Text(state.value)
         }
         uiFlow.first().getOrThrow().let { root ->
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello World")
         }
-        val session = assertIs<TestSession>(sessionManager.getSession(SESSION_KEY))
+        val session = assertIs<TestSession>(sessionManager.scope.getSession(SESSION_KEY))
         session.sendEvent {
             state.value = "Hello Earth"
         }
@@ -156,7 +156,7 @@
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello Earth")
         }
-        sessionManager.closeSession()
+        sessionManager.scope.closeSession()
     }
 
     @Test
@@ -167,7 +167,7 @@
         }
 
         val state = mutableStateOf("Hello World")
-        val uiFlow = sessionManager.startDelayedProcessingSession(context) {
+        val uiFlow = sessionManager.scope.startDelayedProcessingSession(context) {
             Text(state.value)
         }
         uiFlow.first().getOrThrow().let { root ->
@@ -183,9 +183,9 @@
             assertThat(text.text).isEqualTo("Hello Earth")
         }
 
-        val session = assertIs<TestSession>(sessionManager.getSession(SESSION_KEY))
+        val session = assertIs<TestSession>(sessionManager.scope.getSession(SESSION_KEY))
         assertThat(session.processEmittableTreeCancelCount).isEqualTo(1)
-        sessionManager.closeSession()
+        sessionManager.scope.closeSession()
     }
 
     @Test
@@ -197,7 +197,7 @@
 
         val cause = Throwable()
         val exception = Exception("message", cause)
-        val result = sessionManager.startSession(context) {
+        val result = sessionManager.scope.startSession(context) {
             throw exception
         }.first().exceptionOrNull()
         assertThat(result).hasCauseThat().isEqualTo(cause)
@@ -214,7 +214,7 @@
         val runError = mutableStateOf(false)
         val cause = Throwable()
         val exception = Exception("message", cause)
-        val resultFlow = sessionManager.startSession(context) {
+        val resultFlow = sessionManager.scope.startSession(context) {
             if (runError.value) {
                 throw exception
             } else {
@@ -245,7 +245,7 @@
 
         val cause = Throwable()
         val exception = Exception("message", cause)
-        val result = sessionManager.startSession(context) {
+        val result = sessionManager.scope.startSession(context) {
             SideEffect { throw exception }
         }.first().exceptionOrNull()
         assertThat(result).hasCauseThat().isEqualTo(cause)
@@ -261,7 +261,7 @@
 
         val cause = Throwable()
         val exception = Exception("message", cause)
-        val result = sessionManager.startSession(context) {
+        val result = sessionManager.scope.startSession(context) {
             LaunchedEffect(true) { throw exception }
         }.first().exceptionOrNull()
         assertThat(result).hasCauseThat().isEqualTo(cause)
@@ -282,7 +282,7 @@
             }
         }
 
-        sessionManager.startSession(context).first()
+        sessionManager.scope.startSession(context).first()
         workerJob.cancel()
     }
 
@@ -294,54 +294,60 @@
             assertThat(worker.effectJob?.isCancelled).isTrue()
         }
 
-        sessionManager.startSession(context).first()
-        sessionManager.closeSession()
+        sessionManager.scope.startSession(context).first()
+        sessionManager.scope.closeSession()
     }
 }
 
 private const val SESSION_KEY = "123"
 
 class TestSessionManager : SessionManager {
-    private val sessions = mutableMapOf<String, Session>()
+    val scope = TestSessionManagerScope()
+    // No locking needed, tests are run on single threaded environment and is only user of this
+    // SessionManager.
+    override suspend fun <T> runWithLock(block: suspend SessionManagerScope.() -> T): T =
+        scope.block()
 
-    suspend fun startSession(
-        context: Context,
-        content: @GlanceComposable @Composable () -> Unit = {}
-    ) = MutableSharedFlow<kotlin.Result<EmittableWithChildren>>().also { flow ->
-        startSession(context, TestSession(resultFlow = flow, content = content))
-    }
+    class TestSessionManagerScope : SessionManagerScope {
+        private val sessions = mutableMapOf<String, Session>()
+        suspend fun startSession(
+            context: Context,
+            content: @GlanceComposable @Composable () -> Unit = {}
+        ) = MutableSharedFlow<kotlin.Result<EmittableWithChildren>>().also { flow ->
+            startSession(context, TestSession(resultFlow = flow, content = content))
+        }
 
-    suspend fun startDelayedProcessingSession(
-        context: Context,
-        content: @GlanceComposable @Composable () -> Unit = {}
-    ) = MutableSharedFlow<kotlin.Result<EmittableWithChildren>>().also { flow ->
-        startSession(
-            context,
-            TestSession(
-                resultFlow = flow,
-                content = content,
-                processEmittableTreeHasInfiniteDelay = true,
+        suspend fun startDelayedProcessingSession(
+            context: Context,
+            content: @GlanceComposable @Composable () -> Unit = {}
+        ) = MutableSharedFlow<kotlin.Result<EmittableWithChildren>>().also { flow ->
+            startSession(
+                context,
+                TestSession(
+                    resultFlow = flow,
+                    content = content,
+                    processEmittableTreeHasInfiniteDelay = true,
+                )
             )
-        )
-    }
+        }
 
-    suspend fun closeSession() {
-        closeSession(SESSION_KEY)
-    }
+        suspend fun closeSession() {
+            closeSession(SESSION_KEY)
+        }
 
-    override suspend fun startSession(context: Context, session: Session) {
-        sessions[session.key] = session
-    }
+        override suspend fun startSession(context: Context, session: Session) {
+            sessions[session.key] = session
+        }
 
-    override suspend fun closeSession(key: String) {
-        sessions[key]?.close()
-    }
+        override suspend fun closeSession(key: String) {
+            sessions[key]?.close()
+        }
 
-    override suspend fun isSessionRunning(context: Context, key: String): Boolean {
-        TODO("Not yet implemented")
+        override suspend fun isSessionRunning(context: Context, key: String): Boolean {
+            TODO("Not yet implemented")
+        }
+        override fun getSession(key: String): Session? = sessions[key]
     }
-
-    override fun getSession(key: String): Session? = sessions[key]
 }
 
 class TestSession(
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt
index c606168..3a91718 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/CanvasBufferedRendererTests.kt
@@ -138,12 +138,16 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @Test
     fun testPreservationEnabledPreservesContents() =
-        verifyPreservedBuffer(CanvasBufferedRenderer.DEFAULT_IMPL)
+        repeat(20) {
+            verifyPreservedBuffer(CanvasBufferedRenderer.DEFAULT_IMPL)
+        }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @Test
     fun testPreservationEnabledPreservesContentsWithRedrawStrategy() =
-        verifyPreservedBuffer(CanvasBufferedRenderer.USE_V29_IMPL_WITH_REDRAW)
+        repeat(20) {
+            verifyPreservedBuffer(CanvasBufferedRenderer.USE_V29_IMPL_WITH_REDRAW)
+        }
 
     @RequiresApi(Build.VERSION_CODES.Q)
     private fun verifyPreservedBuffer(
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV29.kt b/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV29.kt
index acb359c..a9ae731 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV29.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRendererV29.kt
@@ -155,7 +155,7 @@
                                     CanvasBufferedRenderer.RenderResult(
                                         buffer,
                                         fence,
-                                        if (result != 0) ERROR_UNKNOWN else SUCCESS
+                                        if (isSuccess(result)) SUCCESS else ERROR_UNKNOWN
                                     )
                                 )
                                 if (mMaxBuffers == 1) {
@@ -171,6 +171,14 @@
         }
     }
 
+    /**
+     * Helper method to determine if [HardwareRenderer.FrameRenderRequest.syncAndDraw] was
+     * successful. In this case we wait for the next buffer even if we miss the vsync.
+     */
+    private fun isSuccess(result: Int) =
+        result == HardwareRenderer.SYNC_OK ||
+        result == HardwareRenderer.SYNC_FRAME_DROPPED
+
     private fun updateTransform(transform: Int): Matrix {
         mBufferTransform = transform
         return BufferTransformHintResolver.configureTransformMatrix(
diff --git a/leanback/leanback/src/main/java/androidx/leanback/transition/LeanbackTransitionHelper.java b/leanback/leanback/src/main/java/androidx/leanback/transition/LeanbackTransitionHelper.java
index f1389ba..5c5eda2 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/transition/LeanbackTransitionHelper.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/transition/LeanbackTransitionHelper.java
@@ -30,7 +30,7 @@
 public class LeanbackTransitionHelper {
 
     public static Object loadTitleInTransition(Context context) {
-        if (Build.VERSION.SDK_INT < 19 || Build.VERSION.SDK_INT >= 21) {
+        if (Build.VERSION.SDK_INT >= 21) {
             return TransitionHelper.loadTransition(context, R.transition.lb_title_in);
         }
 
@@ -43,7 +43,7 @@
     }
 
     public static Object loadTitleOutTransition(Context context) {
-        if (Build.VERSION.SDK_INT < 19 || Build.VERSION.SDK_INT >= 21) {
+        if (Build.VERSION.SDK_INT >= 21) {
             return TransitionHelper.loadTransition(context, R.transition.lb_title_out);
         }
 
diff --git a/libraryversions.toml b/libraryversions.toml
index ca8477b..6951452 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -139,7 +139,7 @@
 TEST_UIAUTOMATOR = "2.3.0-alpha05"
 TEXT = "1.0.0-alpha01"
 TRACING = "1.3.0-alpha02"
-TRACING_PERFETTO = "1.0.0-beta03"
+TRACING_PERFETTO = "1.0.0"
 TRANSITION = "1.5.0-alpha05"
 TV = "1.0.0-alpha11"
 TVPROVIDER = "1.1.0-alpha02"
diff --git a/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index a92d2198..cf9e20d 100644
--- a/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -553,15 +553,9 @@
                     ? Looper.myLooper() : Looper.getMainLooper());
             setCallback(new Callback() {}, handler);
             mImpl.setMediaButtonReceiver(mbrIntent);
-        } else if (android.os.Build.VERSION.SDK_INT >= 19) {
+        } else {
             mImpl = new MediaSessionImplApi19(context, tag, mbrComponent, mbrIntent,
                     session2Token, sessionInfo);
-        } else if (android.os.Build.VERSION.SDK_INT >= 18) {
-            mImpl = new MediaSessionImplApi18(context, tag, mbrComponent, mbrIntent,
-                    session2Token, sessionInfo);
-        } else {
-            mImpl = new MediaSessionImplBase(context, tag, mbrComponent, mbrIntent, session2Token,
-                    sessionInfo);
         }
         mController = new MediaControllerCompat(context, this);
 
@@ -3758,7 +3752,6 @@
         }
     }
 
-    @RequiresApi(18)
     static class MediaSessionImplApi18 extends MediaSessionImplBase {
         private static boolean sIsMbrPendingIntentSupported = true;
 
@@ -3844,7 +3837,6 @@
         }
     }
 
-    @RequiresApi(19)
     static class MediaSessionImplApi19 extends MediaSessionImplApi18 {
         MediaSessionImplApi19(Context context, String tag, ComponentName mbrComponent,
                 PendingIntent mbrIntent, VersionedParcelable session2Token, Bundle sessionInfo) {
diff --git a/media2/media2-common/src/main/java/androidx/media2/common/ClassVerificationHelper.java b/media2/media2-common/src/main/java/androidx/media2/common/ClassVerificationHelper.java
index f6e962ee..5fc397b 100644
--- a/media2/media2-common/src/main/java/androidx/media2/common/ClassVerificationHelper.java
+++ b/media2/media2-common/src/main/java/androidx/media2/common/ClassVerificationHelper.java
@@ -51,25 +51,6 @@
         private AudioManager() {}
     }
 
-    /** Helper class for {@link android.os.HandlerThread}. */
-    public static final class HandlerThread {
-
-        /** Helper methods for {@link android.os.HandlerThread} APIs added in API level 18. */
-        @RequiresApi(18)
-        public static final class Api18 {
-
-            /** Helper method to call {@link android.os.HandlerThread#quitSafely()}. */
-            @DoNotInline
-            public static boolean quitSafely(@NonNull android.os.HandlerThread handlerThread) {
-                return handlerThread.quitSafely();
-            }
-
-            private Api18() {}
-        }
-
-        private HandlerThread() {}
-    }
-
     /** Helper class for {@link android.app.PendingIntent}. */
     public static final class PendingIntent {
 
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer_AudioFocusTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer_AudioFocusTest.java
index 20d1c73..6636bb4 100644
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer_AudioFocusTest.java
+++ b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer_AudioFocusTest.java
@@ -43,7 +43,6 @@
 import android.content.Intent;
 import android.media.AudioManager;
 import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.os.Build;
 import android.os.Build.VERSION;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -125,11 +124,7 @@
             if (sHandler == null) {
                 return;
             }
-            if (Build.VERSION.SDK_INT >= 18) {
-                sHandler.getLooper().quitSafely();
-            } else {
-                sHandler.getLooper().quit();
-            }
+            sHandler.getLooper().quitSafely();
             sHandler = null;
             sHandlerExecutor = null;
         }
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTestBase.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTestBase.java
index d39d457..2b482e0 100644
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTestBase.java
+++ b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaSessionTestBase.java
@@ -17,7 +17,6 @@
 package androidx.media2.session;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 
@@ -84,11 +83,7 @@
             if (sHandler == null) {
                 return;
             }
-            if (Build.VERSION.SDK_INT >= 18) {
-                sHandler.getLooper().quitSafely();
-            } else {
-                sHandler.getLooper().quit();
-            }
+            sHandler.getLooper().quitSafely();
             sHandler = null;
             sHandlerExecutor = null;
         }
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
index dc21534..9a4987c 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
@@ -36,7 +36,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -56,7 +55,6 @@
 import androidx.annotation.Nullable;
 import androidx.concurrent.futures.ResolvableFuture;
 import androidx.core.util.ObjectsCompat;
-import androidx.media2.common.ClassVerificationHelper;
 import androidx.media2.common.MediaItem;
 import androidx.media2.common.MediaMetadata;
 import androidx.media2.common.Rating;
@@ -205,11 +203,7 @@
             }
             mHandler.removeCallbacksAndMessages(null);
 
-            if (Build.VERSION.SDK_INT >= 18) {
-                ClassVerificationHelper.HandlerThread.Api18.quitSafely(mHandlerThread);
-            } else {
-                mHandlerThread.quit();
-            }
+            mHandlerThread.quitSafely();
 
             mClosed = true;
 
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
index 52ae967..e4ea230 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
@@ -322,11 +322,7 @@
         });
         mHandler.removeCallbacksAndMessages(null);
         if (mHandlerThread.isAlive()) {
-            if (Build.VERSION.SDK_INT >= 18) {
-                ClassVerificationHelper.HandlerThread.Api18.quitSafely(mHandlerThread);
-            } else {
-                mHandlerThread.quit();
-            }
+            mHandlerThread.quitSafely();
         }
     }
 
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/SessionToken.java b/media2/media2-session/src/main/java/androidx/media2/session/SessionToken.java
index b6c7f26..a0997be 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/SessionToken.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/SessionToken.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -38,7 +37,6 @@
 import androidx.annotation.RestrictTo;
 import androidx.media.MediaBrowserServiceCompat;
 import androidx.media.MediaSessionManager;
-import androidx.media2.common.ClassVerificationHelper;
 import androidx.versionedparcelable.ParcelField;
 import androidx.versionedparcelable.VersionedParcelable;
 import androidx.versionedparcelable.VersionedParcelize;
@@ -345,11 +343,7 @@
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     static void quitHandlerThread(HandlerThread thread) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            ClassVerificationHelper.HandlerThread.Api18.quitSafely(thread);
-        } else {
-            thread.quit();
-        }
+        thread.quitSafely();
     }
 
     @SuppressWarnings("deprecation")
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
index 639fb5b..ea17c74 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
@@ -17,7 +17,6 @@
 package androidx.media2.test.client.tests;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.support.v4.media.session.MediaSessionCompat;
@@ -88,11 +87,7 @@
             if (sHandler == null) {
                 return;
             }
-            if (Build.VERSION.SDK_INT >= 18) {
-                sHandler.getLooper().quitSafely();
-            } else {
-                sHandler.getLooper().quit();
-            }
+            sHandler.getLooper().quitSafely();
             sHandler = null;
             sHandlerExecutor = null;
         }
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
index 78b5841..cb8b580 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
@@ -54,7 +54,6 @@
 
 import android.app.Service;
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.util.Log;
@@ -124,11 +123,7 @@
             sAssertLibraryParams = false;
             sExpectedParams = null;
         }
-        if (Build.VERSION.SDK_INT >= 18) {
-            mHandler.getLooper().quitSafely();
-        } else {
-            mHandler.getLooper().quit();
-        }
+        mHandler.getLooper().quitSafely();
         mHandler = null;
         TestServiceRegistry.getInstance().cleanUp();
     }
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
index 6db0c22..40529f8 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
@@ -17,7 +17,6 @@
 package androidx.media2.test.service.tests;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 
@@ -79,11 +78,7 @@
             if (sHandler == null) {
                 return;
             }
-            if (Build.VERSION.SDK_INT >= 18) {
-                sHandler.getLooper().quitSafely();
-            } else {
-                sHandler.getLooper().quit();
-            }
+            sHandler.getLooper().quitSafely();
             sHandler = null;
             sHandlerExecutor = null;
         }
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
index 639fb5b..ea17c74 100644
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
+++ b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaSessionTestBase.java
@@ -17,7 +17,6 @@
 package androidx.media2.test.client.tests;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.support.v4.media.session.MediaSessionCompat;
@@ -88,11 +87,7 @@
             if (sHandler == null) {
                 return;
             }
-            if (Build.VERSION.SDK_INT >= 18) {
-                sHandler.getLooper().quitSafely();
-            } else {
-                sHandler.getLooper().quit();
-            }
+            sHandler.getLooper().quitSafely();
             sHandler = null;
             sHandlerExecutor = null;
         }
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
index 78b5841..cb8b580 100644
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
+++ b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/MockMediaLibraryService.java
@@ -54,7 +54,6 @@
 
 import android.app.Service;
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.util.Log;
@@ -124,11 +123,7 @@
             sAssertLibraryParams = false;
             sExpectedParams = null;
         }
-        if (Build.VERSION.SDK_INT >= 18) {
-            mHandler.getLooper().quitSafely();
-        } else {
-            mHandler.getLooper().quit();
-        }
+        mHandler.getLooper().quitSafely();
         mHandler = null;
         TestServiceRegistry.getInstance().cleanUp();
     }
diff --git a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
index 6db0c22..40529f8 100644
--- a/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
+++ b/media2/media2-session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionTestBase.java
@@ -17,7 +17,6 @@
 package androidx.media2.test.service.tests;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.HandlerThread;
 
@@ -79,11 +78,7 @@
             if (sHandler == null) {
                 return;
             }
-            if (Build.VERSION.SDK_INT >= 18) {
-                sHandler.getLooper().quitSafely();
-            } else {
-                sHandler.getLooper().quit();
-            }
+            sHandler.getLooper().quitSafely();
             sHandler = null;
             sHandlerExecutor = null;
         }
diff --git a/media2/media2-widget/src/main/java/androidx/media2/widget/ClosedCaptionWidget.java b/media2/media2-widget/src/main/java/androidx/media2/widget/ClosedCaptionWidget.java
index 70d7711..619a73b 100644
--- a/media2/media2-widget/src/main/java/androidx/media2/widget/ClosedCaptionWidget.java
+++ b/media2/media2-widget/src/main/java/androidx/media2/widget/ClosedCaptionWidget.java
@@ -156,9 +156,6 @@
      * Manages whether this renderer is listening for caption style changes.
      */
     private void manageChangeListener() {
-        if (VERSION.SDK_INT < 19) {
-            return;
-        }
         final boolean needsListener =
                 ViewCompat.isAttachedToWindow(this) && getVisibility() == View.VISIBLE;
         if (mHasChangeListener != needsListener) {
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
index c0b072a..937c4c8 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
@@ -97,6 +97,34 @@
             .isFalse()
     }
 
+    @Test
+    fun graphEqualsId() {
+        val graph = NavGraph(navGraphNavigator)
+        graph += navigator.createDestination().apply { id = DESTINATION_ID }
+        graph += navigator.createDestination().apply { id = SECOND_DESTINATION_ID }
+        val other = NavGraph(navGraphNavigator)
+        other += navigator.createDestination().apply { id = DESTINATION_ID }
+        other += navigator.createDestination().apply { id = SECOND_DESTINATION_ID }
+
+        assertWithMessage("Graphs should be equal")
+            .that(graph)
+            .isEqualTo(other)
+    }
+
+    @Test
+    fun graphNotEqualsId() {
+        val graph = NavGraph(navGraphNavigator)
+        graph += navigator.createDestination().apply { id = DESTINATION_ID }
+        graph += navigator.createDestination().apply { id = SECOND_DESTINATION_ID }
+        val other = NavGraph(navGraphNavigator)
+        other += navigator.createDestination().apply { id = DESTINATION_ID }
+        other += navigator.createDestination().apply { id = 3 }
+
+        assertWithMessage("Graphs should not be equal")
+            .that(graph)
+            .isNotEqualTo(other)
+    }
+
     @Test(expected = IllegalArgumentException::class)
     fun getIllegalArgumentException() {
         val graph = NavGraph(navGraphNavigator)
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
index f4b21df..1e1020e 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -384,7 +384,7 @@
         return super.equals(other) &&
             nodes.size == other.nodes.size &&
             startDestinationId == other.startDestinationId &&
-            nodes.valueIterator().asSequence().all { it == nodes.get(it.id) }
+            nodes.valueIterator().asSequence().all { it == other.nodes.get(it.id) }
     }
 
     override fun hashCode(): Int {
diff --git a/percentlayout/percentlayout/src/androidTest/java/androidx/percentlayout/widget/PercentRelativeRtlTest.java b/percentlayout/percentlayout/src/androidTest/java/androidx/percentlayout/widget/PercentRelativeRtlTest.java
index 640be5b..68d01a3 100644
--- a/percentlayout/percentlayout/src/androidTest/java/androidx/percentlayout/widget/PercentRelativeRtlTest.java
+++ b/percentlayout/percentlayout/src/androidTest/java/androidx/percentlayout/widget/PercentRelativeRtlTest.java
@@ -161,22 +161,14 @@
 
     @Test
     public void testStartChild() {
-        if (Build.VERSION.SDK_INT == 17) {
-            return;
-        }
+
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_start);
 
-        if (Build.VERSION.SDK_INT >= 17) {
-            switchToRtl();
+        switchToRtl();
 
-            final int childRight = childToTest.getRight();
-            assertFuzzyEquals("Child start margin as 5% of the container",
-                    0.05f * mContainerWidth, mContainerWidth - childRight);
-        } else {
-            final int childLeft = childToTest.getLeft();
-            assertFuzzyEquals("Child start margin as 5% of the container",
-                    0.05f * mContainerWidth, childLeft);
-        }
+        final int childRight = childToTest.getRight();
+        assertFuzzyEquals("Child start margin as 5% of the container",
+                0.05f * mContainerWidth, mContainerWidth - childRight);
 
         final int childWidth = childToTest.getWidth();
         final int childHeight = childToTest.getHeight();
@@ -194,23 +186,13 @@
 
     @Test
     public void testBottomChild() {
-        if (Build.VERSION.SDK_INT == 17) {
-            return;
-        }
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
 
-        if (Build.VERSION.SDK_INT >= 17) {
-            switchToRtl();
+        switchToRtl();
 
-            final int childLeft = childToTest.getLeft();
-            assertFuzzyEquals("Child end margin as 20% of the container",
-                    0.2f * mContainerWidth, childLeft);
-        } else {
-            final int childRight = childToTest.getRight();
-            assertFuzzyEquals("Child end margin as 20% of the container",
-                    0.2f * mContainerWidth, mContainerWidth - childRight);
-        }
-
+        final int childLeft = childToTest.getLeft();
+        assertFuzzyEquals("Child end margin as 20% of the container",
+                0.2f * mContainerWidth, childLeft);
 
         final int childWidth = childToTest.getWidth();
         final int childHeight = childToTest.getHeight();
@@ -228,22 +210,13 @@
 
     @Test
     public void testEndChild() {
-        if (Build.VERSION.SDK_INT == 17) {
-            return;
-        }
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_end);
 
-        if (Build.VERSION.SDK_INT >= 17) {
-            switchToRtl();
+        switchToRtl();
 
-            final int childLeft = childToTest.getLeft();
-            assertFuzzyEquals("Child end margin as 5% of the container",
-                    0.05f * mContainerWidth, childLeft);
-        } else {
-            final int childRight = childToTest.getRight();
-            assertFuzzyEquals("Child end margin as 5% of the container",
-                    0.05f * mContainerWidth, mContainerWidth - childRight);
-        }
+        final int childLeft = childToTest.getLeft();
+        assertFuzzyEquals("Child end margin as 5% of the container",
+                0.05f * mContainerWidth, childLeft);
 
         final int childWidth = childToTest.getWidth();
         final int childHeight = childToTest.getHeight();
@@ -261,24 +234,15 @@
 
     @Test
     public void testCenterChild() {
-        if (Build.VERSION.SDK_INT == 17) {
-            return;
-        }
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
-
-        boolean supportsRtl = Build.VERSION.SDK_INT >= 17;
-        if (supportsRtl) {
-            switchToRtl();
-        }
+        switchToRtl();
 
         final int childLeft = childToTest.getLeft();
         final int childTop = childToTest.getTop();
         final int childRight = childToTest.getRight();
         final int childBottom = childToTest.getBottom();
 
-        final View leftChild = supportsRtl
-                ? mPercentRelativeLayout.findViewById(R.id.child_end)
-                : mPercentRelativeLayout.findViewById(R.id.child_start);
+        final View leftChild = mPercentRelativeLayout.findViewById(R.id.child_end);
         assertFuzzyEquals("Child left margin as 10% of the container",
                 leftChild.getRight() + 0.1f * mContainerWidth, childLeft);
 
@@ -286,9 +250,8 @@
         assertFuzzyEquals("Child top margin as 10% of the container",
                 topChild.getBottom() + 0.1f * mContainerHeight, childTop);
 
-        final View rightChild = supportsRtl
-                ? mPercentRelativeLayout.findViewById(R.id.child_start)
-                : mPercentRelativeLayout.findViewById(R.id.child_end);
+        final View rightChild = mPercentRelativeLayout.findViewById(R.id.child_start);
+
         assertFuzzyEquals("Child right margin as 10% of the container",
                 rightChild.getLeft() - 0.1f * mContainerWidth, childRight);
 
diff --git a/print/print/src/main/java/androidx/print/PrintHelper.java b/print/print/src/main/java/androidx/print/PrintHelper.java
index 01f01f7..7c1ed96 100644
--- a/print/print/src/main/java/androidx/print/PrintHelper.java
+++ b/print/print/src/main/java/androidx/print/PrintHelper.java
@@ -259,7 +259,7 @@
      */
     public void printBitmap(@NonNull final String jobName, @NonNull final Bitmap bitmap,
             @Nullable final OnPrintFinishCallback callback) {
-        if (Build.VERSION.SDK_INT < 19 || bitmap == null) {
+        if (bitmap == null) {
             return;
         }
 
@@ -357,10 +357,6 @@
     public void printBitmap(@NonNull final String jobName, @NonNull final Uri imageFile,
             @Nullable final OnPrintFinishCallback callback)
             throws FileNotFoundException {
-        if (Build.VERSION.SDK_INT < 19) {
-            return;
-        }
-
         PrintDocumentAdapter printDocumentAdapter = new PrintUriAdapter(jobName, imageFile,
                 callback, mScaleMode);
 
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index c19f539..741bb69 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -53,7 +53,6 @@
 import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Build;
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.view.Gravity;
@@ -841,8 +840,7 @@
             public View onFocusSearchFailed(View focused, int direction,
                     RecyclerView.Recycler recycler,
                     RecyclerView.State state) {
-                int expectedDir = Build.VERSION.SDK_INT <= 15 ? View.FOCUS_DOWN :
-                        View.FOCUS_FORWARD;
+                int expectedDir = View.FOCUS_FORWARD;
                 assertEquals(expectedDir, direction);
                 assertEquals(1, getChildCount());
                 View child0 = getChildAt(0);
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/Placeholder.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/Placeholder.kt
new file mode 100644
index 0000000..bad50f2
--- /dev/null
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/Placeholder.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+// empty file to trigger klib creation
+// see: https://youtrack.jetbrains.com/issue/KT-52344
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java b/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
index 399ba9f..edb4a01 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
@@ -211,7 +211,6 @@
 
     @Override
     public final boolean onCreate() {
-        if (Build.VERSION.SDK_INT < 19) return false;
         return onCreateSliceProvider();
     }
 
@@ -243,7 +242,6 @@
     @Nullable
     @Override
     public final String getType(@NonNull Uri uri) {
-        if (Build.VERSION.SDK_INT < 19) return null;
         if (DEBUG) Log.d(TAG, "getFormat " + uri);
         return SLICE_TYPE;
     }
@@ -272,7 +270,7 @@
     @Nullable
     @Override
     public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
-        if (Build.VERSION.SDK_INT < 19 || Build.VERSION.SDK_INT >= 28) return null;
+        if (Build.VERSION.SDK_INT >= 28) return null;
         if (extras == null) return null;
         return getSliceProviderCompat().call(method, arg, extras);
     }
diff --git a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
index fe1c603..f3fa6a7 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
@@ -17,7 +17,6 @@
 package androidx.fragment.app
 
 import android.content.Context
-import android.os.Build
 import android.os.Bundle
 import android.util.AttributeSet
 import androidx.annotation.LayoutRes
@@ -53,9 +52,7 @@
     }
 
     fun checkActivityNotDestroyed() {
-        if (Build.VERSION.SDK_INT >= 17) {
-            check(!requireActivity().isDestroyed)
-        }
+        check(!requireActivity().isDestroyed)
     }
 
     fun checkState(caller: String, vararg expected: State) {
diff --git a/text/text/lint-baseline.xml b/text/text/lint-baseline.xml
index 9d2be4d..2a6edab 100644
--- a/text/text/lint-baseline.xml
+++ b/text/text/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
+<issues format="6" by="lint 8.3.0-alpha10" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha10)" variant="all" version="8.3.0-alpha10">
 
     <issue
         id="PrimitiveInCollection"
@@ -7,7 +7,7 @@
         errorLine1="    private val paragraphEnds: List&lt;Int>"
         errorLine2="                               ~~~~~~~~~">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/LayoutHelper.android.kt"/>
     </issue>
 
     <issue
@@ -16,7 +16,7 @@
         errorLine1="        val lineFeeds = mutableListOf&lt;Int>()"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/LayoutHelper.android.kt"/>
     </issue>
 
     <issue
@@ -25,7 +25,7 @@
         errorLine1="    private fun breakInWords(layoutHelper: LayoutHelper): List&lt;Int> {"
         errorLine2="                                                          ~~~~~~~~~">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
     </issue>
 
     <issue
@@ -34,7 +34,7 @@
         errorLine1="        val words = breakWithBreakIterator(text, BreakIterator.getLineInstance(Locale.getDefault()))"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
     </issue>
 
     <issue
@@ -43,7 +43,7 @@
         errorLine1="        val set = TreeSet&lt;Int>().apply {"
         errorLine2="        ^">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
     </issue>
 
     <issue
@@ -52,7 +52,7 @@
         errorLine1="    private fun breakWithBreakIterator(text: CharSequence, breaker: BreakIterator): List&lt;Int> {"
         errorLine2="                                                                                    ~~~~~~~~~">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
     </issue>
 
     <issue
@@ -61,7 +61,7 @@
         errorLine1="        val res = mutableListOf(0)"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
     </issue>
 
     <issue
@@ -70,7 +70,7 @@
         errorLine1="    fun breakOffsets(layoutHelper: LayoutHelper, segmentType: SegmentType): List&lt;Int> {"
         errorLine2="                                                                            ~~~~~~~~~">
         <location
-            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt"/>
+            file="src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt"/>
     </issue>
 
 </issues>
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.kt b/text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/BoringLayoutFactory.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/CharSequenceCharacterIterator.kt b/text/text/src/main/java/androidx/compose/ui/text/android/CharSequenceCharacterIterator.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/CharSequenceCharacterIterator.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/CharSequenceCharacterIterator.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/InlineClassUtils.kt b/text/text/src/main/java/androidx/compose/ui/text/android/InlineClassUtils.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/InlineClassUtils.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/InlineClassUtils.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/InternalPlatformTextApi.kt b/text/text/src/main/java/androidx/compose/ui/text/android/InternalPlatformTextApi.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/InternalPlatformTextApi.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/InternalPlatformTextApi.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutCompat.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutCompat.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/LayoutCompat.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/LayoutCompat.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/ListUtils.kt b/text/text/src/main/java/androidx/compose/ui/text/android/ListUtils.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/ListUtils.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/ListUtils.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.kt b/text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/PaintExtensions.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/SpannedExtensions.kt b/text/text/src/main/java/androidx/compose/ui/text/android/SpannedExtensions.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/SpannedExtensions.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/SpannedExtensions.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.kt b/text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/StaticLayoutFactory.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/TextAndroidCanvas.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt b/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentType.kt b/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentType.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentType.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentType.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/selection/WordBoundary.kt b/text/text/src/main/java/androidx/compose/ui/text/android/selection/WordBoundary.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/selection/WordBoundary.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/selection/WordBoundary.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/selection/WordIterator.kt b/text/text/src/main/java/androidx/compose/ui/text/android/selection/WordIterator.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/selection/WordIterator.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/selection/WordIterator.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/BaselineShiftSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/BaselineShiftSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/BaselineShiftSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/BaselineShiftSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/FontFeatureSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/FontFeatureSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/FontFeatureSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/FontFeatureSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/IndentationFixSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/IndentationFixSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/IndentationFixSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/IndentationFixSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanEm.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanEm.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanEm.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanEm.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanPx.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanPx.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanPx.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/LetterSpacingSpanPx.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightStyleSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightStyleSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightStyleSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/LineHeightStyleSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/PlaceholderSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/PlaceholderSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/PlaceholderSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/PlaceholderSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/ShadowSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/ShadowSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/ShadowSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/ShadowSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/SkewXSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/SkewXSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/SkewXSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/SkewXSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.android.kt
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/TypefaceSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/TypefaceSpan.android.kt
similarity index 100%
rename from text/text/src/main/java/androidx/compose/ui/text/android/style/TypefaceSpan.kt
rename to text/text/src/main/java/androidx/compose/ui/text/android/style/TypefaceSpan.android.kt
diff --git a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
index aa266f7..c7ad150 100644
--- a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
+++ b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
@@ -25,7 +25,7 @@
 // Concept of version useful e.g. for human-readable error messages, and stable once released.
 // Does not replace the need for a binary verification mechanism (e.g. checksum check).
 // TODO: populate using CMake
-#define VERSION "1.0.0-beta03"
+#define VERSION "1.0.0"
 
 namespace tracing_perfetto {
     void RegisterWithPerfetto() {
diff --git a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
index ef3b214..d3ef52d 100644
--- a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
+++ b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
@@ -30,7 +30,7 @@
         init {
             PerfettoNative.loadLib()
         }
-        const val libraryVersion = "1.0.0-beta03" // TODO: get using reflection
+        const val libraryVersion = "1.0.0" // TODO: get using reflection
     }
 
     @Test
diff --git a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
index 3cc418e..47e3465 100644
--- a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
+++ b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
@@ -25,12 +25,12 @@
 
     // TODO(224510255): load from a file produced at build time
     object Metadata {
-        const val version = "1.0.0-beta03"
+        const val version = "1.0.0"
         val checksums = mapOf(
-            "arm64-v8a" to "e11502d6fa0c949774a792c2406744a1fff112ba26e6af19a6722dd55a6061ca",
-            "armeabi-v7a" to "cd286085893cc7760b658f48b436fd317493159dcbab680667c0bf01d25ffb04",
-            "x86" to "2679c351d40e405e46dec58c8f63570eb29196cd9e67404cf66abe74bc933a88",
-            "x86_64" to "2971a9135cd69feb2a4e3c7b42f6b52469421713331cdb7a841c1c8cc707b637",
+            "arm64-v8a" to "a152fbd7ebaa109a9c3cf6bbb6d585aa0df08f97ae022b2090b1096a8f5e2665",
+            "armeabi-v7a" to "b2821c9ddb77a3f070cce42be7cd3255d7ec92c868d7d518a99ed968d9018b9f",
+            "x86" to "4cefdc75fe41deeeb2306891c25ce4db33599698cc6fcb2e82caad5aece9aa09",
+            "x86_64" to "23daf0750238cf96bf9ea9fa1b13ae1d2eeb17644ea5439e18939ec6a8b9e5be",
         )
     }
 
diff --git a/transition/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java b/transition/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java
index 3c27a70..92b0821 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java
+++ b/transition/transition/src/androidTest/java/androidx/transition/ChangeBoundsTest.java
@@ -26,7 +26,6 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.Build;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.LinearInterpolator;
@@ -92,11 +91,6 @@
 
     @Test
     public void testSuppressLayoutWhileAnimating() throws Throwable {
-        if (Build.VERSION.SDK_INT < 18) {
-            // prior Android 4.3 suppressLayout port has another implementation which is
-            // harder to test
-            return;
-        }
         final TestSuppressLayout suppressLayout = new TestSuppressLayout(rule.getActivity());
         final View testView = new View(rule.getActivity());
         rule.runOnUiThread(new Runnable() {
diff --git a/transition/transition/src/main/java/androidx/transition/ViewGroupOverlayApi18.java b/transition/transition/src/main/java/androidx/transition/ViewGroupOverlayApi18.java
index 20e4282..4c5f4e8 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewGroupOverlayApi18.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewGroupOverlayApi18.java
@@ -22,9 +22,7 @@
 import android.view.ViewGroupOverlay;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
-@RequiresApi(18)
 class ViewGroupOverlayApi18 implements ViewGroupOverlayImpl {
 
     private final ViewGroupOverlay mViewGroupOverlay;
diff --git a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
index 37126f5..52c2f97 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
@@ -44,10 +44,7 @@
      * Backward-compatible {@link ViewGroup#getOverlay()}.
      */
     static ViewGroupOverlayImpl getOverlay(@NonNull ViewGroup group) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return new ViewGroupOverlayApi18(group);
-        }
-        return ViewGroupOverlayApi14.createFrom(group);
+        return new ViewGroupOverlayApi18(group);
     }
 
     /**
@@ -56,14 +53,11 @@
     static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
         if (Build.VERSION.SDK_INT >= 29) {
             Api29Impl.suppressLayout(group, suppress);
-        } else if (Build.VERSION.SDK_INT >= 18) {
-            hiddenSuppressLayout(group, suppress);
         } else {
-            ViewGroupUtilsApi14.suppressLayout(group, suppress);
+            hiddenSuppressLayout(group, suppress);
         }
     }
 
-    @RequiresApi(18)
     @SuppressLint("NewApi") // Lint doesn't know about the hidden method.
     private static void hiddenSuppressLayout(@NonNull ViewGroup group, boolean suppress) {
         if (sTryHiddenSuppressLayout) {
diff --git a/transition/transition/src/main/java/androidx/transition/ViewGroupUtilsApi14.java b/transition/transition/src/main/java/androidx/transition/ViewGroupUtilsApi14.java
deleted file mode 100644
index 2a83cc1..0000000
--- a/transition/transition/src/main/java/androidx/transition/ViewGroupUtilsApi14.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 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.transition;
-
-import android.animation.LayoutTransition;
-import android.annotation.SuppressLint;
-import android.util.Log;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-class ViewGroupUtilsApi14 {
-
-    private static final String TAG = "ViewGroupUtilsApi14";
-
-    private static final int LAYOUT_TRANSITION_CHANGING = 4;
-
-    private static LayoutTransition sEmptyLayoutTransition;
-
-    private static Field sLayoutSuppressedField;
-    private static boolean sLayoutSuppressedFieldFetched;
-
-    private static Method sCancelMethod;
-    private static boolean sCancelMethodFetched;
-
-    static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
-        // Prepare the empty LayoutTransition
-        if (sEmptyLayoutTransition == null) {
-            sEmptyLayoutTransition = new LayoutTransition() {
-                @Override
-                public boolean isChangingLayout() {
-                    return true;
-                }
-            };
-            sEmptyLayoutTransition.setAnimator(LayoutTransition.APPEARING, null);
-            sEmptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, null);
-            sEmptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null);
-            sEmptyLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, null);
-            sEmptyLayoutTransition.setAnimator(LAYOUT_TRANSITION_CHANGING, null);
-        }
-        if (suppress) {
-            // Save the current LayoutTransition
-            final LayoutTransition layoutTransition = group.getLayoutTransition();
-            if (layoutTransition != null) {
-                if (layoutTransition.isRunning()) {
-                    cancelLayoutTransition(layoutTransition);
-                }
-                if (layoutTransition != sEmptyLayoutTransition) {
-                    group.setTag(R.id.transition_layout_save, layoutTransition);
-                }
-            }
-            // Suppress the layout
-            group.setLayoutTransition(sEmptyLayoutTransition);
-        } else {
-            // Thaw the layout suppression
-            group.setLayoutTransition(null);
-            // Request layout if necessary
-            if (!sLayoutSuppressedFieldFetched) {
-                try {
-                    sLayoutSuppressedField = ViewGroup.class.getDeclaredField("mLayoutSuppressed");
-                    sLayoutSuppressedField.setAccessible(true);
-                } catch (NoSuchFieldException e) {
-                    Log.i(TAG, "Failed to access mLayoutSuppressed field by reflection");
-                }
-                sLayoutSuppressedFieldFetched = true;
-            }
-            boolean layoutSuppressed = false;
-            if (sLayoutSuppressedField != null) {
-                try {
-                    layoutSuppressed = sLayoutSuppressedField.getBoolean(group);
-                    if (layoutSuppressed) {
-                        sLayoutSuppressedField.setBoolean(group, false);
-                    }
-                } catch (IllegalAccessException e) {
-                    Log.i(TAG, "Failed to get mLayoutSuppressed field by reflection");
-                }
-            }
-            if (layoutSuppressed) {
-                group.requestLayout();
-            }
-            // Restore the saved LayoutTransition
-            final LayoutTransition layoutTransition =
-                    (LayoutTransition) group.getTag(R.id.transition_layout_save);
-            if (layoutTransition != null) {
-                group.setTag(R.id.transition_layout_save, null);
-                group.setLayoutTransition(layoutTransition);
-            }
-        }
-    }
-
-    /**
-     * Note, this is only called on API 17 and older.
-     */
-    @SuppressLint({"SoonBlockedPrivateApi", "BanUncheckedReflection"})
-    private static void cancelLayoutTransition(LayoutTransition t) {
-        if (!sCancelMethodFetched) {
-            try {
-                sCancelMethod = LayoutTransition.class.getDeclaredMethod("cancel");
-                sCancelMethod.setAccessible(true);
-            } catch (NoSuchMethodException e) {
-                Log.i(TAG, "Failed to access cancel method by reflection");
-            }
-            sCancelMethodFetched = true;
-        }
-        if (sCancelMethod != null) {
-            try {
-                sCancelMethod.invoke(t);
-            } catch (IllegalAccessException e) {
-                Log.i(TAG, "Failed to access cancel method by reflection");
-            } catch (InvocationTargetException e) {
-                Log.i(TAG, "Failed to invoke cancel method by reflection");
-            }
-        }
-    }
-
-    private ViewGroupUtilsApi14() {
-    }
-}
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtils.java b/transition/transition/src/main/java/androidx/transition/ViewUtils.java
index 388c11d..7d609b0 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtils.java
@@ -87,20 +87,14 @@
      * Backward-compatible {@link View#getOverlay()}.
      */
     static ViewOverlayImpl getOverlay(@NonNull View view) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return new ViewOverlayApi18(view);
-        }
-        return ViewOverlayApi14.createFrom(view);
+        return new ViewOverlayApi18(view);
     }
 
     /**
      * Backward-compatible {@link View#getWindowId()}.
      */
     static @NonNull WindowIdImpl getWindowId(@NonNull View view) {
-        if (Build.VERSION.SDK_INT >= 18) {
-            return new WindowIdApi18(view);
-        }
-        return new WindowIdApi14(view.getWindowToken());
+        return new WindowIdApi18(view);
     }
 
     static void setTransitionAlpha(@NonNull View view, float alpha) {
diff --git a/transition/transition/src/main/java/androidx/transition/WindowIdApi14.java b/transition/transition/src/main/java/androidx/transition/WindowIdApi14.java
deleted file mode 100644
index 6a9231e..0000000
--- a/transition/transition/src/main/java/androidx/transition/WindowIdApi14.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 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.transition;
-
-import android.os.IBinder;
-
-class WindowIdApi14 implements WindowIdImpl {
-
-    private final IBinder mToken;
-
-    WindowIdApi14(IBinder token) {
-        mToken = token;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        return o instanceof WindowIdApi14 && ((WindowIdApi14) o).mToken.equals(this.mToken);
-    }
-
-    @Override
-    public int hashCode() {
-        return mToken.hashCode();
-    }
-}
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
index 13b3fde..5a30ac2 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
@@ -59,7 +59,6 @@
 import androidx.tv.foundation.lazy.list.setContentWithTestViewConfiguration
 import com.google.common.collect.Range
 import com.google.common.truth.IntegerSubject
-import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
@@ -71,6 +70,8 @@
 class LazyGridTest(
     private val orientation: Orientation
 ) : BaseLazyGridTestWithOrientation(orientation) {
+
+    @Suppress("PrivatePropertyName")
     private val LazyGridTag = "LazyGridTag"
 
     companion object {
@@ -148,7 +149,7 @@
             }
         }
 
-        rule.keyPress(3)
+        rule.keyPress(2)
 
         rule.onNodeWithTag("4")
             .assertIsDisplayed()
@@ -670,9 +671,9 @@
                 state.scrollToItem(50)
             }
             composedIndexes.forEach {
-                Truth.assertThat(it).isLessThan(count)
+                assertThat(it).isLessThan(count)
             }
-            Truth.assertThat(state.firstVisibleItemIndex).isEqualTo(9)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(9)
         }
     }
 
@@ -723,7 +724,7 @@
             }
         }
 
-        rule.keyPress(3)
+        rule.keyPress(2)
 
         rule.onNodeWithTag("1")
             .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
@@ -891,7 +892,7 @@
         }
 
         rule.runOnIdle {
-            Truth.assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+            assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
         }
     }
 
@@ -922,17 +923,17 @@
         }
 
         rule.runOnIdle {
-            Truth.assertThat(remeasureCount).isEqualTo(1)
+            assertThat(remeasureCount).isEqualTo(1)
             counter.value++
         }
 
         rule.runOnIdle {
-            Truth.assertThat(remeasureCount).isEqualTo(1)
+            assertThat(remeasureCount).isEqualTo(1)
         }
     }
 
     @Test
-    fun scrollingALotDoesntCauseLazyLayoutRecomposition() {
+    fun scrollingALotDoesNotCauseLazyLayoutRecomposition() {
         var recomposeCount = 0
         lateinit var state: TvLazyGridState
 
@@ -953,7 +954,7 @@
         }
 
         rule.runOnIdle {
-            Truth.assertThat(recomposeCount).isEqualTo(1)
+            assertThat(recomposeCount).isEqualTo(1)
 
             runBlocking {
                 state.scrollToItem(100)
@@ -961,7 +962,7 @@
         }
 
         rule.runOnIdle {
-            Truth.assertThat(recomposeCount).isEqualTo(1)
+            assertThat(recomposeCount).isEqualTo(1)
         }
     }
 
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt
index 4d8ad86..196d022 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridsReverseLayoutTest.kt
@@ -34,16 +34,20 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
 import androidx.tv.foundation.lazy.list.setContentWithTestViewConfiguration
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
 
+private const val ContainerTag = "ContainerTag"
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
 class LazyGridsReverseLayoutTest {
-
-    private val ContainerTag = "ContainerTag"
-
     @get:Rule
     val rule = createComposeRule()
 
@@ -164,7 +168,7 @@
         }
 
         // we scroll down and as the scrolling is reversed it shouldn't affect anything
-        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_UP, 2)
+        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_DOWN, 2)
 
         rule.runOnIdle {
             assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
@@ -193,7 +197,7 @@
             }
         }
 
-        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_DOWN, 2)
+        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_DOWN, 1)
 
         val scrolled = rule.runOnIdle {
             assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt
index baf2ba0..4fd6a16 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt
@@ -93,8 +93,10 @@
 @LargeTest
 @RunWith(Parameterized::class)
 class LazyListTest(orientation: Orientation) : BaseLazyListTestWithOrientation(orientation) {
+    @Suppress("PrivatePropertyName")
     private val LazyListTag = "LazyListTag"
-    private val firstItemTag = "firstItemTag"
+    @Suppress("PrivatePropertyName")
+    private val FirstItemTag = "firstItemTag"
 
     @Test
     fun lazyListShowsCombinedItems() {
@@ -243,7 +245,7 @@
             }
         }
 
-        rule.keyPress(3)
+        rule.keyPress(2)
 
         rule.onNodeWithTag("1")
             .assertIsDisplayed()
@@ -274,7 +276,7 @@
             }
         }
 
-        rule.keyPress(3)
+        rule.keyPress(2)
 
         rule.onNodeWithTag("1")
             .assertIsNotDisplayed()
@@ -306,7 +308,7 @@
             }
         }
 
-        rule.keyPress(4)
+        rule.keyPress(3)
 
         rule.onNodeWithTag("1")
             .assertIsNotDisplayed()
@@ -463,7 +465,8 @@
             }
         }
 
-        rule.keyPress(3)
+        rule.waitForIdle()
+        rule.keyPress(2)
 
         rule.onNodeWithTag(thirdTag)
             .assertExists()
@@ -487,13 +490,13 @@
             LazyColumnOrRow(Modifier.requiredSize(width = 100.dp, height = 150.dp)) {
                 items(listOf(0)) {
                     Spacer(
-                        Modifier.fillParentMaxWidth().requiredHeight(50.dp).testTag(firstItemTag)
+                        Modifier.fillParentMaxWidth().requiredHeight(50.dp).testTag(FirstItemTag)
                     )
                 }
             }
         }
 
-        rule.onNodeWithTag(firstItemTag)
+        rule.onNodeWithTag(FirstItemTag)
             .assertWidthIsEqualTo(100.dp)
             .assertHeightIsEqualTo(50.dp)
     }
@@ -504,13 +507,13 @@
             LazyColumnOrRow(Modifier.requiredSize(width = 100.dp, height = 150.dp)) {
                 items(listOf(0)) {
                     Spacer(
-                        Modifier.requiredWidth(50.dp).fillParentMaxHeight().testTag(firstItemTag)
+                        Modifier.requiredWidth(50.dp).fillParentMaxHeight().testTag(FirstItemTag)
                     )
                 }
             }
         }
 
-        rule.onNodeWithTag(firstItemTag)
+        rule.onNodeWithTag(FirstItemTag)
             .assertWidthIsEqualTo(50.dp)
             .assertHeightIsEqualTo(150.dp)
     }
@@ -520,12 +523,12 @@
         rule.setContentWithTestViewConfiguration {
             LazyColumnOrRow(Modifier.requiredSize(width = 100.dp, height = 150.dp)) {
                 items(listOf(0)) {
-                    Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+                    Spacer(Modifier.fillParentMaxSize().testTag(FirstItemTag))
                 }
             }
         }
 
-        rule.onNodeWithTag(firstItemTag)
+        rule.onNodeWithTag(FirstItemTag)
             .assertWidthIsEqualTo(100.dp)
             .assertHeightIsEqualTo(150.dp)
     }
@@ -538,13 +541,13 @@
                     Spacer(
                         Modifier.fillParentMaxWidth(0.7f)
                             .requiredHeight(50.dp)
-                            .testTag(firstItemTag)
+                            .testTag(FirstItemTag)
                     )
                 }
             }
         }
 
-        rule.onNodeWithTag(firstItemTag)
+        rule.onNodeWithTag(FirstItemTag)
             .assertWidthIsEqualTo(70.dp)
             .assertHeightIsEqualTo(50.dp)
     }
@@ -557,13 +560,13 @@
                     Spacer(
                         Modifier.requiredWidth(50.dp)
                             .fillParentMaxHeight(0.3f)
-                            .testTag(firstItemTag)
+                            .testTag(FirstItemTag)
                     )
                 }
             }
         }
 
-        rule.onNodeWithTag(firstItemTag)
+        rule.onNodeWithTag(FirstItemTag)
             .assertWidthIsEqualTo(50.dp)
             .assertHeightIsEqualTo(45.dp)
     }
@@ -573,12 +576,12 @@
         rule.setContentWithTestViewConfiguration {
             LazyColumnOrRow(Modifier.requiredSize(width = 100.dp, height = 150.dp)) {
                 items(listOf(0)) {
-                    Spacer(Modifier.fillParentMaxSize(0.5f).testTag(firstItemTag))
+                    Spacer(Modifier.fillParentMaxSize(0.5f).testTag(FirstItemTag))
                 }
             }
         }
 
-        rule.onNodeWithTag(firstItemTag)
+        rule.onNodeWithTag(FirstItemTag)
             .assertWidthIsEqualTo(50.dp)
             .assertHeightIsEqualTo(75.dp)
     }
@@ -589,7 +592,7 @@
         rule.setContentWithTestViewConfiguration {
             LazyColumnOrRow(Modifier.requiredSize(parentSize)) {
                 items(listOf(0)) {
-                    Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+                    Spacer(Modifier.fillParentMaxSize().testTag(FirstItemTag))
                 }
             }
         }
@@ -598,7 +601,7 @@
             parentSize = 150.dp
         }
 
-        rule.onNodeWithTag(firstItemTag)
+        rule.onNodeWithTag(FirstItemTag)
             .assertWidthIsEqualTo(150.dp)
             .assertHeightIsEqualTo(150.dp)
     }
@@ -752,6 +755,8 @@
             }
         }
 
+        rule.waitForIdle()
+
         // getting focus to the first element
         rule.keyPress(2)
         // we already displaying the first item, so this should do nothing
@@ -1066,12 +1071,10 @@
             ) {
                 items(items) {
                     Spacer(
-                        if (it == 0) {
-                            Modifier.crossAxisSize(30.dp).mainAxisSize(itemSize / 2)
-                        } else if (it == 1) {
-                            Modifier.crossAxisSize(20.dp).mainAxisSize(itemSize / 2)
-                        } else {
-                            Modifier.crossAxisSize(20.dp).mainAxisSize(itemSize)
+                        when (it) {
+                            0 -> Modifier.crossAxisSize(30.dp).mainAxisSize(itemSize / 2)
+                            1 -> Modifier.crossAxisSize(20.dp).mainAxisSize(itemSize / 2)
+                            else -> Modifier.crossAxisSize(20.dp).mainAxisSize(itemSize)
                         }
                     )
                 }
@@ -1161,7 +1164,7 @@
     }
 
     @Test
-    fun overscrollingBackwardFromNotTheFirstPosition() {
+    fun overScrollingBackwardFromNotTheFirstPosition() {
         val containerTag = "container"
         val itemSizePx = 10
         val itemSizeDp = with(rule.density) { itemSizePx.toDp() }
@@ -1513,7 +1516,7 @@
             }
         }
 
-        rule.keyPress(3)
+        rule.keyPress(2)
 
         rule.onNodeWithTag("1")
             .assertStartPositionInRootIsEqualTo(0.dp)
@@ -1687,7 +1690,7 @@
     }
 
     @Test
-    fun scrollingALotDoesntCauseLazyLayoutRecomposition() {
+    fun scrollingALotDoesNotCauseLazyLayoutRecomposition() {
         var recomposeCount = 0
         lateinit var state: TvLazyListState
 
@@ -1759,8 +1762,8 @@
                         Box(Modifier.fillParentMaxSize())
                     }
                 }
-            }) { measurables, _ ->
-                val placeable = measurables.first().measure(constraints)
+            }) { measurableList, _ ->
+                val placeable = measurableList.first().measure(constraints)
                 layout(constraints.maxWidth, constraints.maxHeight) {
                     placeable.place(0, 0)
                 }
@@ -1789,13 +1792,13 @@
                                 .testTag("item"))
                     }
                 }
-            }) { measurables, _ ->
+            }) { measurableList, _ ->
                 val crossInfinityConstraints = if (vertical) {
                     Constraints(maxWidth = Constraints.Infinity, maxHeight = 100)
                 } else {
                     Constraints(maxWidth = 100, maxHeight = Constraints.Infinity)
                 }
-                val placeable = measurables.first().measure(crossInfinityConstraints)
+                val placeable = measurableList.first().measure(crossInfinityConstraints)
                 layout(placeable.width, placeable.height) {
                     placeable.place(0, 0)
                 }
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListsReverseLayoutTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListsReverseLayoutTest.kt
index 4ec7ab7..32ee3a2 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListsReverseLayoutTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListsReverseLayoutTest.kt
@@ -34,15 +34,22 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.MediumTest
 import androidx.tv.foundation.PivotOffsets
 import androidx.tv.foundation.lazy.grid.keyPress
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
 
+@MediumTest
+@RunWith(AndroidJUnit4::class)
 class LazyListsReverseLayoutTest {
 
+    @Suppress("PrivatePropertyName")
     private val ContainerTag = "ContainerTag"
 
     @get:Rule
@@ -151,6 +158,7 @@
             .assertTopPositionInRootIsEqualTo(itemSize)
     }
 
+    @FlakyTest(bugId = 313465577)
     @Test
     fun column_scrollForwardHalfWay() {
         lateinit var state: TvLazyListState
@@ -167,7 +175,7 @@
             }
         }
 
-        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_UP, 3)
+        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_UP, 1)
 
         val scrolled = rule.runOnIdle {
             assertThat(state.firstVisibleItemIndex).isEqualTo(0)
@@ -310,6 +318,7 @@
             .assertLeftPositionInRootIsEqualTo(itemSize)
     }
 
+    @FlakyTest(bugId = 313465577)
     @Test
     fun row_scrollForwardHalfWay() {
         lateinit var state: TvLazyListState
@@ -326,7 +335,7 @@
             }
         }
 
-        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_LEFT, 3)
+        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_LEFT, 1)
 
         val scrolled = rule.runOnIdle {
             assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
@@ -438,7 +447,7 @@
             }
         }
 
-        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_RIGHT, 3)
+        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_RIGHT, 2)
 
         val scrolled = rule.runOnIdle {
             assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyRowTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyRowTest.kt
index 0817a21..8ae3f7d 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyRowTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyRowTest.kt
@@ -46,6 +46,7 @@
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class LazyRowTest {
+    @Suppress("PrivatePropertyName")
     private val LazyListTag = "LazyListTag"
 
     @get:Rule
@@ -144,7 +145,7 @@
             }
         }
 
-        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_LEFT, 3)
+        rule.keyPress(NativeKeyEvent.KEYCODE_DPAD_LEFT, 2)
 
         rule.runOnIdle {
             assertThat(state.firstVisibleItemIndex).isEqualTo(1)
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
index a39c580..e8223a2 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
@@ -21,6 +21,7 @@
 import android.view.KeyEvent
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -28,13 +29,16 @@
 import androidx.compose.foundation.interaction.collectIsPressedAsState
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -516,7 +520,7 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun clickableSurface_onFocus_changesGlowColor() {
-        rule.setContent {
+        rule.setFocusableContent {
             Surface(
                 modifier = Modifier
                     .testTag("surface")
@@ -926,8 +930,8 @@
     @Test
     fun toggleableSurface_onCheckedChange_changesGlowColor() {
         var isChecked by mutableStateOf(false)
-        var focusManager: FocusManager? = null
-        rule.setContent {
+        lateinit var focusManager: FocusManager
+        rule.setFocusableContent {
             focusManager = LocalFocusManager.current
             Surface(
                 checked = isChecked,
@@ -960,7 +964,7 @@
             .performKeyInput { pressKey(Key.DirectionCenter) }
 
         // Remove focused state to reveal selected state
-        focusManager?.clearFocus()
+        rule.runOnUiThread { focusManager.clearFocus() }
 
         rule.onNodeWithTag("surface")
             .captureToImage()
@@ -971,8 +975,8 @@
     @Test
     fun toggleableSurface_onCheckedChange_changesScaleFactor() {
         var isChecked by mutableStateOf(false)
-        var focusManager: FocusManager? = null
-        rule.setContent {
+        lateinit var focusManager: FocusManager
+        rule.setFocusableContent {
             focusManager = LocalFocusManager.current
             Box(
                 modifier = Modifier
@@ -997,7 +1001,7 @@
             .performKeyInput { pressKey(Key.DirectionCenter) }
 
         // Remove focused state to reveal selected state
-        focusManager?.clearFocus()
+        rule.runOnUiThread { focusManager.clearFocus() }
 
         rule.onRoot().captureToImage().assertDoesNotContainColor(Color.Blue)
     }
@@ -1006,8 +1010,8 @@
     @Test
     fun toggleableSurface_onCheckedChange_showsOutline() {
         var isChecked by mutableStateOf(false)
-        var focusManager: FocusManager? = null
-        rule.setContent {
+        lateinit var focusManager: FocusManager
+        rule.setFocusableContent {
             focusManager = LocalFocusManager.current
             Surface(
                 checked = isChecked,
@@ -1036,7 +1040,9 @@
             .performKeyInput { pressKey(Key.DirectionCenter) }
 
         // Remove focused state to reveal selected state
-        focusManager?.clearFocus()
+        rule.runOnUiThread { focusManager.clearFocus() }
+
+        rule.waitForIdle()
 
         surface.captureToImage().assertContainsColor(Color.Magenta)
     }
@@ -1073,7 +1079,7 @@
         val clickableItemTag = "clickable-item"
         val toggleableItemTag = "toggleable-item"
         val rootElementTag = "root"
-        var focusManager: FocusManager? = null
+        lateinit var focusManager: FocusManager
 
         rule.setContent {
             // arrange
@@ -1147,7 +1153,7 @@
         // blue border shouldn't be visible
         rootEl.captureToImage().assertDoesNotContainColor(Color.Blue)
 
-        focusManager?.moveFocus(FocusDirection.Down)
+        focusManager.moveFocus(FocusDirection.Down)
         rule.waitForIdle()
 
         // blue border should be visible
@@ -1158,7 +1164,7 @@
             .performSemanticsAction(SemanticsActions.OnClick)
         rule.waitForIdle()
 
-        focusManager?.moveFocus(FocusDirection.Up)
+        focusManager.moveFocus(FocusDirection.Up)
         rule.waitForIdle()
 
         // blue border shouldn't be visible
@@ -1207,8 +1213,7 @@
             rule
                 .onNodeWithTag(containerTag)
                 .captureToImage()
-                .toPixelMap(0, 0, 1, 1)
-                .get(0, 0) == Color.White
+                .toPixelMap(0, 0, 1, 1)[0, 0] == Color.White
         )
 
         rule.onNodeWithTag(surfaceTag).requestFocus()
@@ -1219,8 +1224,7 @@
             rule
                 .onNodeWithTag(containerTag)
                 .captureToImage()
-                .toPixelMap(0, 0, 1, 1)
-                .get(0, 0) == Color.Red
+                .toPixelMap(0, 0, 1, 1)[0, 0] == Color.Red
         )
     }
 }
@@ -1258,3 +1262,29 @@
     }
     return this
 }
+
+/**
+ * This function adds a parent composable which has size.
+ * [View.requestFocus()][android.view.View.requestFocus] will not take focus if the view has no
+ * size.
+ *
+ * @param extraItemForInitialFocus Includes an extra item that takes focus initially. This is
+ * useful in cases where we need tests that could be affected by initial focus. Eg. When there is
+ * only one focusable item and we clear focus, that item could end up being focused on again by the
+ * initial focus logic.
+ */
+private fun ComposeContentTestRule.setFocusableContent(
+    extraItemForInitialFocus: Boolean = true,
+    content: @Composable () -> Unit
+) {
+    setContent {
+        if (extraItemForInitialFocus) {
+            Row {
+                Box(modifier = Modifier.requiredSize(10.dp, 10.dp).focusable())
+                Box(modifier = Modifier.requiredSize(100.dp, 100.dp)) { content() }
+            }
+        } else {
+            Box(modifier = Modifier.requiredSize(100.dp, 100.dp)) { content() }
+        }
+    }
+}
diff --git a/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java b/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java
index 2b8df02..2ae01e1 100644
--- a/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java
+++ b/vectordrawable/vectordrawable/src/main/java/androidx/vectordrawable/graphics/drawable/VectorDrawableCompat.java
@@ -918,12 +918,8 @@
 
     // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+.
     private boolean needMirroring() {
-        if (Build.VERSION.SDK_INT >= 17) {
-            return isAutoMirrored()
-                    && DrawableCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
-        } else {
-            return false;
-        }
+        return isAutoMirrored()
+                && DrawableCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
     }
 
     // Extra override functions for delegation for SDK >= 7.
diff --git a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/MutableCollectionBaseActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/MutableCollectionBaseActivity.kt
index 2f3ca9d..46f34c1 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/MutableCollectionBaseActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/MutableCollectionBaseActivity.kt
@@ -16,7 +16,6 @@
 
 package androidx.viewpager2.integration.testapp
 
-import android.os.Build
 import android.os.Bundle
 import android.view.View
 import android.view.ViewGroup
@@ -61,9 +60,7 @@
         itemSpinner.adapter = object : BaseAdapter() {
             override fun getView(position: Int, convertView: View?, parent: ViewGroup): View =
                 ((convertView as TextView?) ?: TextView(parent.context)).apply {
-                    if (Build.VERSION.SDK_INT >= 17) {
-                        textDirection = View.TEXT_DIRECTION_LOCALE
-                    }
+                    textDirection = View.TEXT_DIRECTION_LOCALE
                     text = getItem(position)
                 }
 
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
index 2abc406..9e64aee 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
@@ -57,10 +57,8 @@
             localeUtil.resetLocale()
             localeUtil.setLocale(LocaleTestUtils.RTL_LANGUAGE)
         }
-        if (Build.VERSION.SDK_INT >= 18) {
-            // Make sure accessibility is enabled (side effect of creating a UI Automator instance)
-            uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-        }
+        // Make sure accessibility is enabled (side effect of creating a UI Automator instance)
+        uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
     }
 
     override fun tearDown() {
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index aaa96c9..b340bdf 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -20,7 +20,6 @@
 import android.os.Build
 import android.util.Log
 import android.view.View
-import android.view.View.OVER_SCROLL_NEVER
 import android.view.ViewConfiguration
 import android.view.accessibility.AccessibilityNodeInfo
 import androidx.core.view.ViewCompat
@@ -125,11 +124,6 @@
         }
         onView(withId(R.id.view_pager)).check(matches(isDisplayed()))
 
-        // animations getting in the way on API < 16
-        if (Build.VERSION.SDK_INT < 16) {
-            viewPager.recyclerView.overScrollMode = OVER_SCROLL_NEVER
-        }
-
         return Context(activityTestRule)
     }
 
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/HierarchicalFocusCoordinatorTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/HierarchicalFocusCoordinatorTest.kt
index 53be515..3d55748 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/HierarchicalFocusCoordinatorTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/HierarchicalFocusCoordinatorTest.kt
@@ -170,7 +170,7 @@
         val selected = mutableStateOf(0)
         var focused = false
         rule.setContent {
-            Box {
+            Box(Modifier.focusable()) {
                 HierarchicalFocusCoordinator({ selected.value == 0 }) {
                     FocusableTestItem { focused = it }
                 }
diff --git a/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt b/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt
index 79fd99f..8b9b028 100644
--- a/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt
+++ b/wear/compose/integration-tests/demos/src/androidTest/java/androidx/wear/compose/integration/demos/test/DemoTest.kt
@@ -17,7 +17,6 @@
 package androidx.wear.compose.integration.demos.test
 
 import android.util.Log
-import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsNodeInteractionCollection
 import androidx.compose.ui.test.hasClickAction
 import androidx.compose.ui.test.hasScrollToNodeAction
@@ -47,7 +46,6 @@
 // given the use of ScalingLAZYColumn.
 @LargeTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalTestApi::class)
 class DemoTest {
     // We need to provide the recompose factory first to use new clock.
     @get:Rule
@@ -152,16 +150,18 @@
 
     private fun clearFocusFromDemo() {
         with(rule.activity) {
-            if (hostView.hasFocus()) {
-                if (hostView.isFocused) {
-                    // One of the Compose components has focus.
-                    focusManager.clearFocus(force = true)
-                } else {
-                    // A child view has focus. (View interop scenario).
-                    // We could also use hostViewGroup.focusedChild?.clearFocus(), but the
-                    // interop views might end up being focused if one of them is marked as
-                    // focusedByDefault. So we clear focus by requesting focus on the owner.
-                    rule.runOnUiThread { hostView.requestFocus() }
+            rule.runOnUiThread {
+                if (hostView.hasFocus()) {
+                    if (hostView.isFocused) {
+                        // One of the Compose components has focus.
+                        focusManager.clearFocus(force = true)
+                    } else {
+                        // A child view has focus. (View interop scenario).
+                        // We could also use hostViewGroup.focusedChild?.clearFocus(), but the
+                        // interop views might end up being focused if one of them is marked as
+                        // focusedByDefault. So we clear focus by requesting focus on the owner.
+                        hostView.requestFocus()
+                    }
                 }
             }
         }
@@ -191,13 +191,13 @@
     val newPath = path + this
     return DemoCategory(
         title,
-        demos.mapNotNull {
-            when (it) {
+        demos.mapNotNull { demo ->
+            when (demo) {
                 is DemoCategory -> {
-                    it.filter(newPath, predicate).let { if (it.demos.isEmpty()) null else it }
+                    demo.filter(newPath, predicate).let { if (it.demos.isEmpty()) null else it }
                 }
                 else -> {
-                    if (predicate(newPath, it)) it else null
+                    if (predicate(newPath, demo)) demo else null
                 }
             }
         }
diff --git a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
index da05ab5..08e70ec 100644
--- a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
+++ b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
@@ -29,7 +29,6 @@
 import com.google.android.gms.gcm.Task
 import java.util.concurrent.TimeUnit
 import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.greaterThan
 import org.hamcrest.Matchers.lessThanOrEqualTo
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -210,13 +209,15 @@
 
     @Test
     @SdkSuppress(
-        minSdkVersion = 22, // b/269194015 for minSdkVersion = 22
         maxSdkVersion = WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL
     )
     fun testPeriodicWorkRequest_withFlex_firstRun() {
         val request = PeriodicWorkRequestBuilder<TestWorker>(
             15L, TimeUnit.MINUTES, 5, TimeUnit.MINUTES
         ).build()
+        val now = System.currentTimeMillis()
+        `when`(mTaskConverter.now()).thenReturn(now)
+        request.workSpec.lastEnqueueTime = now
 
         val task = mTaskConverter.convert(request.workSpec)
         assertEquals(task.serviceName, WorkManagerGcmService::class.java.name)
@@ -224,12 +225,12 @@
         assertEquals(task.isUpdateCurrent, true)
         assertEquals(task.requiredNetwork, Task.NETWORK_STATE_ANY)
         assertEquals(task.requiresCharging, false)
-        assertThat(task.windowStart, greaterThan(0L)) // should be in the future
+        // should be period - flex
+        assertEquals(task.windowStart, TimeUnit.MINUTES.toSeconds(10))
     }
 
     @Test
     @SdkSuppress(
-        minSdkVersion = 22, // b/269194015 for minSdkVersion = 22
         maxSdkVersion = WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL
     )
     fun testPeriodicWorkRequest_withFlex_nextRun() {
@@ -241,6 +242,7 @@
         ).build()
 
         request.workSpec.lastEnqueueTime = now
+        request.workSpec.periodCount++
         val expected = TimeUnit.MINUTES.toSeconds(15L)
 
         val task = mTaskConverter.convert(request.workSpec)
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index 72a068d..72d4ff3 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -28,7 +28,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 
@@ -155,9 +154,7 @@
 
     @After
     public void tearDown() {
-        if (Build.VERSION.SDK_INT >= 18) {
-            mHandlerThread.quitSafely();
-        }
+        mHandlerThread.quitSafely();
     }
 
     @Test