Merge "Account for core/layout offset in CursorAnchorInfo" into androidx-main
diff --git a/benchmark/benchmark-macro/api/current.txt b/benchmark/benchmark-macro/api/current.txt
index 7116325..333b233 100644
--- a/benchmark/benchmark-macro/api/current.txt
+++ b/benchmark/benchmark-macro/api/current.txt
@@ -153,6 +153,8 @@
     method public static androidx.benchmark.macro.PowerMetric.Type.Battery Battery();
     method public static androidx.benchmark.macro.PowerMetric.Type.Energy Energy(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
     method public static androidx.benchmark.macro.PowerMetric.Type.Power Power(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
+    method public static boolean deviceBatteryHasMinimumCharge();
+    method public static boolean deviceSupportsPowerEnergy();
     field public static final androidx.benchmark.macro.PowerMetric.Companion Companion;
   }
 
@@ -160,6 +162,8 @@
     method public androidx.benchmark.macro.PowerMetric.Type.Battery Battery();
     method public androidx.benchmark.macro.PowerMetric.Type.Energy Energy(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
     method public androidx.benchmark.macro.PowerMetric.Type.Power Power(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
+    method public boolean deviceBatteryHasMinimumCharge();
+    method public boolean deviceSupportsPowerEnergy();
   }
 
   public abstract static sealed class PowerMetric.Type {
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index 820522c..c3cc53c 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -166,6 +166,8 @@
     method public static androidx.benchmark.macro.PowerMetric.Type.Battery Battery();
     method public static androidx.benchmark.macro.PowerMetric.Type.Energy Energy(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
     method public static androidx.benchmark.macro.PowerMetric.Type.Power Power(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
+    method public static boolean deviceBatteryHasMinimumCharge();
+    method public static boolean deviceSupportsPowerEnergy();
     field public static final androidx.benchmark.macro.PowerMetric.Companion Companion;
   }
 
@@ -173,6 +175,8 @@
     method public androidx.benchmark.macro.PowerMetric.Type.Battery Battery();
     method public androidx.benchmark.macro.PowerMetric.Type.Energy Energy(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
     method public androidx.benchmark.macro.PowerMetric.Type.Power Power(optional java.util.Map<androidx.benchmark.macro.PowerCategory,? extends androidx.benchmark.macro.PowerCategoryDisplayLevel> categories);
+    method public boolean deviceBatteryHasMinimumCharge();
+    method public boolean deviceSupportsPowerEnergy();
   }
 
   public abstract static sealed class PowerMetric.Type {
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
index b47435e..f5b8ee3 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
@@ -16,15 +16,15 @@
 
 package androidx.benchmark.macro
 
-import android.os.Build
-import androidx.annotation.RequiresApi
 import androidx.benchmark.macro.Metric.Measurement
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
 import androidx.benchmark.perfetto.PerfettoTraceProcessor
+import androidx.test.filters.SdkSuppress
 import kotlin.test.assertEquals
 import org.junit.Assume.assumeTrue
 import org.junit.Test
 
+@SdkSuppress(minSdkVersion = 29)
 @OptIn(ExperimentalMetricApi::class)
 class PowerMetricTest {
     private val captureInfo = Metric.CaptureInfo(
@@ -34,7 +34,6 @@
         StartupMode.COLD
     )
 
-    @RequiresApi(Build.VERSION_CODES.Q)
     @Test
     fun successfulFixedTraceEnergyBreakdown() {
         assumeTrue(isAbiSupported())
@@ -72,7 +71,6 @@
         )
     }
 
-    @RequiresApi(Build.VERSION_CODES.Q)
     @Test
     fun successfulFixedTracePowerTotal() {
         assumeTrue(isAbiSupported())
@@ -101,7 +99,6 @@
         )
     }
 
-    @RequiresApi(Build.VERSION_CODES.Q)
     @Test
     fun successfulFixedTracePowerMix() {
         assumeTrue(isAbiSupported())
@@ -134,7 +131,6 @@
         )
     }
 
-    @RequiresApi(Build.VERSION_CODES.Q)
     @Test
     fun emptyFixedTrace() {
         assumeTrue(isAbiSupported())
@@ -150,7 +146,7 @@
         assertEquals(emptyList(), actualMetrics)
     }
 
-    @RequiresApi(Build.VERSION_CODES.Q)
+    @SdkSuppress(minSdkVersion = 29)
     @Test
     fun successfulFixedTraceBatteryDischarge() {
         assumeTrue(isAbiSupported())
@@ -171,4 +167,20 @@
             threshold = 0.1
         )
     }
+
+    @Test
+    fun deviceSupportsPowerEnergy() {
+        assertEquals(
+            PowerRail.hasMetrics(throwOnMissingMetrics = false),
+            PowerMetric.deviceSupportsPowerEnergy()
+        )
+    }
+
+    @Test
+    fun deviceBatteryHasMinimumCharge() {
+        assertEquals(
+            BatteryCharge.hasMinimumCharge(throwOnMissingMetrics = false),
+            PowerMetric.deviceBatteryHasMinimumCharge()
+        )
+    }
 }
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerRailTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerRailTest.kt
index 0d729e3..6f714b2 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerRailTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerRailTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
@@ -30,12 +31,14 @@
 @SmallTest
 class PowerRailTest {
 
+    @SdkSuppress(minSdkVersion = 32)
     @Test
     fun hasMetrics_Pixel6() {
-        assumeTrue(Build.VERSION.SDK_INT > 31 && Build.MODEL.lowercase() == "oriole")
+        assumeTrue(Build.MODEL.lowercase() == "oriole")
 
         assertTrue(PowerRail.hasMetrics(throwOnMissingMetrics = true))
         assertTrue(PowerRail.hasMetrics(throwOnMissingMetrics = false))
+        assertTrue(PowerMetric.deviceSupportsPowerEnergy())
     }
 
     @Test
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
index 6437ad16..1f96839 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -712,6 +712,47 @@
         ): Type.Power {
             return Type.Power(categories)
         }
+
+        /**
+         * Returns true if the current device can be used for power/energy metrics.
+         *
+         * This can be used to change behavior or fall back to lower precision tracking:
+         *
+         * ```
+         * metrics = listOf(
+         *     if (PowerMetric.deviceSupportsPowerEnergy()) {
+         *         PowerMetric(Type.Energy()) // high precision tracking
+         *     } else {
+         *         PowerMetric(Type.Battery()) // fall back to less precise tracking
+         *     }
+         * )
+         * ```
+         *
+         * Or to skip a test when detailed tracking isn't available:
+         * ```
+         * @Test fun myDetailedPowerBenchmark {
+         *     assumeTrue(PowerMetric.deviceSupportsPowerEnergy())
+         *     macrobenchmarkRule.measureRepeated (
+         *         metrics = listOf(PowerMetric(Type.Energy(...)))
+         *     ) {
+         *         ...
+         *     }
+         * }
+         * ```
+         */
+        @JvmStatic
+        fun deviceSupportsPowerEnergy(): Boolean = hasMetrics(throwOnMissingMetrics = false)
+
+        /**
+         * Returns true if [Type.Battery] measurements can be performed, based on current device
+         * charge.
+         *
+         * This can be used to change behavior or throw a clear error before metric configuration,
+         * or to skip the test, e.g. with `assumeTrue(PowerMetric.deviceBatteryHasMinimumCharge())`
+         */
+        @JvmStatic
+        fun deviceBatteryHasMinimumCharge(): Boolean =
+            hasMinimumCharge(throwOnMissingMetrics = false)
     }
 
     /**
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/PowerRail.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/PowerRail.kt
index 1013b84..ecaf2ad 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/PowerRail.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/PowerRail.kt
@@ -65,7 +65,7 @@
 
                 adb shell $DUMPSYS_POWERSTATS
 
-                To check at runtime for this, use PowerRail.hasMetrics()
+                To check at runtime for this, use PowerMetric.deviceSupportsPowerEnergy()
 
                 """.trimIndent()
             )
diff --git a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/PathEasingTest.kt b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/PathEasingTest.kt
index 0f8a79b..5d7d570 100644
--- a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/PathEasingTest.kt
+++ b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/PathEasingTest.kt
@@ -167,4 +167,20 @@
             previousFraction = newFraction
         }
     }
+
+    @Test
+    fun pathEasing_Overshoots() {
+        val path = Path().apply {
+            cubicTo(0.34f, 1.56f, 0.64f, 1.0f, 1.0f, 1.0f)
+        }
+        assertThat(PathEasing(path).transform(0.6f)).isGreaterThan(1.0f)
+    }
+
+    @Test
+    fun pathEasing_Undershoots() {
+        val path = Path().apply {
+            cubicTo(0.68f, -0.6f, 0.32f, 1.6f, 1.0f, 1.0f)
+        }
+        assertThat(PathEasing(path).transform(0.1f)).isLessThan(0.0f)
+    }
 }
diff --git a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/EasingTest.kt b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/EasingTest.kt
index eb5c46e..beea6dbd 100644
--- a/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/EasingTest.kt
+++ b/compose/animation/animation-core/src/androidUnitTest/kotlin/androidx/compose/animation/core/EasingTest.kt
@@ -28,8 +28,8 @@
 
 @RunWith(JUnit4::class)
 class EasingTest {
-    val ZeroEpsilon = -(1.0f.ulp)
-    val OneEpsilon = 1.0f + 1.0f.ulp
+    private val ZeroEpsilon = -(1.0f.ulp)
+    private val OneEpsilon = 1.0f + 1.0f.ulp
 
     @Test
     fun cubicBezierStartsAt0() {
@@ -40,7 +40,19 @@
     @Test
     fun cubicBezierEndsAt1() {
         val easing = FastOutLinearInEasing
-        assertThat(easing.transform(1f) == 1f)
+        assertThat(easing.transform(1f)).isEqualTo(1.0f)
+    }
+
+    @Test
+    fun cubicBezierDoesntExceed1() {
+        val easing = CubicBezierEasing(0f, 0f, 0.15f, 1f)
+        assertThat(easing.transform(0.999999f) <= 1.0f).isTrue()
+    }
+
+    @Test
+    fun cubicBezierDoesExceed1() {
+        val easing = CubicBezierEasing(0.34f, 1.56f, 0.64f, 1.0f)
+        assertThat(easing.transform(0.6f)).isGreaterThan(1.0f)
     }
 
     @Test
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
index 51dbbbf..f69cb99 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Easing.kt
@@ -18,8 +18,10 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
+import androidx.compose.ui.graphics.computeCubicVerticalBounds
 import androidx.compose.ui.graphics.evaluateCubic
 import androidx.compose.ui.graphics.findFirstCubicRoot
+import androidx.compose.ui.util.fastCoerceIn
 
 /**
  * Easing is a way to adjust an animation’s fraction. Easing allows transitioning
@@ -106,10 +108,17 @@
     private val c: Float,
     private val d: Float
 ) : Easing {
+    private val min: Float
+    private val max: Float
+
     init {
         requirePrecondition(!a.isNaN() && !b.isNaN() && !c.isNaN() && !d.isNaN()) {
             "Parameters to CubicBezierEasing cannot be NaN. Actual parameters are: $a, $b, $c, $d."
         }
+        val roots = FloatArray(5)
+        val extrema = computeCubicVerticalBounds(0.0f, b, d, 1.0f, roots, 0)
+        min = extrema.first
+        max = extrema.second
     }
 
     /**
@@ -131,20 +140,24 @@
 
             // No root, the cubic curve has no solution
             if (t.isNaN()) {
-                throw IllegalArgumentException(
-                    "The cubic curve with parameters ($a, $b, $c, $d) has no solution at $fraction"
-                )
+                throwNoSolution(fraction)
             }
 
             // Don't clamp the values since the curve might be used to over- or under-shoot
             // The test above that checks if fraction is in ]0..1[ will ensure we start and
             // end at 0 and 1 respectively
-            evaluateCubic(b, d, t)
+            evaluateCubic(b, d, t).fastCoerceIn(min, max)
         } else {
             fraction
         }
     }
 
+    private fun throwNoSolution(fraction: Float) {
+        throw IllegalArgumentException(
+            "The cubic curve with parameters ($a, $b, $c, $d) has no solution at $fraction"
+        )
+    }
+
     override fun equals(other: Any?): Boolean {
         return other is CubicBezierEasing && a == other.a && b == other.b && c == other.c &&
             d == other.d
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt
index 43446776..9aac1bb 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/PathEasing.kt
@@ -24,7 +24,6 @@
 import androidx.compose.ui.graphics.computeHorizontalBounds
 import androidx.compose.ui.graphics.evaluateY
 import androidx.compose.ui.graphics.findFirstRoot
-import androidx.compose.ui.util.fastCoerceIn
 
 /**
  * An easing function for an arbitrary [Path].
@@ -66,39 +65,7 @@
         }
 
         if (!::intervals.isInitialized) {
-            val roots = FloatArray(5)
-
-            // Using an interval tree is a bit heavy handed but since we are dealing with
-            // easing curves, we don't expect many segments, and therefore few allocations.
-            // The interval tree allows us to quickly query for the correct segment inside
-            // the transform() function.
-            val segmentIntervals = IntervalTree<PathSegment>().apply {
-                // A path easing curve is defined in the domain 0..1, use an error
-                // appropriate for this domain (the default is 0.25). Conic segments
-                // should be unlikely in path easing curves, but just in case...
-                val iterator = path.iterator(
-                    PathIterator.ConicEvaluation.AsQuadratics,
-                    2e-4f
-                )
-                while (iterator.hasNext()) {
-                    val segment = iterator.next()
-                    requirePrecondition(segment.type != PathSegment.Type.Close) {
-                        "The path cannot contain a close() command."
-                    }
-                    if (segment.type != PathSegment.Type.Move &&
-                        segment.type != PathSegment.Type.Done
-                    ) {
-                        val bounds = computeHorizontalBounds(segment, roots)
-                        addInterval(bounds.first, bounds.second, segment)
-                    }
-                }
-            }
-
-            requirePrecondition(0.0f in segmentIntervals && 1.0f in segmentIntervals) {
-                "The easing path must start at 0.0f and end at 1.0f."
-            }
-
-            intervals = segmentIntervals
+            initializeEasing()
         }
 
         val result = intervals.findFirstOverlap(fraction)
@@ -111,6 +78,42 @@
             "The easing path is invalid. Make sure it does not contain NaN/Infinity values."
         }
 
-        return evaluateY(segment, t).fastCoerceIn(0.0f, 1.0f)
+        return evaluateY(segment, t)
+    }
+
+    private fun initializeEasing() {
+        val roots = FloatArray(5)
+
+        // Using an interval tree is a bit heavy handed but since we are dealing with
+        // easing curves, we don't expect many segments, and therefore few allocations.
+        // The interval tree allows us to quickly query for the correct segment inside
+        // the transform() function.
+        val segmentIntervals = IntervalTree<PathSegment>().apply {
+            // A path easing curve is defined in the domain 0..1, use an error
+            // appropriate for this domain (the default is 0.25). Conic segments
+            // should be unlikely in path easing curves, but just in case...
+            val iterator = path.iterator(
+                PathIterator.ConicEvaluation.AsQuadratics,
+                2e-4f
+            )
+            while (iterator.hasNext()) {
+                val segment = iterator.next()
+                requirePrecondition(segment.type != PathSegment.Type.Close) {
+                    "The path cannot contain a close() command."
+                }
+                if (segment.type != PathSegment.Type.Move &&
+                    segment.type != PathSegment.Type.Done
+                ) {
+                    val bounds = computeHorizontalBounds(segment, roots)
+                    addInterval(bounds.first, bounds.second, segment)
+                }
+            }
+        }
+
+        requirePrecondition(0.0f in segmentIntervals && 1.0f in segmentIntervals) {
+            "The easing path must start at 0.0f and end at 1.0f."
+        }
+
+        intervals = segmentIntervals
     }
 }
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt
index 012d184..a268210 100644
--- a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/SharedTransitionTest.kt
@@ -29,16 +29,20 @@
 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.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Text
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
@@ -67,6 +71,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
@@ -79,6 +84,7 @@
 import kotlin.math.roundToInt
 import kotlin.math.sqrt
 import kotlin.random.Random
+import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -2569,6 +2575,139 @@
         isSquare = !isSquare
         rule.waitForIdle()
     }
+
+    sealed class Screen {
+        abstract val key: Int
+
+        object List : Screen() {
+            override val key = 1
+        }
+
+        data class Details(val item: Int) : Screen() {
+            override val key = 2
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun testRandomScrollingWithInterruption() {
+        @Suppress("PrimitiveInCollection")
+        val colors = listOf(
+            Color(0xffff6f69),
+            Color(0xffffcc5c),
+            Color(0xff2a9d84),
+            Color(0xff264653)
+        )
+        var state by mutableStateOf<Screen>(Screen.List)
+        val lazyListState = LazyListState()
+        rule.setContent {
+            SharedTransitionLayout(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .height(800.dp)
+            ) {
+                AnimatedContent(state, label = "", contentKey = { it.key },
+                    transitionSpec = {
+                        if (initialState == Screen.List) {
+                            slideInHorizontally { -it } + fadeIn() togetherWith
+                                slideOutHorizontally { it } + fadeOut()
+                        } else {
+                            slideInHorizontally { it } + fadeIn() togetherWith
+                                slideOutHorizontally { -it } + fadeOut()
+                        }
+                    }) {
+                    when (it) {
+                        Screen.List -> {
+                            LazyColumn(state = lazyListState) {
+                                items(50) { item ->
+                                    Row(modifier = Modifier.fillMaxWidth()) {
+                                        Box(
+                                            modifier = Modifier
+                                                .size(100.dp)
+                                                .then(
+                                                    Modifier.sharedElement(
+                                                        rememberSharedContentState(
+                                                            key = "item-image$item"
+                                                        ),
+                                                        this@AnimatedContent,
+                                                    )
+                                                )
+                                                .background(colors[item % 4]),
+                                        )
+                                        Spacer(Modifier.size(15.dp))
+                                        Text("Item $item")
+                                    }
+                                }
+                            }
+                        }
+
+                        is Screen.Details -> {
+                            val item = it.item
+                            Column(modifier = Modifier.fillMaxSize()) {
+                                Box(
+                                    modifier = Modifier
+                                        .sharedElement(
+                                            rememberSharedContentState(key = "item-image$item"),
+                                            this@AnimatedContent,
+                                        )
+                                        .background(colors[item % 4])
+                                        .fillMaxWidth(),
+                                )
+                                Text(
+                                    "Item $item",
+                                    fontSize = 23.sp
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        state = Screen.Details(5)
+        repeat(10) {
+            rule.waitForIdle()
+            rule.mainClock.advanceTimeByFrame()
+        }
+        state = Screen.List
+        repeat(3) {
+            rule.waitForIdle()
+            rule.mainClock.advanceTimeByFrame()
+        }
+
+        repeat(3) {
+            repeat(5) {
+                rule.runOnIdle {
+                    runBlocking {
+                        lazyListState.scrollToItem(it * 10)
+                    }
+                }
+            }
+            rule.mainClock.advanceTimeByFrame()
+            repeat(5) {
+                rule.runOnIdle {
+                    runBlocking {
+                        lazyListState.scrollToItem(40 - it * 10)
+                    }
+                }
+            }
+            rule.mainClock.advanceTimeByFrame()
+            repeat(10) {
+                rule.runOnIdle {
+                    runBlocking {
+                        lazyListState.scrollToItem(it * 5)
+                    }
+                }
+            }
+            rule.mainClock.advanceTimeByFrame()
+            rule.runOnIdle {
+                runBlocking {
+                    lazyListState.scrollToItem(0)
+                }
+            }
+        }
+    }
 }
 
 private fun assertEquals(a: IntSize, b: IntSize, delta: IntSize) {
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt
index e8913d0..62b5f02 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedContentNode.kt
@@ -42,6 +42,7 @@
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.requireDensity
 import androidx.compose.ui.node.requireGraphicsContext
+import androidx.compose.ui.node.requireLayoutCoordinates
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
@@ -87,23 +88,18 @@
                     provide(ModifierLocalSharedElementInternalState, value)
                     state.parentState = ModifierLocalSharedElementInternalState.current
                     state.layer = layer
-                    state.lookaheadCoords = lookaheadCoords
-                    state.lookaheadSize = lookaheadSize
+                    state.lookaheadCoords = { requireLookaheadLayoutCoordinates() }
                 }
             }
         }
 
-    private var lookaheadCoords: LayoutCoordinates? = state.lookaheadCoords
-        set(value) {
-            state.lookaheadCoords = value
-            field = value
+    @OptIn(ExperimentalComposeUiApi::class)
+    private fun requireLookaheadLayoutCoordinates(): LayoutCoordinates =
+        with(state.sharedElement.scope) {
+            requireLayoutCoordinates().toLookaheadCoordinates()
         }
+
     private val boundsAnimation: BoundsAnimation get() = state.boundsAnimation
-    private var lookaheadSize: Size? = state.lookaheadSize
-        set(value) {
-            state.lookaheadSize = value
-            field = value
-        }
 
     private var layer: GraphicsLayer? = state.layer
         set(value) {
@@ -126,13 +122,21 @@
         provide(ModifierLocalSharedElementInternalState, state)
         state.parentState = ModifierLocalSharedElementInternalState.current
         layer = requireGraphicsContext().createGraphicsLayer()
+        state.lookaheadCoords = { requireLookaheadLayoutCoordinates() }
     }
 
     override fun onDetach() {
         super.onDetach()
         layer = null
         state.parentState = null
-        lookaheadCoords = null
+        state.lookaheadCoords = { null }
+    }
+
+    override fun onReset() {
+        super.onReset()
+        // Reset layer
+        layer?.let { requireGraphicsContext().releaseGraphicsLayer(it) }
+        layer = requireGraphicsContext().createGraphicsLayer()
     }
 
     override fun MeasureScope.measure(
@@ -141,17 +145,14 @@
     ): MeasureResult {
         // Lookahead pass: Record lookahead size and lookahead coordinates
         val placeable = measurable.measure(constraints)
-        lookaheadSize = Size(placeable.width.toFloat(), placeable.height.toFloat())
+        val lookaheadSize = Size(placeable.width.toFloat(), placeable.height.toFloat())
         return layout(placeable.width, placeable.height) {
             val topLeft = coordinates?.let {
-                lookaheadCoords = it
                 rootLookaheadCoords.localPositionOf(it, Offset.Zero).also { topLeft ->
                     if (sharedElement.currentBounds == null) {
                         sharedElement.currentBounds = Rect(
                             topLeft,
-                            requireNotNull(lookaheadSize) {
-                                "Error: Lookahead measure has not happened."
-                            }
+                            lookaheadSize
                         )
                     }
                 }
@@ -160,14 +161,14 @@
             // Update the lookahead result after child placement, so that child has an
             // opportunity to use its placement to influence the bounds animation.
             topLeft?.let {
-                sharedElement.onLookaheadResult(state, lookaheadSize!!, it)
+                sharedElement.onLookaheadResult(state, lookaheadSize, it)
             }
         }
     }
 
     private fun MeasureScope.place(placeable: Placeable): MeasureResult {
         val (w, h) = state.placeHolderSize.calculateSize(
-            lookaheadSize!!.roundToIntSize(),
+            requireLookaheadLayoutCoordinates().size,
             IntSize(placeable.width, placeable.height)
         )
         return layout(w, h) {
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt
index 13841b8..10d63ec 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SharedElement.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.layer.drawLayer
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachReversed
@@ -55,9 +56,7 @@
     val targetBounds: Rect?
         get() {
             _targetBounds = targetBoundsProvider?.run {
-                Rect(calculateLookaheadOffset(), requireNotNull(lookaheadSize) {
-                    "Error: target has not been lookahead measured."
-                })
+                Rect(calculateLookaheadOffset(), nonNullLookaheadSize)
             }
             return _targetBounds
         }
@@ -196,17 +195,15 @@
     var overlayClip: SharedTransitionScope.OverlayClip by mutableStateOf(overlayClip)
     var userState: SharedTransitionScope.SharedContentState by mutableStateOf(userState)
 
-    init {
-        sharedElement.scope.onStateAdded(this)
-        sharedElement.updateTargetBoundsProvider()
-    }
-
     internal var clipPathInOverlay: Path? = null
 
     override fun drawInOverlay(drawScope: DrawScope) {
         val layer = layer ?: return
         if (shouldRenderInOverlay) {
             with(drawScope) {
+                requireNotNull(sharedElement.currentBounds) {
+                    "Error: current bounds not set yet."
+                }
                 val (x, y) = sharedElement.currentBounds?.topLeft!!
                 clipPathInOverlay?.let {
                     clipPath(it) {
@@ -219,21 +216,26 @@
         }
     }
 
-    var lookaheadSize: Size? = null
-    var lookaheadCoords: LayoutCoordinates? = null
+    val nonNullLookaheadSize: Size
+        get() = requireNotNull(lookaheadCoords()) {
+            "Error: lookahead coordinates is null."
+        }.size.toSize()
+    var lookaheadCoords: () -> LayoutCoordinates? = { null }
     override var parentState: SharedElementInternalState? = null
 
     // This can only be accessed during placement
     fun calculateLookaheadOffset(): Offset {
-        val c = requireNotNull(lookaheadCoords) {
-            "Error: target has not been placed in lookahead pass yet."
+        val c = requireNotNull(lookaheadCoords()) {
+            "Error: lookahead coordinates is null."
         }
         return sharedElement.scope.lookaheadRoot.localPositionOf(c, Offset.Zero)
     }
 
     val target: Boolean get() = boundsAnimation.target
 
-    var layer: GraphicsLayer? = null
+    // Delegate the property to a mutable state, so that when layer is updated, the rendering
+    // gets invalidated.
+    var layer: GraphicsLayer? by mutableStateOf(null)
 
     private val shouldRenderBasedOnTarget: Boolean
         get() = sharedElement.targetBoundsProvider == this || !renderOnlyWhenVisible
@@ -246,6 +248,8 @@
         get() = !sharedElement.foundMatch || (!shouldRenderInOverlay && shouldRenderBasedOnTarget)
 
     override fun onRemembered() {
+        sharedElement.scope.onStateAdded(this)
+        sharedElement.updateTargetBoundsProvider()
     }
 
     override fun onForgotten() {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
index 6d1df04..b5227f8 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
@@ -105,13 +105,12 @@
             }
         }
 
-        if (pluginContext.platform.isNative() &&
-            descriptorSerializerContext?.hideFromObjCDeclarationsSet != null) {
+        if (pluginContext.platform.isNative()) {
             AddHiddenFromObjCLowering(
                 pluginContext,
                 symbolRemapper,
                 metrics,
-                descriptorSerializerContext.hideFromObjCDeclarationsSet,
+                descriptorSerializerContext?.hideFromObjCDeclarationsSet,
                 stabilityInferencer
             ).lower(moduleFragment)
         }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt
index c73f49b..037b408 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt
@@ -40,6 +40,7 @@
 import org.jetbrains.kotlin.ir.types.IrType
 import org.jetbrains.kotlin.ir.types.IrTypeArgument
 import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
+import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper
 import org.jetbrains.kotlin.ir.util.DeepCopyTypeRemapper
 import org.jetbrains.kotlin.ir.util.IdSignature
 import org.jetbrains.kotlin.ir.util.SymbolRenamer
@@ -206,7 +207,17 @@
     source: IrFunction,
     target: IrFunction
 ): T {
-    return deepCopyWithSymbols(target) { symbolRemapper, typeRemapper ->
+    val typeParamsAwareSymbolRemapper = object : DeepCopySymbolRemapper() {
+        init {
+            for ((orig, new) in source.typeParameters.zip(target.typeParameters)) {
+                typeParameters[orig.symbol] = new.symbol
+            }
+        }
+    }
+    return deepCopyWithSymbols(
+        target,
+        typeParamsAwareSymbolRemapper
+    ) { symbolRemapper, typeRemapper ->
         val typeParamRemapper = object : TypeRemapper by typeRemapper {
             override fun remapType(type: IrType): IrType {
                 return typeRemapper.remapType(type.remapTypeParameters(source, target))
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCLowering.kt
index d749568..648bf66 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCLowering.kt
@@ -23,8 +23,11 @@
 import androidx.compose.compiler.plugins.kotlin.lower.containsComposableAnnotation
 import androidx.compose.compiler.plugins.kotlin.lower.needsComposableRemapping
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
+import org.jetbrains.kotlin.backend.common.pop
+import org.jetbrains.kotlin.backend.common.push
 import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
 import org.jetbrains.kotlin.ir.IrStatement
+import org.jetbrains.kotlin.ir.declarations.IrClass
 import org.jetbrains.kotlin.ir.declarations.IrDeclaration
 import org.jetbrains.kotlin.ir.declarations.IrFunction
 import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -41,13 +44,13 @@
 /**
  *  AddHiddenFromObjCLowering looks for functions and properties with @Composable types and
  *  adds the `kotlin.native.HiddenFromObjC` annotation to them.
- *  @see https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native/-hidden-from-obj-c/
+ *  [docs](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native/-hidden-from-obj-c/)
  */
 class AddHiddenFromObjCLowering(
     private val pluginContext: IrPluginContext,
     symbolRemapper: ComposableSymbolRemapper,
     metrics: ModuleMetrics,
-    private val hideFromObjCDeclarationsSet: HideFromObjCDeclarationsSet,
+    private val hideFromObjCDeclarationsSet: HideFromObjCDeclarationsSet?,
     stabilityInferencer: StabilityInferencer,
 ) : AbstractComposeLowering(pluginContext, symbolRemapper, metrics, stabilityInferencer) {
 
@@ -55,6 +58,9 @@
         getTopLevelClass(ClassId.fromString("kotlin/native/HiddenFromObjC"))
     }
 
+    private val shouldAnnotateClass = ArrayDeque<Boolean>()
+    private var currentShouldAnnotateClass = false
+
     override fun lower(module: IrModuleFragment) {
         require(context.platform.isNative()) {
             "AddHiddenFromObjCLowering is expected to run only for k/native. " +
@@ -63,6 +69,27 @@
         module.transformChildrenVoid(this)
     }
 
+    /** `visitClass` is only needed until [issue](https://youtrack.jetbrains.com/issue/KT-65288/) fix
+     * after the issue is resolved, `visitClass` could be removed entirely
+     */
+    override fun visitClass(declaration: IrClass): IrStatement {
+        shouldAnnotateClass.push(currentShouldAnnotateClass)
+        currentShouldAnnotateClass = false
+
+        val cls = super.visitClass(declaration) as IrClass
+
+        // We see an issue only with data classes containing something Composable.
+        // Adding an annotation to all classes makes the FirNativeHiddenFromObjCInheritanceChecker (kotlin) complain.
+        // data classes can't be open, so it should work.
+        if (currentShouldAnnotateClass && cls.isData) {
+            cls.addHiddenFromObjCAnnotation()
+            hideFromObjCDeclarationsSet?.add(cls)
+        }
+
+        currentShouldAnnotateClass = shouldAnnotateClass.pop()
+        return cls
+    }
+
     override fun visitFunction(declaration: IrFunction): IrStatement {
         val f = super.visitFunction(declaration) as IrFunction
         if (f.isLocal ||
@@ -72,7 +99,8 @@
 
         if (f.hasComposableAnnotation() || f.needsComposableRemapping()) {
             f.addHiddenFromObjCAnnotation()
-            hideFromObjCDeclarationsSet.add(f)
+            hideFromObjCDeclarationsSet?.add(f)
+            currentShouldAnnotateClass = true
         }
 
         return f
@@ -88,7 +116,8 @@
 
         if (shouldAdd) {
             p.addHiddenFromObjCAnnotation()
-            hideFromObjCDeclarationsSet.add(p)
+            hideFromObjCDeclarationsSet?.add(p)
+            currentShouldAnnotateClass = true
         }
 
         return p
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCSerializationPlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCSerializationPlugin.kt
index 62e06a8..86b897f 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCSerializationPlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCSerializationPlugin.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.compiler.plugins.kotlin.lower.hiddenfromobjc
 
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
 import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
 import org.jetbrains.kotlin.descriptors.FunctionDescriptor
 import org.jetbrains.kotlin.descriptors.PropertyDescriptor
@@ -47,6 +48,20 @@
             id = extension.stringTable.getQualifiedClassNameIndex(annotationToAdd)
         }.build()
 
+    override fun afterClass(
+        descriptor: ClassDescriptor,
+        proto: ProtoBuf.Class.Builder,
+        versionRequirementTable: MutableVersionRequirementTable,
+        childSerializer: DescriptorSerializer,
+        extension: SerializerExtension
+    ) {
+        if (descriptor in hideFromObjCDeclarationsSet) {
+            val annotationProto = createAnnotationProto(extension)
+            proto.addExtension(KlibMetadataSerializerProtocol.classAnnotation, annotationProto)
+            proto.flags = proto.flags or hasAnnotationFlag
+        }
+    }
+
     override fun afterConstructor(
         descriptor: ConstructorDescriptor,
         proto: ProtoBuf.Constructor.Builder,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/HideFromObjCDeclarationsSet.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/HideFromObjCDeclarationsSet.kt
index ec2b064..5495671 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/HideFromObjCDeclarationsSet.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/HideFromObjCDeclarationsSet.kt
@@ -18,6 +18,7 @@
 
 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
 import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
+import org.jetbrains.kotlin.ir.declarations.IrClass
 import org.jetbrains.kotlin.ir.declarations.IrFunction
 import org.jetbrains.kotlin.ir.declarations.IrProperty
 import org.jetbrains.kotlin.name.FqName
@@ -51,6 +52,10 @@
         set.add(property.descriptor.fqNameSafe)
     }
 
+    fun add(cls: IrClass) {
+        set.add(cls.descriptor.fqNameSafe)
+    }
+
     operator fun contains(item: DeclarationDescriptor): Boolean {
         return set.contains(item.fqNameSafe)
     }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
index 61c2ef9..03845b8 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
@@ -54,6 +54,7 @@
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.abs
+import kotlin.math.roundToInt
 import kotlinx.coroutines.runBlocking
 import org.junit.Before
 import org.junit.Rule
@@ -316,6 +317,46 @@
         }
     }
 
+    @Test
+    fun itemOutsideOfViewPortBeingAnimatedIn_shouldBePlacedAtTheEndOfList() {
+        var list by mutableStateOf(
+            listOf(
+                Color.Black,
+                Color.Green,
+                Color.Blue,
+                Color.Yellow,
+                Color.DarkGray
+            )
+        )
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp * 2.5f) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            // item 0 will leave, item 3 will pop up
+            list = listOf(Color.Green, Color.Blue, Color.Yellow, Color.DarkGray)
+        }
+
+        onAnimationFrame { fraction ->
+            if (fraction.isCloseTo(0.5f)) {
+                assertPixels((itemSize * 2.5f).roundToInt()) { offset ->
+                    when (offset) {
+                        // green item is first
+                        in 0 until itemSize -> Color.Green
+                        // blue item is second
+                        in itemSize until 2 * itemSize -> Color.Blue
+                        // yellow item pops up at the bottom
+                        else -> Color.Yellow
+                    }
+                }
+            }
+        }
+    }
+
     private fun assertPixels(
         mainAxisSize: Int,
         crossAxisSize: Int = this.crossAxisSize,
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
index 7ea574b..b90192b 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
@@ -1001,6 +1001,50 @@
     }
 
     @Test
+    fun removingItemsCauseOutOfBoundsItemToPopUp_withContentPadding() {
+        var list by mutableStateOf(listOf(0, 1, 2, 3, 4))
+        val rawStartPadding = 8f
+        val rawEndPadding = 12f
+        val (startPaddingDp, endPaddingDp) = with(rule.density) {
+            rawStartPadding.toDp() to rawEndPadding.toDp()
+        }
+        rule.setContent {
+            // only 4 items will be visible 0, 1, 2, 3
+            LazyList(
+                maxSize = itemSizeDp * 4,
+                startPadding = startPaddingDp,
+                endPadding = endPaddingDp
+            ) {
+                items(list, key = { it }) {
+                    Item(it)
+                }
+            }
+        }
+
+        val startPadding = if (reverseLayout) rawEndPadding else rawStartPadding
+        assertPositions(
+            0 to startPadding,
+            1 to startPadding + itemSize,
+            2 to startPadding + itemSize * 2,
+            3 to startPadding + itemSize * 3
+        )
+
+        rule.runOnUiThread {
+            list = listOf(1, 2, 3, 4)
+        }
+
+        onAnimationFrame { fraction ->
+            assertPositions(
+                1 to startPadding + itemSize - itemSize * fraction,
+                2 to startPadding + itemSize * 2 - itemSize * fraction,
+                3 to startPadding + itemSize * 3 - itemSize * fraction,
+                4 to startPadding + itemSize * 4 - itemSize * fraction,
+                fraction = fraction
+            )
+        }
+    }
+
+    @Test
     fun reorderFirstAndLastItems_noNewLayoutInfoProduced() {
         var list by mutableStateOf(listOf(0, 1, 2, 3, 4))
 
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 b67d4b3..ef97634 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
@@ -163,7 +163,7 @@
     @Test
     fun longClickSemantics() {
         var counter = 0
-        val onClick: () -> Unit = { ++counter }
+        val onLongClick: () -> Unit = { ++counter }
 
         rule.setContent {
             Box {
@@ -171,7 +171,7 @@
                     "ClickableText",
                     modifier = Modifier
                         .testTag("myClickable")
-                        .combinedClickable(onLongClick = onClick) {}
+                        .combinedClickable(onLongClick = onLongClick) {}
                 )
             }
         }
@@ -193,6 +193,59 @@
     }
 
     @Test
+    fun changingLongClickSemantics() {
+        var counter = 0
+        var onLongClick: (() -> Unit)? by mutableStateOf(null)
+
+        rule.setContent {
+            Box {
+                BasicText(
+                    "ClickableText",
+                    modifier = Modifier
+                        .testTag("myClickable")
+                        .combinedClickable(onLongClick = onLongClick) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .assertIsEnabled()
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.OnLongClick))
+
+        rule.runOnIdle {
+            // Add a no-op long click
+            onLongClick = { /* no-op */ }
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .assertIsEnabled()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+            .performSemanticsAction(SemanticsActions.OnLongClick)
+
+        rule.runOnIdle {
+            // no-op long click handler
+            assertThat(counter).isEqualTo(0)
+            // Change to mutate counter
+            onLongClick = { ++counter }
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performSemanticsAction(SemanticsActions.OnLongClick)
+
+        rule.runOnIdle {
+            // Changes should now be applied
+            assertThat(counter).isEqualTo(1)
+            // Make onLongClick null
+            onLongClick = null
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .assertIsEnabled()
+            // Long click action should be removed
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.OnLongClick))
+    }
+
+    @Test
     fun click() {
         var counter = 0
         val onClick: () -> Unit = {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextLayoutTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextLayoutTest.kt
new file mode 100644
index 0000000..b45e79b
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/BasicTextLayoutTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.text
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// largest size of a unfocused dimension from Constraints.kt
+private val UnfocusedDimensionConstraintMax = 2 shl 13
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class BasicTextLayoutTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun simple_layoutText_doesNotThrow_when2shl14char() {
+        var textLayoutResult: TextLayoutResult? = null
+        rule.setContent {
+            BasicText(
+                text = "a".repeat(2 shl 14),
+                style = TextStyle(fontSize = 48.sp),
+                onTextLayout = { textLayoutResult = it }
+            )
+        }
+        rule.waitForIdle()
+
+        assertThat(textLayoutResult?.multiParagraph?.height).isGreaterThan(
+            UnfocusedDimensionConstraintMax
+        )
+    }
+
+    @Test
+    fun annotatedString_layoutText_doesNotThrow_when2shl14char() {
+        var textLayoutResult: TextLayoutResult? = null
+        rule.setContent {
+            BasicText(
+                text = AnnotatedString("a".repeat(2 shl 14)),
+                style = TextStyle(fontSize = 48.sp),
+                onTextLayout = { textLayoutResult = it }
+            )
+        }
+        rule.waitForIdle()
+        assertThat(textLayoutResult?.multiParagraph?.height).isGreaterThan(
+            UnfocusedDimensionConstraintMax
+        )
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
index ecc70a7..51d2ef0 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
@@ -36,7 +36,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(InternalFoundationTextApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class TextDelegateIntegrationTest {
@@ -200,6 +199,23 @@
 
         assertThat(layoutResultLtr.size.width).isEqualTo(layoutResultRtl.size.width)
     }
+
+    @Test
+    fun layoutText_doesntThrow_when2shl14char() {
+        val textDelegate = TextDelegate(
+            text = AnnotatedString(text = "a".repeat(2 shl 14)),
+            style = TextStyle.Default,
+            density = density,
+            fontFamilyResolver = fontFamilyResolver
+        )
+        val subject = textDelegate.layout(
+            Constraints() /* unbounded */,
+            layoutDirection = LayoutDirection.Ltr,
+            null
+        )
+        assertThat(subject.size.width).isGreaterThan(0)
+        assertThat(subject.size.height).isGreaterThan(0)
+    }
 }
 
 private fun TextLayoutResult.toBitmap() = Bitmap.createBitmap(
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldDrawPhaseToggleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldDrawPhaseToggleTest.kt
index 4a8dfe9..21b9699 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldDrawPhaseToggleTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldDrawPhaseToggleTest.kt
@@ -182,7 +182,7 @@
  * Instead of looking for an exact match of pixel values, this assertion provides the ability to
  * judge each pixel individually to whether it fits a predefined filter.
  */
-private inline fun ImageBitmap.assertPixelConsistency(
+internal inline fun ImageBitmap.assertPixelConsistency(
     filter: (color: Color) -> Boolean
 ) {
     val pixel = toPixelMap()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldLayoutPhaseToggleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldLayoutPhaseToggleTest.kt
new file mode 100644
index 0000000..b7f00c2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldLayoutPhaseToggleTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.input
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.matchers.assertThat
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.sp
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+
+class BasicTextFieldLayoutPhaseToggleTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var state: TextFieldState
+
+    private val fontSize = 20.sp
+    private val textStyle = TextStyle(
+        fontSize = fontSize,
+        fontFamily = TEST_FONT_FAMILY
+    )
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun fontWeightChange_reflectsOnView() {
+        state = TextFieldState("abc")
+        var fontWeight by mutableStateOf(FontWeight.Normal)
+        rule.setContent {
+            BasicTextField(
+                state = state,
+                textStyle = TextStyle(fontWeight = fontWeight),
+                modifier = Modifier.background(Color.White)
+            )
+        }
+
+        val firstBitmap = rule.onNode(hasSetTextAction()).captureToImage().asAndroidBitmap()
+        val firstTextLayoutResult = rule.onNode(hasSetTextAction()).fetchTextLayoutResult()
+
+        assertThat(firstTextLayoutResult.layoutInput.style.fontWeight).isEqualTo(FontWeight.Normal)
+
+        fontWeight = FontWeight.Bold
+
+        val secondBitmap = rule.onNode(hasSetTextAction()).captureToImage().asAndroidBitmap()
+        val secondTextLayoutResult = rule.onNode(hasSetTextAction()).fetchTextLayoutResult()
+
+        assertThat(secondTextLayoutResult.layoutInput.style.fontWeight).isEqualTo(FontWeight.Bold)
+        assertThat(firstBitmap).isNotEqualToBitmap(secondBitmap)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textAlignChange_reflectsOnView() {
+        state = TextFieldState("abc")
+        var textAlign by mutableStateOf(TextAlign.End)
+        rule.setContent {
+            BasicTextField(
+                state = state,
+                textStyle = textStyle.copy(textAlign = textAlign),
+                modifier = Modifier.background(Color.White)
+            )
+        }
+
+        val firstBitmap = rule.onNode(hasSetTextAction()).captureToImage().asAndroidBitmap()
+        val firstTextLayoutResult = rule.onNode(hasSetTextAction()).fetchTextLayoutResult()
+
+        assertThat(firstTextLayoutResult.layoutInput.style.textAlign).isEqualTo(TextAlign.End)
+
+        textAlign = TextAlign.Start
+
+        val secondBitmap = rule.onNode(hasSetTextAction()).captureToImage().asAndroidBitmap()
+        val secondTextLayoutResult = rule.onNode(hasSetTextAction()).fetchTextLayoutResult()
+
+        assertThat(secondTextLayoutResult.layoutInput.style.textAlign).isEqualTo(TextAlign.Start)
+        assertThat(firstBitmap).isNotEqualToBitmap(secondBitmap)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCacheTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCacheTest.kt
index 9acdbc4..4efd4b4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCacheTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCacheTest.kt
@@ -492,6 +492,7 @@
         ) { old, new ->
             Truth.assertThat(old.layoutInput.style.fontSize).isEqualTo(12.sp)
             Truth.assertThat(new.layoutInput.style.fontSize).isEqualTo(23.sp)
+            Truth.assertThat(old.multiParagraph).isNotSameInstanceAs(new.multiParagraph)
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt
index 8863ec7..d4efca6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt
@@ -18,17 +18,44 @@
 
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Constraints.Companion.fitPrioritizingWidth
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 
+/**
+ * Constraints are packed see [Constraints] implementation.
+ *
+ * These constants are the largest values that each slot can hold. The following pairings are the
+ * only ones allowed:
+ *
+ * (Big, Tiny), (Tiny, Big)
+ * (Medium, Small), (Small, Medium)
+ *
+ * For more information see [Constraints] implementation
+ */
+internal const val BigConstraintValue = (1 shl 18) - 1
+
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class LayoutUtilsKtTest {
 
     @Test
+    fun finalConstraints_doesntThrowWhenLarge() {
+        // this used to throw, ensure it doesn't
+        val subject = finalConstraints(
+            /* minWidth != maxWidth */
+            Constraints(0, 500, 0, BigConstraintValue - 1),
+            true /* width matters */,
+            TextOverflow.Ellipsis,
+            (BigConstraintValue - 1).toFloat()
+        )
+        assertThat(subject).isNotNull()
+    }
+
+    @Test
     fun finalConstraints_returnsTightWidth() {
         val subject = finalConstraints(
             Constraints(500, 500, 0, 50),
@@ -99,7 +126,12 @@
             while (1 shl b > 0) {
                 val height = 1 shl b
                 /* shouldn't crash */
-                val constraints = Constraints.fixedCoerceHeightAndWidthForBits(width, height)
+                val constraints = fitPrioritizingWidth(
+                    minWidth = width,
+                    maxWidth = width,
+                    minHeight = height,
+                    maxHeight = height
+                )
                 println("$width $height => $constraints")
                 b++
             }
@@ -107,48 +139,4 @@
             a++
         }
     }
-
-    @Test
-    fun fixedCoerce_BigToTiny() {
-        val subject = Constraints.fixedCoerceHeightAndWidthForBits(
-            BigConstraintValue,
-            BigConstraintValue
-        )
-        assertThat(subject).isEqualTo(
-            Constraints.fixed(BigConstraintValue - 1, TinyConstraintValue - 1)
-        )
-    }
-
-    @Test
-    fun fixdCoerce_MediumToSmall() {
-        val subject = Constraints.fixedCoerceHeightAndWidthForBits(
-            MediumConstraintValue - 1,
-            BigConstraintValue
-        )
-        assertThat(subject).isEqualTo(
-            Constraints.fixed(MediumConstraintValue - 1, SmallConstraintValue - 1)
-        )
-    }
-
-    @Test
-    fun fixdCoerce_SmallToMedium() {
-        val subject = Constraints.fixedCoerceHeightAndWidthForBits(
-            SmallConstraintValue - 1,
-            BigConstraintValue
-        )
-        assertThat(subject).isEqualTo(
-            Constraints.fixed(SmallConstraintValue - 1, MediumConstraintValue - 1)
-        )
-    }
-
-    @Test
-    fun fixdCoerce_TinyToBig() {
-        val subject = Constraints.fixedCoerceHeightAndWidthForBits(
-            TinyConstraintValue - 1,
-            BigConstraintValue
-        )
-        assertThat(subject).isEqualTo(
-            Constraints.fixed(TinyConstraintValue - 1, BigConstraintValue - 1)
-        )
-    }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 2cea5ef..28375ae 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -823,13 +823,12 @@
         if ((this.onLongClick == null) != (onLongClick == null)) {
             // Adding or removing longClick should cancel any existing press interactions
             disposeInteractions()
+            // Adding or removing longClick should add / remove the corresponding property
+            invalidateSemantics()
             resetPointerInputHandling = true
         }
 
-        if (this.onLongClick !== onLongClick) {
-            this.onLongClick = onLongClick
-            invalidateSemantics()
-        }
+        this.onLongClick = onLongClick
 
         if ((this.onDoubleClick == null) != (onDoubleClick == null)) {
             resetPointerInputHandling = true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
index f95084a..5db29b1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
@@ -455,71 +455,7 @@
         }
     }
 
-    private val pointerInputNode = delegate(SuspendingPointerInputModifierNode {
-        // TODO: conditionally undelegate when aosp/2462416 lands?
-        if ([email protected]) return@SuspendingPointerInputModifierNode
-        // re-create tracker when pointer input block restarts. This lazily creates the tracker
-        // only when it is need.
-        val velocityTracker = VelocityTracker()
-        coroutineScope {
-            try {
-                awaitPointerEventScope {
-                    while (isActive) {
-                        awaitDownAndSlop(
-                            _canDrag,
-                            ::startDragImmediately,
-                            velocityTracker,
-                            pointerDirectionConfig
-                        )?.let {
-                            /**
-                             * The gesture crossed the touch slop, events are now relevant
-                             * and should be propagated
-                             */
-                            if (!isListeningForEvents) {
-                                if (channel == null) {
-                                    channel = Channel(capacity = Channel.UNLIMITED)
-                                }
-                                startListeningForEvents()
-                            }
-                            var isDragSuccessful = false
-                            try {
-                                isDragSuccessful = awaitDrag(
-                                    it.first,
-                                    it.second,
-                                    velocityTracker,
-                                    channel
-                                ) { event ->
-                                    pointerDirectionConfig.calculateDeltaChange(
-                                        event.positionChangeIgnoreConsumed()
-                                    ) != 0f
-                                }
-                            } catch (cancellation: CancellationException) {
-                                isDragSuccessful = false
-                                if (!isActive) throw cancellation
-                            } finally {
-                                val maximumVelocity = currentValueOf(LocalViewConfiguration)
-                                    .maximumFlingVelocity
-                                val event = if (isDragSuccessful) {
-                                    val velocity = velocityTracker.calculateVelocity(
-                                        Velocity(maximumVelocity, maximumVelocity)
-                                    )
-                                    velocityTracker.resetTracking()
-                                    DragStopped(velocity)
-                                } else {
-                                    DragCancelled
-                                }
-                                channel?.trySend(event)
-                            }
-                        }
-                    }
-                }
-            } catch (exception: CancellationException) {
-                if (!isActive) {
-                    throw exception
-                }
-            }
-        }
-    })
+    private var pointerInputNode: SuspendingPointerInputModifierNode? = null
 
     override fun onDetach() {
         isListeningForEvents = false
@@ -531,11 +467,80 @@
         pass: PointerEventPass,
         bounds: IntSize
     ) {
-        pointerInputNode.onPointerEvent(pointerEvent, pass, bounds)
+        if (enabled && pointerInputNode == null) {
+            pointerInputNode = delegate(initializePointerInputNode())
+        }
+        pointerInputNode?.onPointerEvent(pointerEvent, pass, bounds)
+    }
+
+    private fun initializePointerInputNode(): SuspendingPointerInputModifierNode {
+        return SuspendingPointerInputModifierNode {
+            // re-create tracker when pointer input block restarts. This lazily creates the tracker
+            // only when it is need.
+            val velocityTracker = VelocityTracker()
+            coroutineScope {
+                try {
+                    awaitPointerEventScope {
+                        while (isActive) {
+                            awaitDownAndSlop(
+                                _canDrag,
+                                ::startDragImmediately,
+                                velocityTracker,
+                                pointerDirectionConfig
+                            )?.let {
+                                /**
+                                 * The gesture crossed the touch slop, events are now relevant
+                                 * and should be propagated
+                                 */
+                                if (!isListeningForEvents) {
+                                    if (channel == null) {
+                                        channel = Channel(capacity = Channel.UNLIMITED)
+                                    }
+                                    startListeningForEvents()
+                                }
+                                var isDragSuccessful = false
+                                try {
+                                    isDragSuccessful = awaitDrag(
+                                        it.first,
+                                        it.second,
+                                        velocityTracker,
+                                        channel
+                                    ) { event ->
+                                        pointerDirectionConfig.calculateDeltaChange(
+                                            event.positionChangeIgnoreConsumed()
+                                        ) != 0f
+                                    }
+                                } catch (cancellation: CancellationException) {
+                                    isDragSuccessful = false
+                                    if (!isActive) throw cancellation
+                                } finally {
+                                    val maximumVelocity = currentValueOf(LocalViewConfiguration)
+                                        .maximumFlingVelocity
+                                    val event = if (isDragSuccessful) {
+                                        val velocity = velocityTracker.calculateVelocity(
+                                            Velocity(maximumVelocity, maximumVelocity)
+                                        )
+                                        velocityTracker.resetTracking()
+                                        DragStopped(velocity)
+                                    } else {
+                                        DragCancelled
+                                    }
+                                    channel?.trySend(event)
+                                }
+                            }
+                        }
+                    }
+                } catch (exception: CancellationException) {
+                    if (!isActive) {
+                        throw exception
+                    }
+                }
+            }
+        }
     }
 
     override fun onCancelPointerInput() {
-        pointerInputNode.onCancelPointerInput()
+        pointerInputNode?.onCancelPointerInput()
     }
 
     private suspend fun CoroutineScope.processDragStart(event: DragStarted) {
@@ -578,11 +583,14 @@
         isResetPointerInputHandling: Boolean = false
     ) {
         var resetPointerInputHandling = isResetPointerInputHandling
+
         this.canDrag = canDrag
         if (this.enabled != enabled) {
             this.enabled = enabled
             if (!enabled) {
                 disposeInteractionSource()
+                pointerInputNode?.let { undelegate(it) }
+                pointerInputNode = null
             }
             resetPointerInputHandling = true
         }
@@ -592,7 +600,7 @@
         }
 
         if (resetPointerInputHandling) {
-            pointerInputNode.resetPointerInputHandler()
+            pointerInputNode?.resetPointerInputHandler()
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 30babe0..5dcee03 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -81,6 +81,8 @@
             consumedScroll = 0,
             layoutWidth = layoutWidth,
             layoutHeight = layoutHeight,
+            beforeContentPadding = beforeContentPadding,
+            afterContentPadding = afterContentPadding,
             positionedItems = mutableListOf(),
             keyIndexMap = measuredItemProvider.keyIndexMap,
             itemProvider = measuredItemProvider,
@@ -352,6 +354,8 @@
             consumedScroll = consumedScroll.toInt(),
             layoutWidth = layoutWidth,
             layoutHeight = layoutHeight,
+            beforeContentPadding = beforeContentPadding,
+            afterContentPadding = afterContentPadding,
             positionedItems = positionedItems,
             keyIndexMap = measuredItemProvider.keyIndexMap,
             itemProvider = measuredItemProvider,
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 8953e39..34f7ca9 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
@@ -81,6 +81,8 @@
             consumedScroll = 0,
             layoutWidth = layoutWidth,
             layoutHeight = layoutHeight,
+            beforeContentPadding = beforeContentPadding,
+            afterContentPadding = afterContentPadding,
             positionedItems = mutableListOf(),
             keyIndexMap = measuredItemProvider.keyIndexMap,
             itemProvider = measuredItemProvider,
@@ -319,6 +321,8 @@
             consumedScroll = consumedScroll.toInt(),
             layoutWidth = layoutWidth,
             layoutHeight = layoutHeight,
+            beforeContentPadding = beforeContentPadding,
+            afterContentPadding = afterContentPadding,
             positionedItems = positionedItems,
             keyIndexMap = measuredItemProvider.keyIndexMap,
             itemProvider = measuredItemProvider,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
index f1adbb1..612ba62 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemAnimator.kt
@@ -71,6 +71,8 @@
         consumedScroll: Int,
         layoutWidth: Int,
         layoutHeight: Int,
+        beforeContentPadding: Int,
+        afterContentPadding: Int,
         positionedItems: MutableList<T>,
         keyIndexMap: LazyLayoutKeyIndexMap,
         itemProvider: LazyLayoutMeasuredItemProvider<T>,
@@ -186,8 +188,10 @@
                 movingInFromEndBound.sortBy { previousKeyToIndexMap.getIndex(it.key) }
                 movingInFromEndBound.fastForEach { item ->
                     val accumulatedOffset = accumulatedOffsetPerLane.updateAndReturnOffsetFor(item)
-                    val mainAxisOffset =
-                        mainAxisLayoutSize + accumulatedOffset - item.mainAxisSizeWithSpacings
+                    // Compensate content padding
+                    val contentPadding = beforeContentPadding + afterContentPadding
+                    val mainAxisOffset = mainAxisLayoutSize + accumulatedOffset -
+                        item.mainAxisSizeWithSpacings + contentPadding
                     initializeAnimation(item, mainAxisOffset)
                     startPlacementAnimationsIfNeeded(item)
                 }
@@ -285,7 +289,8 @@
                     positionedItems.last()
                         .let { it.mainAxisOffset }
                 else {
-                    mainAxisLayoutSize - item.mainAxisSizeWithSpacings
+                    val contentPadding = beforeContentPadding + afterContentPadding
+                    mainAxisLayoutSize - item.mainAxisSizeWithSpacings + contentPadding
                 } + accumulatedOffset
 
                 val itemInfo = keyToItemInfoMap[item.key]!!
@@ -390,19 +395,20 @@
         return maxOffset
     }
 
-    val minSizeToFitDisappearingItems: IntSize get() {
-        var size = IntSize.Zero
-        disappearingItems.fastForEach {
-            val layer = it.layer
-            if (layer != null) {
-                size = IntSize(
-                    width = maxOf(size.width, it.rawOffset.x + layer.size.width),
-                    height = maxOf(size.height, it.rawOffset.y + layer.size.height)
-                )
+    val minSizeToFitDisappearingItems: IntSize
+        get() {
+            var size = IntSize.Zero
+            disappearingItems.fastForEach {
+                val layer = it.layer
+                if (layer != null) {
+                    size = IntSize(
+                        width = maxOf(size.width, it.rawOffset.x + layer.size.width),
+                        height = maxOf(size.height, it.rawOffset.y + layer.size.height)
+                    )
+                }
             }
+            return size
         }
-        return size
-    }
 
     val modifier: Modifier = DisplayingDisappearingItemsElement(this)
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index eeb9286..5928a6e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -263,6 +263,8 @@
                 consumedScroll = 0,
                 layoutWidth = layoutWidth,
                 layoutHeight = layoutHeight,
+                beforeContentPadding = beforeContentPadding,
+                afterContentPadding = afterContentPadding,
                 positionedItems = mutableListOf(),
                 keyIndexMap = measuredItemProvider.keyIndexMap,
                 itemProvider = measuredItemProvider,
@@ -870,6 +872,8 @@
             consumedScroll = consumedScroll.toInt(),
             layoutWidth = layoutWidth,
             layoutHeight = layoutHeight,
+            beforeContentPadding = beforeContentPadding,
+            afterContentPadding = afterContentPadding,
             positionedItems = positionedItems,
             keyIndexMap = measuredItemProvider.keyIndexMap,
             itemProvider = measuredItemProvider,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
index 4fecf94..1b48576 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.text.modifiers.TextAnnotatedStringElement
 import androidx.compose.foundation.text.modifiers.TextAnnotatedStringNode
 import androidx.compose.foundation.text.modifiers.TextStringSimpleElement
-import androidx.compose.foundation.text.modifiers.fixedCoerceHeightAndWidthForBits
 import androidx.compose.foundation.text.modifiers.hasLinks
 import androidx.compose.foundation.text.selection.LocalSelectionRegistrar
 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
@@ -53,6 +52,7 @@
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Constraints.Companion.fitPrioritizingWidth
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastForEach
@@ -453,9 +453,11 @@
                 textRangeLayoutMeasureScope.measure()
             }
             val placeable = measurable.measure(
-                Constraints.fixedCoerceHeightAndWidthForBits(
-                    rangeMeasureResult.width,
-                    rangeMeasureResult.height
+                fitPrioritizingWidth(
+                    minWidth = rangeMeasureResult.width,
+                    maxWidth = rangeMeasureResult.width,
+                    minHeight = rangeMeasureResult.height,
+                    maxHeight = rangeMeasureResult.height
                 )
             )
             Pair(placeable, rangeMeasureResult.place)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
index 7320768..dc8e0d5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
@@ -199,7 +199,12 @@
 
         return MultiParagraph(
             intrinsics = nonNullIntrinsics,
-            constraints = Constraints(maxWidth = width, maxHeight = constraints.maxHeight),
+            constraints = Constraints.fitPrioritizingWidth(
+                minWidth = 0,
+                maxWidth = width,
+                minHeight = 0,
+                maxHeight = constraints.maxHeight
+            ),
             // This is a fallback behavior for ellipsis. Native
             maxLines = finalMaxLines,
             ellipsis = overflow == TextOverflow.Ellipsis
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLinkScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLinkScope.kt
index 2703b2d..14af807 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLinkScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLinkScope.kt
@@ -201,7 +201,7 @@
         uriHandler: UriHandler
     ) {
         when (link) {
-            is LinkAnnotation.Url -> link.linkInteractionListener?.onClicked(link) ?: try {
+            is LinkAnnotation.Url -> link.linkInteractionListener?.onClick(link) ?: try {
                 uriHandler.openUri(link.url)
             } catch (_: IllegalArgumentException) {
                 // we choose to silently fail when the uri can't be opened to avoid crashes
@@ -209,7 +209,7 @@
                 // handlers themselves and therefore I suspect are less likely to test them
                 // manually.
             }
-            is LinkAnnotation.Clickable -> link.linkInteractionListener?.onClicked(link)
+            is LinkAnnotation.Clickable -> link.linkInteractionListener?.onClick(link)
         }
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
index 547eeac..8426da0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldLayoutStateCache.kt
@@ -165,12 +165,17 @@
                 cachedRecord.constraints == measureInputs.constraints &&
                 cachedRecord.fontFamilyResolver == measureInputs.fontFamilyResolver
             ) {
+                val isLayoutAffectingSame = cachedRecord.textStyle
+                    ?.hasSameLayoutAffectingAttributes(nonMeasureInputs.textStyle) ?: false
+
+                val isDrawAffectingSame = cachedRecord.textStyle
+                    ?.hasSameDrawAffectingAttributes(nonMeasureInputs.textStyle) ?: false
+
                 // Fast path: None of the inputs changed.
-                if (cachedRecord.textStyle == nonMeasureInputs.textStyle) return cachedResult
+                if (isLayoutAffectingSame && isDrawAffectingSame) return cachedResult
+
                 // Slightly slower than fast path: Layout did not change but TextLayoutInput did
-                if (cachedRecord.textStyle
-                        ?.hasSameDrawAffectingAttributes(nonMeasureInputs.textStyle) == true
-                ) {
+                if (isLayoutAffectingSame) {
                     return cachedResult.copy(
                         layoutInput = TextLayoutInput(
                             cachedResult.layoutInput.text,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt
index ed89579..201baaf 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt
@@ -29,7 +29,9 @@
     overflow: TextOverflow,
     maxIntrinsicWidth: Float
 ): Constraints = Constraints(
+        minWidth = 0,
         maxWidth = finalMaxWidth(constraints, softWrap, overflow, maxIntrinsicWidth),
+        minHeight = 0,
         maxHeight = constraints.maxHeight
     )
 
@@ -86,48 +88,3 @@
     val overwriteMaxLines = !softWrap && overflow == TextOverflow.Ellipsis
     return if (overwriteMaxLines) 1 else maxLinesIn.coerceAtLeast(1)
 }
-
-/**
- * Constraints are packed see [Constraints] implementation.
- *
- * These constants are the largest values that each slot can hold. The following pairings are the
- * only ones allowed:
- *
- * (Big, Tiny), (Tiny, Big)
- * (Medium, Small), (Small, Medium)
- *
- * For more information see [Constraints] implementation
- */
-internal const val BigConstraintValue = (1 shl 18) - 1
-internal const val MediumConstraintValue = (1 shl 16) - 1
-internal const val SmallConstraintValue = (1 shl 15) - 1
-internal const val TinyConstraintValue = (1 shl 13) - 1
-
-/**
- * Make constraints that never throw from being too large. Prefer to keep accurate width information
- * first, then constrain height based on the size of width.
- *
- * This will return a Constraint with the same or smaller dimensions than the passed (width, height)
- *
- * see b/312294386 for more details
- *
- * This particular logic is text specific, so not generalizing.
- *
- * @param width desired width (has priority)
- * @param height desired height (uses the remaining bits after width)
- *
- * @return a safe Constraint that never throws for running out of bits
- */
-internal fun Constraints.Companion.fixedCoerceHeightAndWidthForBits(
-    width: Int,
-    height: Int
-): Constraints {
-    val safeWidth = minOf(width, BigConstraintValue - 1)
-    val safeHeight = when {
-        safeWidth < TinyConstraintValue -> minOf(height, BigConstraintValue - 1)
-        safeWidth < SmallConstraintValue -> minOf(height, MediumConstraintValue - 1)
-        safeWidth < MediumConstraintValue -> minOf(height, SmallConstraintValue - 1)
-        else -> minOf(height, TinyConstraintValue - 1)
-    }
-    return fixed(safeWidth, safeHeight)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
index 6496137..79212ae 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
@@ -60,6 +60,7 @@
 import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Constraints.Companion.fitPrioritizingWidth
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastRoundToInt
 
@@ -442,9 +443,11 @@
 
         // then allow children to measure _inside_ our final box, with the above placeholders
         val placeable = measurable.measure(
-            Constraints.fixedCoerceHeightAndWidthForBits(
-                width = textLayoutResult.size.width,
-                height = textLayoutResult.size.height
+            fitPrioritizingWidth(
+                minWidth = textLayoutResult.size.width,
+                maxWidth = textLayoutResult.size.width,
+                minHeight = textLayoutResult.size.height,
+                maxHeight = textLayoutResult.size.height
             )
         )
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
index 8ce81b6..e3ce8a7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
@@ -55,6 +55,7 @@
 import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Constraints.Companion.fitPrioritizingWidth
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastRoundToInt
 
@@ -359,9 +360,11 @@
 
         // then allow children to measure _inside_ our final box, with the above placeholders
         val placeable = measurable.measure(
-            Constraints.fixedCoerceHeightAndWidthForBits(
-                layoutSize.width,
-                layoutSize.height
+            fitPrioritizingWidth(
+                minWidth = layoutSize.width,
+                maxWidth = layoutSize.width,
+                minHeight = layoutSize.height,
+                maxHeight = layoutSize.height
             )
         )
 
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SliderSample.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SliderSample.kt
index e2e6f8b..0e014ff 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SliderSample.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SliderSample.kt
@@ -17,6 +17,8 @@
 package androidx.compose.material.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.RangeSlider
@@ -28,34 +30,43 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
 
 @Sampled
 @Composable
 fun SliderSample() {
     var sliderPosition by remember { mutableStateOf(0f) }
-    Text(text = sliderPosition.toString())
-    Slider(value = sliderPosition, onValueChange = { sliderPosition = it })
+    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
+        Text(text = "%.2f".format(sliderPosition))
+        Slider(value = sliderPosition, onValueChange = { sliderPosition = it })
+    }
 }
 
 @Sampled
 @Composable
 fun StepsSliderSample() {
     var sliderPosition by remember { mutableStateOf(0f) }
-    Text(text = sliderPosition.toString())
-    Slider(
-        value = sliderPosition,
-        onValueChange = { sliderPosition = it },
-        valueRange = 0f..100f,
-        onValueChangeFinished = {
-            // launch some business logic update with the state you hold
-            // viewModel.updateSelectedSliderValue(sliderPosition)
-        },
-        steps = 5,
-        colors = SliderDefaults.colors(
-            thumbColor = MaterialTheme.colors.secondary,
-            activeTrackColor = MaterialTheme.colors.secondary
+    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
+        Text(text = sliderPosition.roundToInt().toString())
+        Slider(
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it },
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            },
+            // Only allow multiples of 10. Excluding the endpoints of `valueRange`,
+            // there are 9 steps (10, 20, ..., 90).
+            steps = 9,
+            colors = SliderDefaults.colors(
+                thumbColor = MaterialTheme.colors.secondary,
+                activeTrackColor = MaterialTheme.colors.secondary
+            )
         )
-    )
+    }
 }
 
 @Sampled
@@ -63,16 +74,20 @@
 @OptIn(ExperimentalMaterialApi::class)
 fun RangeSliderSample() {
     var sliderPosition by remember { mutableStateOf(0f..100f) }
-    Text(text = sliderPosition.toString())
-    RangeSlider(
-        value = sliderPosition,
-        onValueChange = { sliderPosition = it },
-        valueRange = 0f..100f,
-        onValueChangeFinished = {
-            // launch some business logic update with the state you hold
-            // viewModel.updateSelectedSliderValue(sliderPosition)
-        },
-    )
+    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
+        val rangeStart = "%.2f".format(sliderPosition.start)
+        val rangeEnd = "%.2f".format(sliderPosition.endInclusive)
+        Text(text = "$rangeStart .. $rangeEnd")
+        RangeSlider(
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it },
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            },
+        )
+    }
 }
 
 @Sampled
@@ -80,19 +95,25 @@
 @OptIn(ExperimentalMaterialApi::class)
 fun StepRangeSliderSample() {
     var sliderPosition by remember { mutableStateOf(0f..100f) }
-    Text(text = sliderPosition.toString())
-    RangeSlider(
-        steps = 5,
-        value = sliderPosition,
-        onValueChange = { sliderPosition = it },
-        valueRange = 0f..100f,
-        onValueChangeFinished = {
-            // launch some business logic update with the state you hold
-            // viewModel.updateSelectedSliderValue(sliderPosition)
-        },
-        colors = SliderDefaults.colors(
-            thumbColor = MaterialTheme.colors.secondary,
-            activeTrackColor = MaterialTheme.colors.secondary
+    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
+        val rangeStart = sliderPosition.start.roundToInt()
+        val rangeEnd = sliderPosition.endInclusive.roundToInt()
+        Text(text = "$rangeStart .. $rangeEnd")
+        RangeSlider(
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it },
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            },
+            // Only allow multiples of 10. Excluding the endpoints of `valueRange`,
+            // there are 9 steps (10, 20, ..., 90).
+            steps = 9,
+            colors = SliderDefaults.colors(
+                thumbColor = MaterialTheme.colors.secondary,
+                activeTrackColor = MaterialTheme.colors.secondary
+            )
         )
-    )
+    }
 }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index 3004819..85fc6f7 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -132,9 +132,9 @@
  * @param enabled whether or not component is enabled and can be interacted with or not
  * @param valueRange range of values that Slider value can take. Passed [value] will be coerced to
  * this range
- * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed
- * between across the whole value range. If 0, slider will behave as a continuous slider and allow
- * to choose any value from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * slider will behave continuously and allow any value from the range. Must not be negative.
  * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback
  * shouldn't be used to update the slider value (use [onValueChange] for that), but rather to
  * know when the user has completed selecting a new value by ending a drag or a click.
@@ -282,9 +282,9 @@
  * @param enabled whether or not component is enabled and can we interacted with or not
  * @param valueRange range of values that Range Slider values can take. Passed [value] will be
  * coerced to this range
- * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed
- * between across the whole value range. If 0, range slider will behave as a continuous slider and
- * allow to choose any values from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * range slider will behave continuously and allow any value from the range. Must not be negative.
  * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback
  * shouldn't be used to update the range slider values (use [onValueChange] for that), but rather to
  * know when the user has completed selecting a new value by ending a drag or a click.
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 9d1a055..cf79bdb 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -38,7 +38,7 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
+  public final class ListDetailPaneScaffoldRole {
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getDetail();
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getExtra();
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getList();
@@ -63,7 +63,7 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
+  @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
     ctor public PaneScaffoldDirective(int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, float defaultPanePreferredWidth, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
     method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective copy(optional int maxHorizontalPartitions, optional float horizontalPartitionSpacerSize, optional int maxVerticalPartitions, optional float verticalPartitionSpacerSize, optional float defaultPanePreferredWidth, optional java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
     method public float getDefaultPanePreferredWidth();
@@ -91,7 +91,7 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldScope {
+  public sealed interface PaneScaffoldScope {
     method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
   }
 
@@ -127,7 +127,7 @@
     property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
+  public enum ThreePaneScaffoldRole {
     enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
     enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
     enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 9d1a055..cf79bdb 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -38,7 +38,7 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
+  public final class ListDetailPaneScaffoldRole {
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getDetail();
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getExtra();
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole getList();
@@ -63,7 +63,7 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
+  @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
     ctor public PaneScaffoldDirective(int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, float defaultPanePreferredWidth, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
     method public androidx.compose.material3.adaptive.layout.PaneScaffoldDirective copy(optional int maxHorizontalPartitions, optional float horizontalPartitionSpacerSize, optional int maxVerticalPartitions, optional float verticalPartitionSpacerSize, optional float defaultPanePreferredWidth, optional java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
     method public float getDefaultPanePreferredWidth();
@@ -91,7 +91,7 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldScope {
+  public sealed interface PaneScaffoldScope {
     method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
   }
 
@@ -127,7 +127,7 @@
     property public final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole pane;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
+  public enum ThreePaneScaffoldRole {
     enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Primary;
     enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Secondary;
     enum_constant public static final androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole Tertiary;
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/LargeScreenTestUtils.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/LargeScreenTestUtils.kt
index 5dfa0175..37edf98c 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/LargeScreenTestUtils.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/LargeScreenTestUtils.kt
@@ -19,7 +19,6 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.currentWindowSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -30,7 +29,6 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.toSize
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 internal fun ComposeContentTestRule.setContentWithSimulatedSize(
     simulatedWidth: Dp,
     simulatedHeight: Dp,
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
index 829f4a2..f738efb 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
@@ -166,7 +166,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockScaffoldDirective = PaneScaffoldDirective.Default
 
 internal const val ThreePaneScaffoldTestTag = "SampleThreePaneScaffold"
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
index 52b3b90..f7b506b 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
@@ -268,18 +268,21 @@
 private val hingeList = listOf(
     HingeInfo(
         bounds = Rect(0F, 0F, 1F, 1F),
+        isFlat = true,
         isVertical = true,
         isSeparating = false,
         isOccluding = true
     ),
     HingeInfo(
         bounds = Rect(1F, 1F, 2F, 2F),
+        isFlat = false,
         isVertical = true,
         isSeparating = false,
         isOccluding = true
     ),
     HingeInfo(
         bounds = Rect(2F, 2F, 3F, 3F),
+        isFlat = true,
         isVertical = true,
         isSeparating = true,
         isOccluding = false
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
index 74f39ac..6ab8f53 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
@@ -95,7 +95,6 @@
  * of three pane scaffolds. We suggest you to use the values defined here instead of the raw
  * [ThreePaneScaffoldRole] under the context of [ListDetailPaneScaffold] for better code clarity.
  */
-@ExperimentalMaterial3AdaptiveApi
 object ListDetailPaneScaffoldRole {
     /**
      * The list pane of [ListDetailPaneScaffold], which is supposed to hold a list of item summaries
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
index e164e2f..b840410 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.material3.adaptive.layout
 
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.ParentDataModifierNode
@@ -29,7 +28,6 @@
 /**
  * Scope for the panes of pane scaffolds.
  */
-@ExperimentalMaterial3AdaptiveApi
 sealed interface PaneScaffoldScope {
     /**
      * This modifier specifies the preferred width for a pane, and the pane scaffold implementation
@@ -43,7 +41,6 @@
     fun Modifier.preferredWidth(width: Dp): Modifier
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 internal abstract class PaneScaffoldScopeImpl : PaneScaffoldScope {
     override fun Modifier.preferredWidth(width: Dp): Modifier {
         require(width == Dp.Unspecified || width > 0.dp) { "invalid width" }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
index 8ba2159..8072c34 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
@@ -126,7 +126,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private fun getExcludedVerticalBounds(posture: Posture, hingePolicy: HingePolicy): List<Rect> {
     return when (hingePolicy) {
         HingePolicy.AvoidSeparating -> posture.separatingVerticalHingeBounds
@@ -155,9 +154,7 @@
  * @property excludedBounds the bounds of all areas in the window that the layout needs to avoid
  *           displaying anything upon it. Usually these bounds represent where physical hinges are.
  */
-@ExperimentalMaterial3AdaptiveApi
 @Immutable
-// TODO(conradchen): Hide the constructor. Please use the copy() method instead.
 class PaneScaffoldDirective(
     val maxHorizontalPartitions: Int,
     val horizontalPartitionSpacerSize: Dp,
@@ -190,8 +187,7 @@
         maxVerticalPartitions: Int = this.maxVerticalPartitions,
         verticalPartitionSpacerSize: Dp = this.verticalPartitionSpacerSize,
         defaultPanePreferredWidth: Dp = this.defaultPanePreferredWidth,
-        @Suppress("ListIterator") // No guarantee to be an array list
-        excludedBounds: List<Rect> = this.excludedBounds.toList()
+        excludedBounds: List<Rect> = this.excludedBounds
     ): PaneScaffoldDirective = PaneScaffoldDirective(
         maxHorizontalPartitions,
         horizontalPartitionSpacerSize,
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index 86e729e..6e92343 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -580,7 +580,6 @@
         }
     }
 
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
     private fun MutableList<PaneMeasurable>.createPaneMeasurableIfNeeded(
         measurables: List<Measurable>,
         priority: Int,
@@ -632,7 +631,6 @@
         )
     }
 
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
     private fun Placeable.PlacementScope.measureAndPlacePanesWithLocalBounds(
         partitionBounds: IntRect,
         spacerSize: Int,
@@ -747,7 +745,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private class PaneMeasurable(
     val measurable: Measurable,
     val priority: Int,
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
index 589c663..a672945 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
@@ -111,7 +111,6 @@
 /**
  * The set of the available pane roles of [ThreePaneScaffold].
  */
-@ExperimentalMaterial3AdaptiveApi
 enum class ThreePaneScaffoldRole {
     /**
      * The primary pane of [ThreePaneScaffold]. It is supposed to have the highest priority during
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
index 5218d92..6307eda 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
@@ -566,10 +566,8 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective.Default
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockDualPaneScaffoldDirective = PaneScaffoldDirective.Default.copy(
     maxHorizontalPartitions = 2,
     horizontalPartitionSpacerSize = 16.dp,
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
index d270567..baa76ed 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
@@ -580,10 +580,8 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective.Default
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockDualPaneScaffoldDirective = PaneScaffoldDirective.Default.copy(
     maxHorizontalPartitions = 2,
     horizontalPartitionSpacerSize = 16.dp,
diff --git a/compose/material3/adaptive/adaptive/api/current.txt b/compose/material3/adaptive/adaptive/api/current.txt
index 6fe8f2a..4532ffe 100644
--- a/compose/material3/adaptive/adaptive/api/current.txt
+++ b/compose/material3/adaptive/adaptive/api/current.txt
@@ -15,12 +15,14 @@
   }
 
   @androidx.compose.runtime.Immutable public final class HingeInfo {
-    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isVertical, boolean isSeparating, boolean isOccluding);
+    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isFlat, boolean isVertical, boolean isSeparating, boolean isOccluding);
     method public androidx.compose.ui.geometry.Rect getBounds();
+    method public boolean isFlat();
     method public boolean isOccluding();
     method public boolean isSeparating();
     method public boolean isVertical();
     property public final androidx.compose.ui.geometry.Rect bounds;
+    property public final boolean isFlat;
     property public final boolean isOccluding;
     property public final boolean isSeparating;
     property public final boolean isVertical;
diff --git a/compose/material3/adaptive/adaptive/api/restricted_current.txt b/compose/material3/adaptive/adaptive/api/restricted_current.txt
index 6fe8f2a..4532ffe 100644
--- a/compose/material3/adaptive/adaptive/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive/api/restricted_current.txt
@@ -15,12 +15,14 @@
   }
 
   @androidx.compose.runtime.Immutable public final class HingeInfo {
-    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isVertical, boolean isSeparating, boolean isOccluding);
+    ctor public HingeInfo(androidx.compose.ui.geometry.Rect bounds, boolean isFlat, boolean isVertical, boolean isSeparating, boolean isOccluding);
     method public androidx.compose.ui.geometry.Rect getBounds();
+    method public boolean isFlat();
     method public boolean isOccluding();
     method public boolean isSeparating();
     method public boolean isVertical();
     property public final androidx.compose.ui.geometry.Rect bounds;
+    property public final boolean isFlat;
     property public final boolean isOccluding;
     property public final boolean isSeparating;
     property public final boolean isVertical;
diff --git a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt
index 1eb4823..26a5113 100644
--- a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt
+++ b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectFoldingFeaturesAsStateTest.kt
@@ -33,7 +33,6 @@
 import org.junit.rules.TestRule
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CollectFoldingFeaturesAsStateTest {
diff --git a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
index 8ff84ab..4a62e9d 100644
--- a/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
+++ b/compose/material3/adaptive/adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
@@ -37,7 +37,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CollectWindowSizeAsStateTest {
diff --git a/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt b/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
index 61ad91e..7907e41 100644
--- a/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
+++ b/compose/material3/adaptive/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt
@@ -35,6 +35,7 @@
         }
         hingeList.add(HingeInfo(
             bounds = it.bounds.toComposeRect(),
+            isFlat = it.state == FoldingFeature.State.FLAT,
             isVertical = it.orientation == FoldingFeature.Orientation.VERTICAL,
             isSeparating = it.isSeparating,
             isOccluding = it.occlusionType == FoldingFeature.OcclusionType.FULL
diff --git a/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
index 1510287..349405b 100644
--- a/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
+++ b/compose/material3/adaptive/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt
@@ -100,6 +100,8 @@
  * A class that contains the info of a hinge relevant to a [Posture].
  *
  * @param bounds the bounds of the hinge in the relevant viewport.
+ * @param isFlat `true` if the hinge is fully open and the relevant window space presented to the
+ *        user is flat.
  * @param isVertical `true` if the hinge is a vertical one, i.e., it separates the viewport into
  *        left and right; `false` if the hinge is horizontal, i.e., it separates the viewport
  *        into top and bottom.
@@ -109,6 +111,7 @@
 @Immutable
 class HingeInfo(
     val bounds: Rect,
+    val isFlat: Boolean,
     val isVertical: Boolean,
     val isSeparating: Boolean,
     val isOccluding: Boolean
@@ -117,6 +120,7 @@
         if (this === other) return true
         if (other !is HingeInfo) return false
         if (bounds != other.bounds) return false
+        if (isFlat != other.isFlat) return false
         if (isVertical != other.isVertical) return false
         if (isSeparating != other.isSeparating) return false
         if (isOccluding != other.isOccluding) return false
@@ -125,6 +129,7 @@
 
     override fun hashCode(): Int {
         var result = bounds.hashCode()
+        result = 31 * result + isFlat.hashCode()
         result = 31 * result + isVertical.hashCode()
         result = 31 * result + isSeparating.hashCode()
         result = 31 * result + isOccluding.hashCode()
@@ -133,6 +138,7 @@
 
     override fun toString(): String {
         return "HingeInfo(bounds=$bounds, " +
+            "isFlat=$isFlat, " +
             "isVertical=$isVertical, " +
             "isSeparating=$isSeparating, " +
             "isOccluding=$isOccluding)"
diff --git a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
index fd44e30..217aa37 100644
--- a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
+++ b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
@@ -34,10 +34,8 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 val singlePaneDirective = PaneScaffoldDirective.Default
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 val dualPaneDirective = PaneScaffoldDirective.Default.copy(
     maxHorizontalPartitions = 2,
     horizontalPartitionSpacerSize = 24.dp,
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationSuiteScaffoldBenchmarkTest.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationSuiteScaffoldBenchmarkTest.kt
index 6b2b165..16a22cc 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationSuiteScaffoldBenchmarkTest.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/NavigationSuiteScaffoldBenchmarkTest.kt
@@ -19,7 +19,6 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi
 import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableIntState
@@ -59,7 +58,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 internal class NavigationSuiteScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
     private lateinit var selectedIndexState: MutableIntState
 
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/current.txt b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
index 7a7bc4a..83d23c0 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
@@ -4,7 +4,7 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive-navigation-suite API is experimental and is likely to" + "change or to be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveNavigationSuiteApi {
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteColors {
+  public final class NavigationSuiteColors {
     method public long getNavigationBarContainerColor();
     method public long getNavigationBarContentColor();
     method public long getNavigationDrawerContainerColor();
@@ -19,13 +19,13 @@
     property public final long navigationRailContentColor;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteDefaults {
+  public final class NavigationSuiteDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors(optional long navigationBarContainerColor, optional long navigationBarContentColor, optional long navigationRailContainerColor, optional long navigationRailContentColor, optional long navigationDrawerContainerColor, optional long navigationDrawerContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors itemColors(optional androidx.compose.material3.NavigationBarItemColors navigationBarItemColors, optional androidx.compose.material3.NavigationRailItemColors navigationRailItemColors, optional androidx.compose.material3.NavigationDrawerItemColors navigationDrawerItemColors);
     field public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteItemColors {
+  public final class NavigationSuiteItemColors {
     ctor public NavigationSuiteItemColors(androidx.compose.material3.NavigationBarItemColors navigationBarItemColors, androidx.compose.material3.NavigationRailItemColors navigationRailItemColors, androidx.compose.material3.NavigationDrawerItemColors navigationDrawerItemColors);
     method public androidx.compose.material3.NavigationBarItemColors getNavigationBarItemColors();
     method public androidx.compose.material3.NavigationDrawerItemColors getNavigationDrawerItemColors();
@@ -35,7 +35,7 @@
     property public final androidx.compose.material3.NavigationRailItemColors navigationRailItemColors;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteScaffoldDefaults {
+  public final class NavigationSuiteScaffoldDefaults {
     method public String calculateFromAdaptiveInfo(androidx.compose.material3.adaptive.WindowAdaptiveInfo adaptiveInfo);
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method @androidx.compose.runtime.Composable public long getContentColor();
@@ -45,16 +45,16 @@
   }
 
   public final class NavigationSuiteScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public sealed interface NavigationSuiteScope {
+  public sealed interface NavigationSuiteScope {
     method public void item(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional boolean alwaysShowLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors? colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @kotlin.jvm.JvmInline public final value class NavigationSuiteType {
+  @kotlin.jvm.JvmInline public final value class NavigationSuiteType {
     field public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType.Companion Companion;
   }
 
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
index 7a7bc4a..83d23c0 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
@@ -4,7 +4,7 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive-navigation-suite API is experimental and is likely to" + "change or to be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveNavigationSuiteApi {
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteColors {
+  public final class NavigationSuiteColors {
     method public long getNavigationBarContainerColor();
     method public long getNavigationBarContentColor();
     method public long getNavigationDrawerContainerColor();
@@ -19,13 +19,13 @@
     property public final long navigationRailContentColor;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteDefaults {
+  public final class NavigationSuiteDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors(optional long navigationBarContainerColor, optional long navigationBarContentColor, optional long navigationRailContainerColor, optional long navigationRailContentColor, optional long navigationDrawerContainerColor, optional long navigationDrawerContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors itemColors(optional androidx.compose.material3.NavigationBarItemColors navigationBarItemColors, optional androidx.compose.material3.NavigationRailItemColors navigationRailItemColors, optional androidx.compose.material3.NavigationDrawerItemColors navigationDrawerItemColors);
     field public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteItemColors {
+  public final class NavigationSuiteItemColors {
     ctor public NavigationSuiteItemColors(androidx.compose.material3.NavigationBarItemColors navigationBarItemColors, androidx.compose.material3.NavigationRailItemColors navigationRailItemColors, androidx.compose.material3.NavigationDrawerItemColors navigationDrawerItemColors);
     method public androidx.compose.material3.NavigationBarItemColors getNavigationBarItemColors();
     method public androidx.compose.material3.NavigationDrawerItemColors getNavigationDrawerItemColors();
@@ -35,7 +35,7 @@
     property public final androidx.compose.material3.NavigationRailItemColors navigationRailItemColors;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public final class NavigationSuiteScaffoldDefaults {
+  public final class NavigationSuiteScaffoldDefaults {
     method public String calculateFromAdaptiveInfo(androidx.compose.material3.adaptive.WindowAdaptiveInfo adaptiveInfo);
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method @androidx.compose.runtime.Composable public long getContentColor();
@@ -45,16 +45,16 @@
   }
 
   public final class NavigationSuiteScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteColors navigationSuiteColors, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi public sealed interface NavigationSuiteScope {
+  public sealed interface NavigationSuiteScope {
     method public void item(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional boolean alwaysShowLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors? colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi @kotlin.jvm.JvmInline public final value class NavigationSuiteType {
+  @kotlin.jvm.JvmInline public final value class NavigationSuiteType {
     field public static final androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType.Companion Companion;
   }
 
diff --git a/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt b/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
index 45d63a4..8326319 100644
--- a/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/samples/src/main/java/androidx/compose/material3/adaptive/navigationsuite/samples/NavigationSuiteScaffoldSamples.kt
@@ -27,7 +27,6 @@
 import androidx.compose.material3.Text
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
-import androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi
 import androidx.compose.material3.adaptive.navigationsuite.NavigationSuite
 import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
 import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults
@@ -43,10 +42,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.window.core.layout.WindowWidthSizeClass
 
-@OptIn(
-    ExperimentalMaterial3AdaptiveApi::class,
-    ExperimentalMaterial3AdaptiveNavigationSuiteApi::class
-)
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Preview
 @Sampled
 @Composable
@@ -76,10 +72,7 @@
     }
 }
 
-@OptIn(
-    ExperimentalMaterial3AdaptiveApi::class,
-    ExperimentalMaterial3AdaptiveNavigationSuiteApi::class
-)
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Preview
 @Sampled
 @Composable
@@ -117,10 +110,7 @@
     }
 }
 
-@OptIn(
-    ExperimentalMaterial3AdaptiveApi::class,
-    ExperimentalMaterial3AdaptiveNavigationSuiteApi::class
-)
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Preview
 @Sampled
 @Composable
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
index 737aa7f..11dfa5f 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
@@ -34,7 +34,6 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 @RunWith(JUnit4::class)
 class NavigationSuiteScaffoldTest {
     @get:Rule
@@ -92,7 +91,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 @Composable
 private fun SampleNavigationSuiteScaffoldLayout(
     layoutType: NavigationSuiteType
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteTest.kt b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteTest.kt
index df34599..ea3718c 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteTest.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteTest.kt
@@ -28,7 +28,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class NavigationSuiteTest {
@@ -47,7 +46,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 @Composable
 private fun SampleNavigationSuite(
     layoutType: NavigationSuiteType
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt b/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
index 0183840..12ec173 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffoldTest.kt
@@ -25,8 +25,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class,
-    ExperimentalMaterial3AdaptiveApi::class)
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @RunWith(JUnit4::class)
 class NavigationSuiteScaffoldTest {
 
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
index e2055fb..b951ec3 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigationsuite/NavigationSuiteScaffold.kt
@@ -42,7 +42,6 @@
 import androidx.compose.material3.PermanentDrawerSheet
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.WindowAdaptiveInfo
 import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
 import androidx.compose.material3.contentColorFor
@@ -84,8 +83,6 @@
  * passed in [content] lambda inside the navigation suite scaffold.
  * @param content the content of your screen
  */
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 @Composable
 fun NavigationSuiteScaffold(
     navigationSuiteItems: NavigationSuiteScope.() -> Unit,
@@ -142,8 +139,6 @@
  * [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
  * @param content the content of your screen
  */
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 @Composable
 fun NavigationSuiteScaffoldLayout(
     navigationSuite: @Composable () -> Unit,
@@ -221,8 +216,6 @@
  * @param content the content inside the current navigation component, typically
  * [NavigationSuiteScope.item]s
  */
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 @Composable
 fun NavigationSuite(
     modifier: Modifier = Modifier,
@@ -310,7 +303,6 @@
 }
 
 /** The scope associated with the [NavigationSuiteScope]. */
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 sealed interface NavigationSuiteScope {
 
     /**
@@ -360,7 +352,6 @@
  * The [NavigationSuiteType] informs the [NavigationSuite] of what navigation component to expect.
  */
 @JvmInline
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 value class NavigationSuiteType private constructor(private val description: String) {
     override fun toString(): String {
         return description
@@ -400,7 +391,6 @@
 }
 
 /** Contains the default values used by the [NavigationSuiteScaffold]. */
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 object NavigationSuiteScaffoldDefaults {
     /**
      * Returns the expected [NavigationSuiteType] according to the provided [WindowAdaptiveInfo].
@@ -409,7 +399,6 @@
      * @param adaptiveInfo the provided [WindowAdaptiveInfo]
      * @see NavigationSuiteScaffold
      */
-    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
     fun calculateFromAdaptiveInfo(adaptiveInfo: WindowAdaptiveInfo): NavigationSuiteType {
         return with(adaptiveInfo) {
             if (windowPosture.isTabletop ||
@@ -434,7 +423,6 @@
 }
 
 /** Contains the default values used by the [NavigationSuite]. */
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 object NavigationSuiteDefaults {
     /**
      * Creates a [NavigationSuiteColors] with the provided colors for the container color, according
@@ -519,7 +507,6 @@
  * @param navigationDrawerContentColor the content color for the [PermanentDrawerSheet] of the
  * [NavigationSuite]
  */
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
 class NavigationSuiteColors
 internal constructor(
     val navigationBarContainerColor: Color,
@@ -543,14 +530,12 @@
  * @param navigationDrawerItemColors the [NavigationDrawerItemColors] associated with the
  * [NavigationDrawerItem] of the [NavigationSuiteScope.item]
  */
-@ExperimentalMaterial3AdaptiveNavigationSuiteApi
-class NavigationSuiteItemColors constructor(
+class NavigationSuiteItemColors(
     val navigationBarItemColors: NavigationBarItemColors,
     val navigationRailItemColors: NavigationRailItemColors,
     val navigationDrawerItemColors: NavigationDrawerItemColors,
 )
 
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
 internal val WindowAdaptiveInfoDefault
     @Composable
     get() = currentWindowAdaptiveInfo()
@@ -560,7 +545,6 @@
     val itemList: MutableVector<NavigationSuiteItem>
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 private class NavigationSuiteItem(
     val selected: Boolean,
     val onClick: () -> Unit,
@@ -575,7 +559,6 @@
     val interactionSource: MutableInteractionSource
 )
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 private class NavigationSuiteScopeImpl : NavigationSuiteScope,
     NavigationSuiteItemProvider {
 
@@ -614,7 +597,6 @@
         get() = itemList.size
 }
 
-@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
 @Composable
 private fun rememberStateOfItems(
     content: NavigationSuiteScope.() -> Unit
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 26756e5..cfed154 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -996,22 +996,24 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class ModalBottomSheetDefaults {
-    method public androidx.compose.material3.ModalBottomSheetProperties properties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean isFocusable, optional boolean shouldDismissOnBackPress);
+    method public androidx.compose.material3.ModalBottomSheetProperties getProperties();
+    method @Deprecated public androidx.compose.material3.ModalBottomSheetProperties properties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean isFocusable, optional boolean shouldDismissOnBackPress);
+    property public final androidx.compose.material3.ModalBottomSheetProperties properties;
     field public static final androidx.compose.material3.ModalBottomSheetDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class ModalBottomSheetProperties {
-    ctor public ModalBottomSheetProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy, boolean isFocusable, boolean shouldDismissOnBackPress);
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class ModalBottomSheetProperties {
+    ctor public ModalBottomSheetProperties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean shouldDismissOnBackPress);
+    ctor @Deprecated public ModalBottomSheetProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy, boolean isFocusable, boolean shouldDismissOnBackPress);
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
     method public boolean getShouldDismissOnBackPress();
-    method public boolean isFocusable();
-    property public final boolean isFocusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
     property public final boolean shouldDismissOnBackPress;
   }
 
   public final class ModalBottomSheet_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional kotlin.jvm.functions.Function0<? extends androidx.compose.foundation.layout.WindowInsets> contentWindowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberModalBottomSheetState(optional boolean skipPartiallyExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
   }
 
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 26756e5..cfed154 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -996,22 +996,24 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class ModalBottomSheetDefaults {
-    method public androidx.compose.material3.ModalBottomSheetProperties properties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean isFocusable, optional boolean shouldDismissOnBackPress);
+    method public androidx.compose.material3.ModalBottomSheetProperties getProperties();
+    method @Deprecated public androidx.compose.material3.ModalBottomSheetProperties properties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean isFocusable, optional boolean shouldDismissOnBackPress);
+    property public final androidx.compose.material3.ModalBottomSheetProperties properties;
     field public static final androidx.compose.material3.ModalBottomSheetDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class ModalBottomSheetProperties {
-    ctor public ModalBottomSheetProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy, boolean isFocusable, boolean shouldDismissOnBackPress);
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class ModalBottomSheetProperties {
+    ctor public ModalBottomSheetProperties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean shouldDismissOnBackPress);
+    ctor @Deprecated public ModalBottomSheetProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy, boolean isFocusable, boolean shouldDismissOnBackPress);
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
     method public boolean getShouldDismissOnBackPress();
-    method public boolean isFocusable();
-    property public final boolean isFocusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
     property public final boolean shouldDismissOnBackPress;
   }
 
   public final class ModalBottomSheet_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional kotlin.jvm.functions.Function0<? extends androidx.compose.foundation.layout.WindowInsets> contentWindowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberModalBottomSheetState(optional boolean skipPartiallyExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
   }
 
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogScaffold.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogScaffold.kt
index 6156e44..7a50700 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogScaffold.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogScaffold.kt
@@ -17,7 +17,6 @@
 package androidx.compose.material3.catalog.library.ui.common
 
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ModalBottomSheet
 import androidx.compose.material3.Scaffold
@@ -93,7 +92,6 @@
         ModalBottomSheet(
             onDismissRequest = { openThemePicker = false },
             sheetState = sheetState,
-            windowInsets = WindowInsets(0),
             content = {
                 ThemePicker(
                     theme = theme,
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt
index ae1e46b..b720202 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt
@@ -23,7 +23,6 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
@@ -34,7 +33,6 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.filled.Menu
-import androidx.compose.material3.BottomSheetDefaults
 import androidx.compose.material3.BottomSheetScaffold
 import androidx.compose.material3.Button
 import androidx.compose.material3.Checkbox
@@ -73,8 +71,7 @@
 @Composable
 fun ModalBottomSheetSample() {
     var openBottomSheet by rememberSaveable { mutableStateOf(false) }
-    var skipPartiallyExpanded by remember { mutableStateOf(false) }
-    var edgeToEdgeEnabled by remember { mutableStateOf(false) }
+    var skipPartiallyExpanded by rememberSaveable { mutableStateOf(false) }
     val scope = rememberCoroutineScope()
     val bottomSheetState = rememberModalBottomSheetState(
         skipPartiallyExpanded = skipPartiallyExpanded
@@ -96,17 +93,6 @@
             Spacer(Modifier.width(16.dp))
             Text("Skip partially expanded State")
         }
-        Row(
-            Modifier.toggleable(
-                value = edgeToEdgeEnabled,
-                role = Role.Checkbox,
-                onValueChange = { checked -> edgeToEdgeEnabled = checked }
-            )
-        ) {
-            Checkbox(checked = edgeToEdgeEnabled, onCheckedChange = null)
-            Spacer(Modifier.width(16.dp))
-            Text("Toggle edge to edge enabled")
-        }
         Button(
             onClick = { openBottomSheet = !openBottomSheet },
             modifier = Modifier.align(Alignment.CenterHorizontally)
@@ -117,15 +103,11 @@
 
     // Sheet content
     if (openBottomSheet) {
-        val windowInsets = if (edgeToEdgeEnabled)
-            WindowInsets(0) else BottomSheetDefaults.windowInsets
 
         ModalBottomSheet(
             onDismissRequest = { openBottomSheet = false },
             sheetState = bottomSheetState,
-            windowInsets = windowInsets
         ) {
-
             Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
                 Button(
                     // Note: If you provide logic outside of onDismissRequest to remove the sheet,
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
index 34aa79f..d763b89 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
@@ -137,7 +137,9 @@
                 // launch some business logic update with the state you hold
                 // viewModel.updateSelectedSliderValue(sliderPosition)
             },
-            steps = 4
+            // Only allow multiples of 10. Excluding the endpoints of `valueRange`,
+            // there are 9 steps (10, 20, ..., 90).
+            steps = 9
         )
     }
 }
@@ -344,7 +346,9 @@
                 // launch some business logic update with the state you hold
                 // viewModel.updateSelectedSliderValue(sliderPosition)
             },
-            steps = 4
+            // Only allow multiples of 10. Excluding the endpoints of `valueRange`,
+            // there are 9 steps (10, 20, ..., 90).
+            steps = 9
         )
     }
     Column(modifier = Modifier.padding(horizontal = 16.dp)) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index a3464b2..6bdf964 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -57,11 +57,12 @@
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHeightIsEqualTo
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.getUnclippedBoundsInRoot
-import androidx.compose.ui.test.isPopup
+import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onFirst
 import androidx.compose.ui.test.onNodeWithTag
@@ -79,6 +80,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.height
 import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -96,12 +98,11 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
 
 @MediumTest
-@RunWith(Parameterized::class)
+@RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalMaterial3Api::class)
-class ModalBottomSheetTest(private val edgeToEdgeWrapper: EdgeToEdgeWrapper) {
+class ModalBottomSheetTest {
 
     @get:Rule
     val rule = createAndroidComposeRule<ComponentActivity>()
@@ -119,14 +120,10 @@
         val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
 
         rule.setContent {
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             if (showBottomSheet) {
                 ModalBottomSheet(
                     sheetState = sheetState,
                     onDismissRequest = { showBottomSheet = false },
-                    windowInsets = windowInsets
                 ) {
                     Box(
                         Modifier
@@ -141,7 +138,7 @@
 
         // Tap Scrim
         val outsideY = with(rule.density) {
-            rule.onAllNodes(isPopup()).onFirst().getUnclippedBoundsInRoot().height.roundToPx() / 4
+            rule.onAllNodes(isDialog()).onFirst().getUnclippedBoundsInRoot().height.roundToPx() / 4
         }
         UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).click(0, outsideY)
         rule.waitForIdle()
@@ -156,14 +153,10 @@
         val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
 
         rule.setContent {
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             if (showBottomSheet) {
                 ModalBottomSheet(
                     sheetState = sheetState,
                     onDismissRequest = { showBottomSheet = false },
-                    windowInsets = windowInsets
                 ) {
                     Box(
                         Modifier
@@ -194,12 +187,9 @@
         rule.setContent {
             val context = LocalContext.current
             screenWidth = context.resources.displayMetrics.widthPixels
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
 
             ModalBottomSheet(
                 onDismissRequest = {},
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -235,11 +225,8 @@
         try {
             latch.await(1500, TimeUnit.MILLISECONDS)
             rule.setContent {
-                val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                    WindowInsets(0) else BottomSheetDefaults.windowInsets
                 ModalBottomSheet(
                     onDismissRequest = {},
-                    windowInsets = windowInsets
                 ) {
                     Box(
                         Modifier
@@ -249,7 +236,7 @@
                 }
             }
             rule.waitForIdle()
-            val simulatedRootWidth = rule.onNode(isPopup()).getUnclippedBoundsInRoot().width
+            val simulatedRootWidth = rule.onNode(isDialog()).getUnclippedBoundsInRoot().width
             val maxSheetWidth = 640.dp
             val expectedSheetWidth = maxSheetWidth.coerceAtMost(simulatedRootWidth)
             // Our sheet should be max 640 dp but fill the width if the container is less wide
@@ -299,12 +286,9 @@
             rule.setContent {
                 val context = LocalContext.current
                 screenWidthPx = context.resources.displayMetrics.widthPixels
-                val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                    WindowInsets(0) else BottomSheetDefaults.windowInsets
                 ModalBottomSheet(
                     onDismissRequest = {},
                     sheetMaxWidth = Dp.Unspecified,
-                    windowInsets = windowInsets
                 ) {
                     Box(
                         Modifier
@@ -330,17 +314,13 @@
         var height by mutableStateOf(0.dp)
 
         rule.setContent {
-            val config = LocalContext.current.resources.configuration
-            height = config.screenHeightDp.dp
             sheetState = rememberModalBottomSheetState()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
 
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
                 dragHandle = null,
-                windowInsets = windowInsets
+                contentWindowInsets = { WindowInsets(0) }
             ) {
                 Box(
                     Modifier
@@ -351,7 +331,7 @@
             }
         }
 
-        height = rule.onNode(isPopup()).getUnclippedBoundsInRoot().height
+        height = rule.onNode(isDialog()).getUnclippedBoundsInRoot().height
         assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
         rule.onNodeWithTag(sheetTag).assertTopPositionInRootIsEqualTo(height - sheetHeight)
     }
@@ -363,12 +343,9 @@
 
         rule.setContent {
             sheetState = rememberModalBottomSheetState()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -380,7 +357,7 @@
         }
         rule.waitForIdle()
         screenHeightPx = with(rule.density) {
-            rule.onNode(isPopup()).getUnclippedBoundsInRoot().height.toPx()
+            rule.onNode(isDialog()).getUnclippedBoundsInRoot().height.toPx()
         }
         assertThat(sheetState.targetValue).isEqualTo(SheetValue.PartiallyExpanded)
         assertThat(sheetState.requireOffset())
@@ -395,13 +372,10 @@
 
         rule.setContent {
             val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             if (showBottomSheet) {
                 ModalBottomSheet(
                     sheetState = sheetState,
                     onDismissRequest = { showBottomSheet = false },
-                    windowInsets = windowInsets
                 ) {
                     Box(
                         Modifier
@@ -435,13 +409,10 @@
 
         rule.setContent {
             val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             if (showBottomSheet) {
                 ModalBottomSheet(
                     sheetState = sheetState,
                     onDismissRequest = { showBottomSheet = false },
-                    windowInsets = windowInsets
                 ) {
                     Box(
                         Modifier
@@ -481,13 +452,11 @@
             val context = LocalContext.current
             screenHeight = context.resources.configuration.screenHeightDp.dp
             state = rememberModalBottomSheetState()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = state,
                 dragHandle = null,
-                windowInsets = windowInsets
+                contentWindowInsets = { WindowInsets(0) }
             ) {
                 Box(
                     Modifier
@@ -496,7 +465,7 @@
                 )
             }
         }
-        screenHeight = rule.onNode(isPopup()).getUnclippedBoundsInRoot().height
+        screenHeight = rule.onNode(isDialog()).getUnclippedBoundsInRoot().height
         assertThat(state.requireOffset()).isWithin(1f).of(expectedExpandedAnchor)
 
         size = 100.dp
@@ -513,8 +482,6 @@
         lateinit var sheetMaxWidth: MutableState<Dp>
         var screenWidth by mutableStateOf(0.dp)
         rule.setContent {
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             sheetMaxWidth = remember { mutableStateOf(0.dp) }
             val context = LocalContext.current
             val density = LocalDensity.current
@@ -522,7 +489,6 @@
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetMaxWidth = sheetMaxWidth.value,
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -547,14 +513,12 @@
         rule.setContent {
             state = rememberModalBottomSheetState()
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
 
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = state,
                 dragHandle = null,
-                windowInsets = windowInsets
+                contentWindowInsets = { WindowInsets(0) }
             ) {}
         }
         assertThat(state.anchoredDraggableState.currentValue).isEqualTo(SheetValue.Hidden)
@@ -575,14 +539,11 @@
         lateinit var scope: CoroutineScope
         rule.setContent {
             state = rememberModalBottomSheetState()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = state,
                 dragHandle = null,
-                windowInsets = windowInsets
+                contentWindowInsets = { WindowInsets(0) }
             ) {
                 scope = rememberCoroutineScope()
                 LazyColumn {
@@ -630,7 +591,7 @@
             sheetState = rememberModalBottomSheetState()
             ModalBottomSheet(
                 onDismissRequest = {},
-                sheetState = sheetState
+                sheetState = sheetState,
             ) {
                 scrollState = rememberScrollState()
                 Column(
@@ -700,14 +661,10 @@
 
         rule.setContent {
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             ModalBottomSheet(
                 onDismissRequest = {},
                 modifier = Modifier.testTag(topTag),
                 sheetState = sheetState,
-                windowInsets = windowInsets
             ) {
                 if (showShortContent) {
                     Box(
@@ -748,12 +705,9 @@
         lateinit var sheetState: SheetState
         rule.setContent {
             sheetState = rememberModalBottomSheetState()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -784,8 +738,6 @@
                     newState != SheetValue.Hidden
                 }
             )
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
 
             ModalBottomSheet(
                 onDismissRequest = {},
@@ -797,7 +749,6 @@
                             .size(dragHandleSize)
                     )
                 },
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -827,7 +778,7 @@
 
         // Tap Scrim
         val outsideY = with(rule.density) {
-            rule.onAllNodes(isPopup()).onFirst().getUnclippedBoundsInRoot().height.roundToPx() / 4
+            rule.onAllNodes(isDialog()).onFirst().getUnclippedBoundsInRoot().height.roundToPx() / 4
         }
         UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).click(0, outsideY)
         rule.waitForIdle()
@@ -843,12 +794,9 @@
         rule.setContent {
             sheetState = rememberModalBottomSheetState()
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -880,13 +828,10 @@
         lateinit var sheetState: SheetState
         rule.setContent {
             sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
 
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -916,12 +861,9 @@
         lateinit var sheetState: SheetState
         rule.setContent {
             sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -946,13 +888,9 @@
         )
         rule.setContent {
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = bottomSheetState,
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -976,8 +914,6 @@
     @Test
     fun modalBottomSheet_testDismissAction_tallBottomSheet_whenPartiallyExpanded() {
         rule.setContent {
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             ModalBottomSheet(
                 onDismissRequest = {},
                 dragHandle = {
@@ -987,7 +923,6 @@
                             .size(dragHandleSize)
                     )
                 },
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -1009,8 +944,6 @@
         lateinit var sheetState: SheetState
         rule.setContent {
             sheetState = rememberModalBottomSheetState()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
@@ -1021,7 +954,6 @@
                             .size(dragHandleSize)
                     )
                 },
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -1052,9 +984,6 @@
         rule.setContent {
             sheetState = rememberModalBottomSheetState()
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
@@ -1065,7 +994,6 @@
                             .size(dragHandleSize)
                     )
                 },
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -1075,7 +1003,7 @@
             }
         }
         screenHeightPx = with(rule.density) {
-            rule.onNode(isPopup()).getUnclippedBoundsInRoot().height.toPx()
+            rule.onNode(isDialog()).getUnclippedBoundsInRoot().height.toPx()
         }
         scope.launch {
             sheetState.expand()
@@ -1103,9 +1031,6 @@
         rule.setContent {
             sheetState = rememberModalBottomSheetState()
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
@@ -1116,7 +1041,6 @@
                             .size(dragHandleSize)
                     )
                 },
-                windowInsets = windowInsets
             ) {
                 Box(
                     Modifier
@@ -1126,7 +1050,7 @@
             }
         }
         screenHeightPx = with(rule.density) {
-            rule.onNode(isPopup()).getUnclippedBoundsInRoot().height.toPx()
+            rule.onNode(isDialog()).getUnclippedBoundsInRoot().height.toPx()
         }
         scope.launch {
             sheetState.expand()
@@ -1151,14 +1075,12 @@
         lateinit var scope: CoroutineScope
         rule.setContent {
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
 
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
                 dragHandle = null,
-                windowInsets = windowInsets
+                contentWindowInsets = { WindowInsets(0) }
             ) {
                 if (hasSheetContent) {
                     Box(Modifier.fillMaxHeight(0.4f))
@@ -1190,14 +1112,11 @@
         lateinit var scope: CoroutineScope
         rule.setContent {
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             ModalBottomSheet(
                 onDismissRequest = {},
                 sheetState = sheetState,
                 dragHandle = null,
-                windowInsets = windowInsets
+                contentWindowInsets = { WindowInsets(0) }
             ) {
                 if (hasSheetContent) {
                     Box(Modifier.fillMaxHeight(0.6f))
@@ -1236,13 +1155,9 @@
 
         rule.setContent {
             scope = rememberCoroutineScope()
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
-
             ModalBottomSheet(
                 onDismissRequest = { callCount += 1 },
                 sheetState = sheetState,
-                windowInsets = windowInsets
             ) {
                 Column(
                     Modifier
@@ -1288,14 +1203,10 @@
 
         lateinit var sheetState: SheetState
         rule.setContent {
-            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
-                WindowInsets(0) else BottomSheetDefaults.windowInsets
             sheetState = rememberModalBottomSheetState()
             ModalBottomSheet(
                 sheetState = sheetState,
                 onDismissRequest = {},
-                windowInsets = windowInsets,
-                properties = ModalBottomSheetDefaults.properties(isFocusable = true)
             ) {
                 Box(Modifier.testTag(sheetTag)) {
                     TextField(
@@ -1311,7 +1222,7 @@
         rule.mainClock.autoAdvance = false
 
         val textFieldNode = rule.onNodeWithTag(textFieldTag)
-        var sheetNode = rule.onNodeWithTag(sheetTag)
+        val sheetNode = rule.onNodeWithTag(sheetTag)
         val initialTop = sheetNode.getUnclippedBoundsInRoot().top
 
         // Focus on the text field to force ime visibility.
@@ -1331,7 +1242,9 @@
         var value = LayoutDirection.Ltr
         rule.setContent {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-                ModalBottomSheet(onDismissRequest = { /*TODO*/ }) {
+                ModalBottomSheet(
+                    onDismissRequest = { /*TODO*/ },
+                ) {
                     value = LocalLayoutDirection.current
                 }
             }
@@ -1341,18 +1254,18 @@
         }
     }
 
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun parameters() = arrayOf(
-            EdgeToEdgeWrapper("EdgeToEdge", true),
-            EdgeToEdgeWrapper("NonEdgeToEdge", false)
-        )
-    }
-
-    class EdgeToEdgeWrapper(val name: String, val edgeToEdgeEnabled: Boolean) {
-        override fun toString(): String {
-            return name
+    @Test
+    fun modalBottomSheetContent_respectsProvidedInsets() {
+        rule.setContent {
+            ModalBottomSheet(
+                onDismissRequest = { /*TODO*/ },
+                dragHandle = {},
+                contentWindowInsets = { WindowInsets(bottom = sheetHeight) },
+                ) {
+                Box(Modifier.testTag(sheetTag))
+            }
         }
+        // Size entirely filled by padding provided by WindowInsetPadding
+        rule.onNodeWithTag(sheetTag).onParent().assertHeightIsEqualTo(sheetHeight)
     }
 }
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index 9bfec4d..e441735 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -17,17 +17,20 @@
 package androidx.compose.material3
 
 import android.content.Context
-import android.graphics.PixelFormat
+import android.graphics.Outline
 import android.os.Build
-import android.view.Gravity
-import android.view.KeyEvent
+import android.view.ContextThemeWrapper
+import android.view.MotionEvent
 import android.view.View
-import android.view.ViewTreeObserver
+import android.view.ViewOutlineProvider
+import android.view.Window
 import android.view.WindowManager
 import android.window.BackEvent
 import android.window.OnBackAnimationCallback
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
+import androidx.activity.ComponentDialog
+import androidx.activity.addCallback
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.animation.core.Animatable
@@ -62,6 +65,7 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -72,6 +76,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.R
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.graphics.Shape
@@ -81,21 +86,25 @@
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.ViewRootForInspector
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.collapse
+import androidx.compose.ui.semantics.dialog
 import androidx.compose.ui.semantics.dismiss
 import androidx.compose.ui.semantics.expand
 import androidx.compose.ui.semantics.paneTitle
-import androidx.compose.ui.semantics.popup
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
+import androidx.compose.ui.window.DialogWindowProvider
 import androidx.compose.ui.window.SecureFlagPolicy
+import androidx.core.view.WindowCompat
 import androidx.lifecycle.findViewTreeLifecycleOwner
 import androidx.lifecycle.findViewTreeViewModelStoreOwner
 import androidx.lifecycle.setViewTreeLifecycleOwner
@@ -139,10 +148,10 @@
  * darker color in light theme and lighter color in dark theme. See also: [Surface].
  * @param scrimColor Color of the scrim that obscures content when the bottom sheet is open.
  * @param dragHandle Optional visual marker to swipe the bottom sheet.
- * @param windowInsets window insets to be passed to the bottom sheet window via [PaddingValues]
+ * @param contentWindowInsets window insets to be passed to the bottom sheet content via [PaddingValues]
  * params.
  * @param properties [ModalBottomSheetProperties] for further customization of this
- * modal bottom sheet's behavior.
+ * modal bottom sheet's window behavior.
  * @param content The content to be displayed inside the bottom sheet.
  */
 @Composable
@@ -158,8 +167,8 @@
     tonalElevation: Dp = 0.dp,
     scrimColor: Color = BottomSheetDefaults.ScrimColor,
     dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
-    windowInsets: WindowInsets = BottomSheetDefaults.windowInsets,
-    properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties(),
+    contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
+    properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties,
     content: @Composable ColumnScope.() -> Unit,
 ) {
     val scope = rememberCoroutineScope()
@@ -180,7 +189,7 @@
 
     val predictiveBackProgress = remember { Animatable(initialValue = 0f) }
 
-    ModalBottomSheetPopup(
+    ModalBottomSheetDialog(
         properties = properties,
         onDismissRequest = {
             if (sheetState.currentValue == Expanded && sheetState.hasPartiallyExpandedState) {
@@ -194,9 +203,13 @@
             }
         },
         predictiveBackProgress = predictiveBackProgress,
-        windowInsets = windowInsets,
     ) {
-        Box(Modifier.fillMaxSize(), propagateMinConstraints = false) {
+        Box(
+            modifier = Modifier
+                .fillMaxSize()
+                .imePadding(),
+            propagateMinConstraints = false
+        ) {
             Scrim(
                 color = scrimColor,
                 onDismissRequest = animateToDismiss,
@@ -215,6 +228,7 @@
                 contentColor,
                 tonalElevation,
                 dragHandle,
+                contentWindowInsets,
                 content
             )
         }
@@ -241,6 +255,7 @@
     contentColor: Color = contentColorFor(containerColor),
     tonalElevation: Dp = BottomSheetDefaults.Elevation,
     dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
+    contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
     content: @Composable ColumnScope.() -> Unit
 ) {
     val bottomSheetPaneTitle = getString(string = Strings.BottomSheetPaneTitle)
@@ -314,6 +329,7 @@
         Column(
             Modifier
                 .fillMaxWidth()
+                .windowInsetsPadding(contentWindowInsets())
                 .graphicsLayer {
                     val progress = predictiveBackProgress.value
                     val predictiveBackScaleX = calculatePredictiveBackScaleX(progress)
@@ -392,39 +408,46 @@
     }
 }
 
+// Logic forked from androidx.compose.ui.window.DialogProperties. Removed dismissOnClickOutside
+// and usePlatformDefaultWidth as they are not relevant for fullscreen experience.
 /**
  * Properties used to customize the behavior of a [ModalBottomSheet].
  *
  * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom
  * sheet's window.
- * @param isFocusable Whether the modal bottom sheet is focusable. When true,
- * the modal bottom sheet will receive IME events and key presses, such as when
- * the back button is pressed.
  * @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing
  * the back button. If true, pressing the back button will call onDismissRequest.
- * Note that [isFocusable] must be set to true in order to receive key events such as
- * the back button - if the modal bottom sheet is not focusable then this property does nothing.
  */
+@Immutable
 @ExperimentalMaterial3Api
 class ModalBottomSheetProperties(
-    val securePolicy: SecureFlagPolicy,
-    val isFocusable: Boolean,
-    val shouldDismissOnBackPress: Boolean
+    val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
+    val shouldDismissOnBackPress: Boolean = true,
 ) {
+    @Deprecated(
+        message = "'isFocusable' param is no longer used. Use constructor without this parameter.",
+        level = DeprecationLevel.WARNING,
+        replaceWith = ReplaceWith(
+            "ModalBottomSheetProperties(securePolicy, shouldDismissOnBackPress)"
+        )
+    )
+    @Suppress("UNUSED_PARAMETER")
+    constructor(
+        securePolicy: SecureFlagPolicy,
+        isFocusable: Boolean,
+        shouldDismissOnBackPress: Boolean,
+    ) : this(securePolicy, shouldDismissOnBackPress)
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is ModalBottomSheetProperties) return false
-
         if (securePolicy != other.securePolicy) return false
-        if (isFocusable != other.isFocusable) return false
-        if (shouldDismissOnBackPress != other.shouldDismissOnBackPress) return false
 
         return true
     }
 
     override fun hashCode(): Int {
         var result = securePolicy.hashCode()
-        result = 31 * result + isFocusable.hashCode()
         result = 31 * result + shouldDismissOnBackPress.hashCode()
         return result
     }
@@ -436,6 +459,11 @@
 @Immutable
 @ExperimentalMaterial3Api
 object ModalBottomSheetDefaults {
+
+    /**
+     * Properties used to customize the behavior of a [ModalBottomSheet]. */
+    val properties = ModalBottomSheetProperties()
+
     /**
      * Properties used to customize the behavior of a [ModalBottomSheet].
      *
@@ -449,11 +477,17 @@
      * Note that [isFocusable] must be set to true in order to receive key events such as
      * the back button - if the modal bottom sheet is not focusable then this property does nothing.
      */
+    @Deprecated(
+        level = DeprecationLevel.WARNING,
+        message = "'isFocusable' param is no longer used. Use value without this parameter.",
+        replaceWith = ReplaceWith("properties")
+    )
+    @Suppress("UNUSED_PARAMETER")
     fun properties(
         securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
         isFocusable: Boolean = true,
         shouldDismissOnBackPress: Boolean = true
-    ) = ModalBottomSheetProperties(securePolicy, isFocusable, shouldDismissOnBackPress)
+    ) = ModalBottomSheetProperties(securePolicy, shouldDismissOnBackPress)
 }
 
 /**
@@ -475,6 +509,95 @@
     initialValue = Hidden,
 )
 
+/**
+ * <a href="https://m3.material.io/components/bottom-sheets/overview" class="external" target="_blank">Material Design modal bottom sheet</a>.
+ *
+ * Modal bottom sheets are used as an alternative to inline menus or simple dialogs on mobile,
+ * especially when offering a long list of action items, or when items require longer descriptions
+ * and icons. Like dialogs, modal bottom sheets appear in front of app content, disabling all other
+ * app functionality when they appear, and remaining on screen until confirmed, dismissed, or a
+ * required action has been taken.
+ *
+ * ![Bottom sheet image](https://developer.android.com/images/reference/androidx/compose/material3/bottom_sheet.png)
+ *
+ * A simple example of a modal bottom sheet looks like this:
+ *
+ * @sample androidx.compose.material3.samples.ModalBottomSheetSample
+ *
+ * @param onDismissRequest Executes when the user clicks outside of the bottom sheet, after sheet
+ * animates to [Hidden].
+ * @param modifier Optional [Modifier] for the bottom sheet.
+ * @param sheetState The state of the bottom sheet.
+ * @param sheetMaxWidth [Dp] that defines what the maximum width the sheet will take.
+ * Pass in [Dp.Unspecified] for a sheet that spans the entire screen width.
+ * @param shape The shape of the bottom sheet.
+ * @param containerColor The color used for the background of this bottom sheet
+ * @param contentColor The preferred color for content inside this bottom sheet. Defaults to either
+ * the matching content color for [containerColor], or to the current [LocalContentColor] if
+ * [containerColor] is not a color from the theme.
+ * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
+ * overlay is applied on top of the container. A higher tonal elevation value will result in a
+ * darker color in light theme and lighter color in dark theme. See also: [Surface].
+ * @param scrimColor Color of the scrim that obscures content when the bottom sheet is open.
+ * @param dragHandle Optional visual marker to swipe the bottom sheet.
+ * @param windowInsets window insets to be passed to the bottom sheet content via [PaddingValues]
+ * params.
+ * @param properties [ModalBottomSheetProperties] for further customization of this
+ * modal bottom sheet's window behavior.
+ * @param content The content to be displayed inside the bottom sheet.
+ */
+@Composable
+@ExperimentalMaterial3Api
+@Deprecated(
+    level = DeprecationLevel.HIDDEN,
+    message = "Use constructor with contentWindowInsets parameter.",
+    replaceWith = ReplaceWith(
+        "ModalBottomSheet(" +
+            "onDismissRequest," +
+            "modifier," +
+            "sheetState," +
+            "sheetMaxWidth," +
+            "shape," +
+            "containerColor," +
+            "contentColor," +
+            "tonalElevation," +
+            "scrimColor," +
+            "dragHandle," +
+            "{ windowInsets }," +
+            "properties," +
+            "content," +
+            ")")
+)
+fun ModalBottomSheet(
+    onDismissRequest: () -> Unit,
+    modifier: Modifier = Modifier,
+    sheetState: SheetState = rememberModalBottomSheetState(),
+    sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
+    shape: Shape = BottomSheetDefaults.ExpandedShape,
+    containerColor: Color = BottomSheetDefaults.ContainerColor,
+    contentColor: Color = contentColorFor(containerColor),
+    tonalElevation: Dp = 0.dp,
+    scrimColor: Color = BottomSheetDefaults.ScrimColor,
+    dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
+    windowInsets: WindowInsets = BottomSheetDefaults.windowInsets,
+    properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties,
+    content: @Composable ColumnScope.() -> Unit,
+) = ModalBottomSheet(
+    onDismissRequest = onDismissRequest,
+    modifier = modifier,
+    sheetState = sheetState,
+    sheetMaxWidth = sheetMaxWidth,
+    shape = shape,
+    containerColor = containerColor,
+    contentColor = contentColor,
+    tonalElevation = tonalElevation,
+    scrimColor = scrimColor,
+    dragHandle = dragHandle,
+    contentWindowInsets = { windowInsets },
+    properties = properties,
+    content = content,
+)
+
 @Composable
 private fun Scrim(
     color: Color,
@@ -507,195 +630,96 @@
     }
 }
 
-/**
- * Popup specific for modal bottom sheet.
- */
+// Fork of androidx.compose.ui.window.AndroidDialog_androidKt.Dialog
+// Added predictiveBackProgress param to pass into BottomSheetDialogWrapper.
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
-internal fun ModalBottomSheetPopup(
-    properties: ModalBottomSheetProperties,
+private fun ModalBottomSheetDialog(
     onDismissRequest: () -> Unit,
+    properties: ModalBottomSheetProperties,
     predictiveBackProgress: Animatable<Float, AnimationVector1D>,
-    windowInsets: WindowInsets,
-    content: @Composable () -> Unit,
+    content: @Composable () -> Unit
 ) {
     val view = LocalView.current
-    val id = rememberSaveable { UUID.randomUUID() }
-    val parentComposition = rememberCompositionContext()
-    val currentContent by rememberUpdatedState(content)
-    val scope = rememberCoroutineScope()
+    val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
-    val modalBottomSheetWindow = remember {
-        ModalBottomSheetWindow(
-            properties = properties,
-            onDismissRequest = onDismissRequest,
-            composeView = view,
-            saveId = id,
-            predictiveBackProgress = predictiveBackProgress,
-            scope = scope
+    val composition = rememberCompositionContext()
+    val currentContent by rememberUpdatedState(content)
+    val dialogId = rememberSaveable { UUID.randomUUID() }
+    val scope = rememberCoroutineScope()
+    val dialog = remember(view, density) {
+        ModalBottomSheetDialogWrapper(
+            onDismissRequest,
+            properties,
+            view,
+            layoutDirection,
+            density,
+            dialogId,
+            predictiveBackProgress,
+            scope,
         ).apply {
-            setCustomContent(
-                parent = parentComposition,
-                content = {
-                    Box(
-                        Modifier
-                            .semantics { this.popup() }
-                            .windowInsetsPadding(windowInsets)
-                            .then(
-                                // TODO(b/290893168): Figure out a solution for APIs < 30.
-                                if (Build.VERSION.SDK_INT >= 33)
-                                    Modifier.imePadding()
-                                else Modifier
-                            )
-                    ) {
-                        currentContent()
-                    }
+            setContent(composition) {
+                Box(
+                    Modifier.semantics { dialog() },
+                ) {
+                    currentContent()
                 }
-            )
+            }
         }
     }
 
-    DisposableEffect(modalBottomSheetWindow) {
-        modalBottomSheetWindow.show()
-        modalBottomSheetWindow.superSetLayoutDirection(layoutDirection)
+    DisposableEffect(dialog) {
+        dialog.show()
+
         onDispose {
-            modalBottomSheetWindow.disposeComposition()
-            modalBottomSheetWindow.dismiss()
+            dialog.dismiss()
+            dialog.disposeComposition()
         }
     }
+
+    SideEffect {
+        dialog.updateParameters(
+            onDismissRequest = onDismissRequest,
+            properties = properties,
+            layoutDirection = layoutDirection
+        )
+    }
 }
 
-/** Custom compose view for [ModalBottomSheet] */
-@OptIn(ExperimentalMaterial3Api::class)
-private class ModalBottomSheetWindow(
-    private val properties: ModalBottomSheetProperties,
-    private var onDismissRequest: () -> Unit,
-    private val composeView: View,
+// Fork of androidx.compose.ui.window.DialogLayout
+// Additional parameters required for current predictive back implementation.
+@Suppress("ViewConstructor")
+private class ModalBottomSheetDialogLayout(
+    context: Context,
+    override val window: Window,
+    val shouldDismissOnBackPress: Boolean,
+    private val onDismissRequest: () -> Unit,
     private val predictiveBackProgress: Animatable<Float, AnimationVector1D>,
     private val scope: CoroutineScope,
-    saveId: UUID
-) :
-    AbstractComposeView(composeView.context),
-    ViewTreeObserver.OnGlobalLayoutListener,
-    ViewRootForInspector {
-
-    private var backCallback: Any? = null
-
-    init {
-        id = android.R.id.content
-        // Set up view owners
-        setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner())
-        setViewTreeViewModelStoreOwner(composeView.findViewTreeViewModelStoreOwner())
-        setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner())
-        setTag(androidx.compose.ui.R.id.compose_view_saveable_id_tag, "Popup:$saveId")
-        // Enable children to draw their shadow by not clipping them
-        clipChildren = false
-    }
-
-    private val windowManager =
-        composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-
-    private val displayWidth: Int
-        get() = context.resources.displayMetrics.widthPixels
-
-    private val params: WindowManager.LayoutParams =
-        WindowManager.LayoutParams().apply {
-            // Position bottom sheet from the bottom of the screen
-            gravity = Gravity.BOTTOM or Gravity.START
-            // Application panel window
-            type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
-            // Fill up the entire app view
-            width = displayWidth
-            height = WindowManager.LayoutParams.MATCH_PARENT
-
-            // Format of screen pixels
-            format = PixelFormat.TRANSLUCENT
-            // Title used as fallback for a11y services
-            // TODO: Provide bottom sheet window resource
-            title = composeView.context.resources.getString(
-                androidx.compose.ui.R.string.default_popup_window_title
-            )
-            // Get the Window token from the parent view
-            token = composeView.applicationWindowToken
-
-            // Flags specific to modal bottom sheet.
-            flags = flags and (
-                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or
-                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-                ).inv()
-
-            flags = flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
-
-            // Security flag
-            val secureFlagEnabled =
-                properties.securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled())
-            if (secureFlagEnabled) {
-                flags = flags or WindowManager.LayoutParams.FLAG_SECURE
-            } else {
-                flags = flags and (WindowManager.LayoutParams.FLAG_SECURE.inv())
-            }
-
-            // Focusable
-            if (!properties.isFocusable) {
-                flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-            } else {
-                flags = flags and (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv())
-            }
-        }
+) : AbstractComposeView(context), DialogWindowProvider {
 
     private var content: @Composable () -> Unit by mutableStateOf({})
 
+    private var backCallback: Any? = null
+
     override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
         private set
 
+    fun setContent(parent: CompositionContext, content: @Composable () -> Unit) {
+        setParentCompositionContext(parent)
+        this.content = content
+        shouldCreateCompositionOnAttachedToWindow = true
+        createComposition()
+    }
+
+    // Display width and height logic removed, size will always span fillMaxSize().
+
     @Composable
     override fun Content() {
         content()
     }
 
-    fun setCustomContent(
-        parent: CompositionContext? = null,
-        content: @Composable () -> Unit
-    ) {
-        parent?.let { setParentCompositionContext(it) }
-        this.content = content
-        shouldCreateCompositionOnAttachedToWindow = true
-    }
-
-    fun show() {
-        windowManager.addView(this, params)
-    }
-
-    fun dismiss() {
-        setViewTreeLifecycleOwner(null)
-        setViewTreeSavedStateRegistryOwner(null)
-        composeView.viewTreeObserver.removeOnGlobalLayoutListener(this)
-        windowManager.removeViewImmediate(this)
-    }
-
-    /**
-     * Taken from PopupWindow. Calls [onDismissRequest] when back button is pressed.
-     */
-    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
-        if (event.keyCode == KeyEvent.KEYCODE_BACK && properties.shouldDismissOnBackPress) {
-            if (keyDispatcherState == null) {
-                return super.dispatchKeyEvent(event)
-            }
-            if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) {
-                val state = keyDispatcherState
-                state?.startTracking(event, this)
-                return true
-            } else if (event.action == KeyEvent.ACTION_UP) {
-                val state = keyDispatcherState
-                if (state != null && state.isTracking(event) && !event.isCanceled) {
-                    onDismissRequest()
-                    return true
-                }
-            }
-        }
-        return super.dispatchKeyEvent(event)
-    }
-
+    // Existing predictive back behavior below.
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
 
@@ -709,7 +733,7 @@
     }
 
     private fun maybeRegisterBackCallback() {
-        if (!properties.shouldDismissOnBackPress || Build.VERSION.SDK_INT < 33) {
+        if (!shouldDismissOnBackPress || Build.VERSION.SDK_INT < 33) {
             return
         }
         if (backCallback == null) {
@@ -729,24 +753,6 @@
         backCallback = null
     }
 
-    override fun onGlobalLayout() {
-        // No-op
-    }
-
-    override fun setLayoutDirection(layoutDirection: Int) {
-        // Do nothing. ViewRootImpl will call this method attempting to set the layout direction
-        // from the context's locale, but we have one already from the parent composition.
-    }
-
-    // Sets the "real" layout direction for our content that we obtain from the parent composition.
-    fun superSetLayoutDirection(layoutDirection: LayoutDirection) {
-        val direction = when (layoutDirection) {
-            LayoutDirection.Ltr -> android.util.LayoutDirection.LTR
-            LayoutDirection.Rtl -> android.util.LayoutDirection.RTL
-        }
-        super.setLayoutDirection(direction)
-    }
-
     @RequiresApi(34)
     private object Api34Impl {
         @JvmStatic
@@ -807,8 +813,165 @@
     }
 }
 
-// Taken from AndroidPopup.android.kt
-private fun View.isFlagSecureEnabled(): Boolean {
+// Fork of androidx.compose.ui.window.DialogWrapper.
+// predictiveBackProgress and scope params added for predictive back implementation.
+// EdgeToEdgeFloatingDialogWindowTheme provided to allow theme to extend into status bar.
+@ExperimentalMaterial3Api
+private class ModalBottomSheetDialogWrapper(
+    private var onDismissRequest: () -> Unit,
+    private var properties: ModalBottomSheetProperties,
+    private val composeView: View,
+    layoutDirection: LayoutDirection,
+    density: Density,
+    dialogId: UUID,
+    predictiveBackProgress: Animatable<Float, AnimationVector1D>,
+    scope: CoroutineScope,
+) : ComponentDialog(
+    ContextThemeWrapper(
+        composeView.context,
+        androidx.compose.material3.R.style.EdgeToEdgeFloatingDialogWindowTheme
+    )
+), ViewRootForInspector {
+
+    private val dialogLayout: ModalBottomSheetDialogLayout
+
+    // On systems older than Android S, there is a bug in the surface insets matrix math used by
+    // elevation, so high values of maxSupportedElevation break accessibility services: b/232788477.
+    private val maxSupportedElevation = 8.dp
+
+    override val subCompositionView: AbstractComposeView get() = dialogLayout
+
+    init {
+        val window = window ?: error("Dialog has no window")
+        window.requestFeature(Window.FEATURE_NO_TITLE)
+        window.setBackgroundDrawableResource(android.R.color.transparent)
+        WindowCompat.setDecorFitsSystemWindows(window, false)
+        dialogLayout = ModalBottomSheetDialogLayout(
+            context,
+            window,
+            properties.shouldDismissOnBackPress,
+            onDismissRequest,
+            predictiveBackProgress,
+            scope,
+        ).apply {
+            // Set unique id for AbstractComposeView. This allows state restoration for the state
+            // defined inside the Dialog via rememberSaveable()
+            setTag(R.id.compose_view_saveable_id_tag, "Dialog:$dialogId")
+            // Enable children to draw their shadow by not clipping them
+            clipChildren = false
+            // Allocate space for elevation
+            with(density) { elevation = maxSupportedElevation.toPx() }
+            // Simple outline to force window manager to allocate space for shadow.
+            // Note that the outline affects clickable area for the dismiss listener. In case of
+            // shapes like circle the area for dismiss might be to small (rectangular outline
+            // consuming clicks outside of the circle).
+            outlineProvider = object : ViewOutlineProvider() {
+                override fun getOutline(view: View, result: Outline) {
+                    result.setRect(0, 0, view.width, view.height)
+                    // We set alpha to 0 to hide the view's shadow and let the composable to draw
+                    // its own shadow. This still enables us to get the extra space needed in the
+                    // surface.
+                    result.alpha = 0f
+                }
+            }
+        }
+        // Clipping logic removed because we are spanning edge to edge.
+
+        setContentView(dialogLayout)
+        dialogLayout.setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner())
+        dialogLayout.setViewTreeViewModelStoreOwner(composeView.findViewTreeViewModelStoreOwner())
+        dialogLayout.setViewTreeSavedStateRegistryOwner(
+            composeView.findViewTreeSavedStateRegistryOwner()
+        )
+
+        // Initial setup
+        updateParameters(onDismissRequest, properties, layoutDirection)
+
+        WindowCompat.getInsetsController(window, window.decorView).apply {
+            isAppearanceLightStatusBars = false
+            isAppearanceLightNavigationBars = false
+        }
+        // Due to how the onDismissRequest callback works
+        // (it enforces a just-in-time decision on whether to update the state to hide the dialog)
+        // we need to unconditionally add a callback here that is always enabled,
+        // meaning we'll never get a system UI controlled predictive back animation
+        // for these dialogs
+        onBackPressedDispatcher.addCallback(this) {
+            if (properties.shouldDismissOnBackPress) {
+                onDismissRequest()
+            }
+        }
+    }
+
+    private fun setLayoutDirection(layoutDirection: LayoutDirection) {
+        dialogLayout.layoutDirection = when (layoutDirection) {
+            LayoutDirection.Ltr -> android.util.LayoutDirection.LTR
+            LayoutDirection.Rtl -> android.util.LayoutDirection.RTL
+        }
+    }
+
+    fun setContent(parentComposition: CompositionContext, children: @Composable () -> Unit) {
+        dialogLayout.setContent(parentComposition, children)
+    }
+
+    private fun setSecurePolicy(securePolicy: SecureFlagPolicy) {
+        val secureFlagEnabled =
+            securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled())
+        window!!.setFlags(
+            if (secureFlagEnabled) {
+                WindowManager.LayoutParams.FLAG_SECURE
+            } else {
+                WindowManager.LayoutParams.FLAG_SECURE.inv()
+            },
+            WindowManager.LayoutParams.FLAG_SECURE
+        )
+    }
+
+    fun updateParameters(
+        onDismissRequest: () -> Unit,
+        properties: ModalBottomSheetProperties,
+        layoutDirection: LayoutDirection
+    ) {
+        this.onDismissRequest = onDismissRequest
+        this.properties = properties
+        setSecurePolicy(properties.securePolicy)
+        setLayoutDirection(layoutDirection)
+
+        // Window flags to span parent window.
+        window?.setLayout(
+            WindowManager.LayoutParams.MATCH_PARENT,
+            WindowManager.LayoutParams.MATCH_PARENT,
+        )
+        window?.setSoftInputMode(
+            if (Build.VERSION.SDK_INT >= 30) {
+                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+            } else {
+                @Suppress("DEPRECATION")
+                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+            },
+        )
+    }
+
+    fun disposeComposition() {
+        dialogLayout.disposeComposition()
+    }
+
+    override fun onTouchEvent(event: MotionEvent): Boolean {
+        val result = super.onTouchEvent(event)
+        if (result) {
+            onDismissRequest()
+        }
+
+        return result
+    }
+
+    override fun cancel() {
+        // Prevents the dialog from dismissing itself
+        return
+    }
+}
+
+internal fun View.isFlagSecureEnabled(): Boolean {
     val windowParams = rootView.layoutParams as? WindowManager.LayoutParams
     if (windowParams != null) {
         return (windowParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
index a8977c5..a04b579 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.WindowInsetsSides
 import androidx.compose.foundation.layout.only
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
 import androidx.compose.foundation.layout.size
 import androidx.compose.material3.SheetValue.Expanded
 import androidx.compose.material3.SheetValue.Hidden
@@ -34,7 +35,6 @@
 import androidx.compose.material3.internal.animateTo
 import androidx.compose.material3.internal.getString
 import androidx.compose.material3.internal.snapTo
-import androidx.compose.material3.internal.systemBarsForVisualComponents
 import androidx.compose.material3.tokens.ScrimTokens
 import androidx.compose.material3.tokens.SheetBottomTokens
 import androidx.compose.runtime.Composable
@@ -332,11 +332,11 @@
     val SheetMaxWidth = 640.dp
 
     /**
-     * Default insets to be used and consumed by the [ModalBottomSheet] window.
+     * Default insets to be used and consumed by the [ModalBottomSheet]'s content.
      */
     val windowInsets: WindowInsets
         @Composable
-        get() = WindowInsets.systemBarsForVisualComponents.only(WindowInsetsSides.Vertical)
+        get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)
 
     /**
      * The optional visual marker placed on top of a bottom sheet to indicate it may be dragged.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index 0683f94..c06a03e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -140,9 +140,9 @@
  * services.
  * @param valueRange range of values that this slider can take. The passed [value] will be coerced
  * to this range.
- * @param steps if greater than 0, specifies the amount of discrete allowable values, evenly
- * distributed across the whole value range. If 0, the slider will behave continuously and allow any
- * value from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * slider will behave continuously and allow any value from the range. Must not be negative.
  * @param onValueChangeFinished called when value change has ended. This should not be used to
  * update the slider value (use [onValueChange] instead), but rather to know when the user has
  * completed selecting a new value by ending a drag or a click.
@@ -236,9 +236,9 @@
  * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
  * for this slider. You can create and pass in your own `remember`ed instance to observe
  * [Interaction]s and customize the appearance / behavior of this slider in different states.
- * @param steps if greater than 0, specifies the amount of discrete allowable values, evenly
- * distributed across the whole value range. If 0, the slider will behave continuously and allow any
- * value from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * slider will behave continuously and allow any value from the range. Must not be negative.
  * @param thumb the thumb to be displayed on the slider, it is placed on top of the track. The
  * lambda receives a [SliderState] which is used to obtain the current active track.
  * @param track the track to be displayed on the slider, it is placed underneath the thumb. The
@@ -402,9 +402,9 @@
  * @param enabled whether or not component is enabled and can we interacted with or not
  * @param valueRange range of values that Range Slider values can take. Passed [value] will be
  * coerced to this range
- * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed
- * between across the whole value range. If 0, range slider will behave as a continuous slider and
- * allow to choose any value from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * range slider will behave continuously and allow any value from the range. Must not be negative.
  * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback
  * shouldn't be used to update the range slider values (use [onValueChange] for that), but rather to
  * know when the user has completed selecting a new value by ending a drag or a click.
@@ -503,9 +503,9 @@
  * @param endInteractionSource the [MutableInteractionSource] representing the stream of
  * [Interaction]s for the end thumb. You can create and pass in your own
  * `remember`ed instance to observe.
- * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed
- * between across the whole value range. If 0, range slider will behave as a continuous slider and
- * allow to choose any value from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * range slider will behave continuously and allow any value from the range. Must not be negative.
  * @param startThumb the start thumb to be displayed on the Range Slider. The lambda receives a
  * [RangeSliderState] which is used to obtain the current active track.
  * @param endThumb the end thumb to be displayed on the Range Slider. The lambda receives a
@@ -2031,9 +2031,9 @@
  * @param value [Float] that indicates the initial
  * position of the thumb. If outside of [valueRange]
  * provided, value will be coerced to this range.
- * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed
- * between across the whole value range. If 0, range slider will behave as a continuous slider and
- * allow to choose any value from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * slider will behave continuously and allow any value from the range. Must not be negative.
  * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback
  * shouldn't be used to update the range slider values (use [onValueChange] for that),
  * but rather to know when the user has completed selecting a new value by ending a drag or a click.
@@ -2158,9 +2158,9 @@
  * @param activeRangeEnd [Float] that indicates the initial
  * end of the active range of the slider. If outside of [valueRange]
  * provided, value will be coerced to this range.
- * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed
- * between across the whole value range. If 0, range slider will behave as a continuous slider and
- * allow to choose any value from the range specified. Must not be negative.
+ * @param steps if positive, specifies the amount of discrete allowable values (in addition to the
+ * endpoints of the value range). Step values are evenly distributed across the range. If 0, the
+ * range slider will behave continuously and allow any value from the range. Must not be negative.
  * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback
  * shouldn't be used to update the range slider values (use [onValueChange] for that), but rather
  * to know when the user has completed selecting a new value by ending a drag or a click.
diff --git a/compose/material3/material3/src/main/res/values-v30/styles.xml b/compose/material3/material3/src/main/res/values-v30/styles.xml
new file mode 100644
index 0000000..3d9425e
--- /dev/null
+++ b/compose/material3/material3/src/main/res/values-v30/styles.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Copyright 2024 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <!-- "always" Value for windowLayoutInDisplayCutoutMode. Needed for APIs 30+. -->
+    <integer name="m3c_window_layout_in_display_cutout_mode">3</integer>
+</resources>
\ No newline at end of file
diff --git a/compose/material3/material3/src/main/res/values/styles.xml b/compose/material3/material3/src/main/res/values/styles.xml
new file mode 100644
index 0000000..8249ac4
--- /dev/null
+++ b/compose/material3/material3/src/main/res/values/styles.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+     Copyright 2024 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <style name="EdgeToEdgeFloatingDialogWindowTheme">
+        <item name="android:dialogTheme">@style/EdgeToEdgeFloatingDialogTheme</item>
+    </style>
+    <style name="EdgeToEdgeFloatingDialogTheme" parent="android:Theme.DeviceDefault.Dialog">
+        <item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="27">@integer/m3c_window_layout_in_display_cutout_mode</item>
+        <item name="android:windowClipToOutline">false</item>
+        <item name="android:windowIsFloating">false</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowElevation">0dp</item>
+    </style>
+    <!-- "shortEdges" Value for windowLayoutInDisplayCutoutMode. Needed for APIs 27-29. -->
+    <integer name="m3c_window_layout_in_display_cutout_mode">1</integer>
+</resources>
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index cd5b143..68a8faa 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -193,6 +193,7 @@
   }
 
   public final class BezierKt {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static long computeCubicVerticalBounds(float p0y, float p1y, float p2y, float p3y, float[] roots, optional int index);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static long computeHorizontalBounds(androidx.compose.ui.graphics.PathSegment segment, float[] roots, optional int index);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static float evaluateCubic(float p1, float p2, float t);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static float evaluateY(androidx.compose.ui.graphics.PathSegment segment, float t);
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
index 5713996..541181b 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
@@ -20,17 +20,13 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.RequiresApi
-import androidx.compose.ui.graphics.drawscope.DefaultDensity
 import androidx.compose.ui.graphics.layer.GraphicsLayer
-import androidx.compose.ui.graphics.layer.GraphicsLayerImpl
 import androidx.compose.ui.graphics.layer.GraphicsLayerV23
 import androidx.compose.ui.graphics.layer.GraphicsLayerV29
 import androidx.compose.ui.graphics.layer.GraphicsViewLayer
 import androidx.compose.ui.graphics.layer.LayerManager
 import androidx.compose.ui.graphics.layer.view.DrawChildContainer
 import androidx.compose.ui.graphics.layer.view.ViewLayerContainer
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * Create a new [GraphicsContext] with the provided [ViewGroup] to contain [View] based layers.
@@ -73,27 +69,7 @@
                 )
             }
             return GraphicsLayer(layerImpl, layerManager).also { layer ->
-                // Do a placeholder recording of drawing instructions to avoid errors when doing a
-                // persistence render.
-                // This will be overridden by the consumer of the created GraphicsLayer
-                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P &&
-                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                    // Only API levels between M (inclusive) and P (exclusive) require a placeholder
-                    // displaylist for persistence rendering. On some API levels like (ex. API 28)
-                    // actually doing a placeholder render before the activity is setup
-                    // (ex in unit tests) causes the emulator to crash with an NPE in native code
-                    // on the HWUI canvas implementation
-                    layer.record(
-                        DefaultDensity,
-                        LayoutDirection.Ltr,
-                        IntSize(1, 1),
-                        GraphicsLayerImpl.DefaultDrawBlock
-                    )
-                }
                 layerManager.persist(layer)
-                // Reset the size to zero so that immediately after GraphicsLayer creation
-                // we do not advertise a size of 1 x 1
-                layer.size = IntSize.Zero
             }
         }
     }
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
index f240a88..c8500ae 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
@@ -57,6 +57,7 @@
     private var layerPaint: android.graphics.Paint? = null
     private var matrix: android.graphics.Matrix? = null
     private var outlineIsProvided = false
+    private var recordWasCalled = false
 
     private fun obtainLayerPaint(): android.graphics.Paint =
         layerPaint ?: android.graphics.Paint().also { layerPaint = it }
@@ -282,6 +283,7 @@
         layer: GraphicsLayer,
         block: DrawScope.() -> Unit
     ) {
+        recordWasCalled = true
         val recordingCanvas = renderNode.start(size.width, size.height)
         canvasHolder.drawInto(recordingCanvas) {
             canvasDrawScope.draw(
@@ -298,6 +300,14 @@
     }
 
     override fun draw(canvas: androidx.compose.ui.graphics.Canvas) {
+        if (!recordWasCalled) {
+            recordWasCalled = true
+            // Do a placeholder recording of drawing instructions to avoid errors when doing a
+            // persistence render.
+            // This will be overridden by the consumer of the created GraphicsLayer
+            val recordingCanvas = renderNode.start(1, 1)
+            renderNode.end(recordingCanvas)
+        }
         (canvas.nativeCanvas as DisplayListCanvas).drawRenderNode(renderNode)
     }
 
@@ -327,6 +337,7 @@
     }
 
     private fun discardDisplayListInternal() {
+        recordWasCalled = false
         // See b/216660268. RenderNode#discardDisplayList was originally called
         // destroyDisplayListData on Android M and below. Make sure we gate on the corresponding
         // API level and call the original method name on these API levels, otherwise invoke
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
index acf82f8..9beda3b 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Bezier.kt
@@ -421,9 +421,11 @@
  */
 private fun findDerivativeRoots(
     segment: PathSegment,
+    horizontal: Boolean,
     roots: FloatArray,
-    index: Int = 0,
+    index: Int
 ): Int {
+    val offset = if (horizontal) 0 else 1
     val points = segment.points
     return when (segment.type) {
         PathSegment.Type.Move -> 0
@@ -434,8 +436,8 @@
             // Line derivative of a quadratic function
             // We do the computation inline to avoid using arrays of other data
             // structures to return the result
-            val d0 = 2 * (points[2] - points[0])
-            val d1 = 2 * (points[4] - points[2])
+            val d0 = 2 * (points[offset + 2] - points[offset + 0])
+            val d1 = 2 * (points[offset + 4] - points[offset + 2])
             findLineRoot(d0, d1, roots, index)
         }
 
@@ -446,9 +448,9 @@
             // Quadratic derivative of a cubic function
             // We do the computation inline to avoid using arrays of other data
             // structures to return the result
-            val d0 = 3.0f * (points[2] - points[0])
-            val d1 = 3.0f * (points[4] - points[2])
-            val d2 = 3.0f * (points[6] - points[4])
+            val d0 = 3.0f * (points[offset + 2] - points[offset + 0])
+            val d1 = 3.0f * (points[offset + 4] - points[offset + 2])
+            val d2 = 3.0f * (points[offset + 6] - points[offset + 4])
             val count = findQuadraticRoots(d0, d1, d2, roots, index)
 
             // Compute the second derivative as a line
@@ -477,7 +479,7 @@
     roots: FloatArray,
     index: Int = 0
 ): FloatFloatPair {
-    val count = findDerivativeRoots(segment, roots, index)
+    val count = findDerivativeRoots(segment, true, roots, index)
     var minX = min(segment.startX, segment.endX)
     var maxX = max(segment.startX, segment.endX)
 
@@ -504,18 +506,54 @@
     roots: FloatArray,
     index: Int = 0
 ): FloatFloatPair {
-    val count = findDerivativeRoots(segment, roots, index)
-    var minX = min(segment.startY, segment.endY)
-    var maxX = max(segment.startY, segment.endY)
+    val count = findDerivativeRoots(segment, false, roots, index)
+    var minY = min(segment.startY, segment.endY)
+    var maxY = max(segment.startY, segment.endY)
 
     for (i in 0 until count) {
         val t = roots[i]
         val x = evaluateY(segment, t)
-        minX = min(minX, x)
-        maxX = max(maxX, x)
+        minY = min(minY, x)
+        maxY = max(maxY, x)
     }
 
-    return FloatFloatPair(minX, maxX)
+    return FloatFloatPair(minY, maxY)
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun computeCubicVerticalBounds(
+    p0y: Float,
+    p1y: Float,
+    p2y: Float,
+    p3y: Float,
+    roots: FloatArray,
+    index: Int = 0
+): FloatFloatPair {
+    // Quadratic derivative of a cubic function
+    // We do the computation inline to avoid using arrays of other data
+    // structures to return the result
+    val d0 = 3.0f * (p1y - p0y)
+    val d1 = 3.0f * (p2y - p1y)
+    val d2 = 3.0f * (p3y - p2y)
+    var count = findQuadraticRoots(d0, d1, d2, roots, index)
+
+    // Compute the second derivative as a line
+    val dd0 = 2.0f * (d1 - d0)
+    val dd1 = 2.0f * (d2 - d1)
+    count += findLineRoot(dd0, dd1, roots, index + count)
+
+    var minY = min(p0y, p3y)
+    var maxY = max(p0y, p3y)
+
+    for (i in 0 until count) {
+        val t = roots[i]
+        println(t)
+        val y = evaluateCubic(p0y, p1y, p2y, p3y, t)
+        minY = min(minY, y)
+        maxY = max(maxY, y)
+    }
+
+    return FloatFloatPair(minY, maxY)
 }
 
 @Suppress("NOTHING_TO_INLINE")
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 1f1425a..76206b2 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -170,7 +170,7 @@
   }
 
   public fun interface LinkInteractionListener {
-    method public void onClicked(androidx.compose.ui.text.LinkAnnotation link);
+    method public void onClick(androidx.compose.ui.text.LinkAnnotation link);
   }
 
   public final class MultiParagraph {
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index 01a1f6f..2156b8f 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -170,7 +170,7 @@
   }
 
   public fun interface LinkInteractionListener {
-    method public void onClicked(androidx.compose.ui.text.LinkAnnotation link);
+    method public void onClick(androidx.compose.ui.text.LinkAnnotation link);
   }
 
   public final class MultiParagraph {
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/URLSpanCache.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/URLSpanCache.android.kt
index 137833f..790beec 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/URLSpanCache.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/URLSpanCache.android.kt
@@ -76,6 +76,6 @@
 
 private class ComposeClickableSpan(private val link: LinkAnnotation) : ClickableSpan() {
     override fun onClick(widget: View) {
-        link.linkInteractionListener?.onClicked(link)
+        link.linkInteractionListener?.onClick(link)
     }
 }
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkAnnotation.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkAnnotation.kt
index 86d1c54..3a1c75a 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkAnnotation.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkAnnotation.kt
@@ -52,7 +52,7 @@
     /**
      * An annotation that contains a [url] string. When clicking on the text to which this annotation
      * is attached, the app will try to open the url using [androidx.compose.ui.platform.UriHandler].
-     * However, if [linkInteractionListener] is provided, its [LinkInteractionListener.onClicked]
+     * However, if [linkInteractionListener] is provided, its [LinkInteractionListener.onClick]
      * method will be called instead and so you need to then handle opening url manually (for
      * example by calling [androidx.compose.ui.platform.UriHandler]).
      */
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkInteractionListener.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkInteractionListener.kt
index dde0f0e..dc1aa74 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkInteractionListener.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/LinkInteractionListener.kt
@@ -22,5 +22,5 @@
 fun interface LinkInteractionListener {
 
     /** Triggered when a user clicks on the [link] */
-    fun onClicked(link: LinkAnnotation)
+    fun onClick(link: LinkAnnotation)
 }
diff --git a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
index 21d84a2..c0e5ba0 100644
--- a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
+++ b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt
@@ -41,7 +41,7 @@
 /**
  * Returns the smaller of the given values. If any value is NaN, returns NaN.
  * Preferred over `kotlin.comparisons.minfOf()` for 4 arguments as it avoids
- * allocaing an array because of the varargs.
+ * allocating an array because of the varargs.
  */
 @Suppress("NOTHING_TO_INLINE")
 inline fun fastMinOf(a: Float, b: Float, c: Float, d: Float): Float {
@@ -52,7 +52,7 @@
 /**
  * Returns the largest of the given values. If any value is NaN, returns NaN.
  * Preferred over `kotlin.comparisons.maxOf()` for 4 arguments as it avoids
- * allocaing an array because of the varargs.
+ * allocating an array because of the varargs.
  */
 @Suppress("NOTHING_TO_INLINE")
 inline fun fastMaxOf(a: Float, b: Float, c: Float, d: Float): Float {
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 48c1ab5..1e4745c 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -643,6 +643,290 @@
     }
 
     @Test
+    @SmallTest
+    public void testSetFNumber_decimalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        String value = "1.4";
+        exifInterface.setAttribute(ExifInterface.TAG_F_NUMBER, value);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_F_NUMBER)).isEqualTo(value);
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_F_NUMBER, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(1.4);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_F_NUMBER)).isEqualTo(value);
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_F_NUMBER, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(1.4);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetFNumber_rationalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        exifInterface.setAttribute(ExifInterface.TAG_F_NUMBER, "7/5");
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_F_NUMBER)).isEqualTo("1.4");
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_F_NUMBER, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(1.4);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_F_NUMBER)).isEqualTo("1.4");
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_F_NUMBER, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(1.4);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDigitalZoomRatio_decimalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        String value = "0.8";
+        exifInterface.setAttribute(ExifInterface.TAG_DIGITAL_ZOOM_RATIO, value);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_DIGITAL_ZOOM_RATIO))
+                .isEqualTo("0.8");
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_DIGITAL_ZOOM_RATIO, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(0.8);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_DIGITAL_ZOOM_RATIO))
+                .isEqualTo("0.8");
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_DIGITAL_ZOOM_RATIO, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(0.8);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDigitalZoomRatio_rationalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        exifInterface.setAttribute(ExifInterface.TAG_DIGITAL_ZOOM_RATIO, "12/5");
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_DIGITAL_ZOOM_RATIO))
+                .isEqualTo("2.4");
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_DIGITAL_ZOOM_RATIO, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(2.4);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_DIGITAL_ZOOM_RATIO))
+                .isEqualTo("2.4");
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_DIGITAL_ZOOM_RATIO, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(2.4);
+    }
+
+    // https://issuetracker.google.com/312680558
+    @Test
+    @SmallTest
+    public void testSetExposureTime_decimalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        exifInterface.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, "0.000625");
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_EXPOSURE_TIME))
+                .isEqualTo("6.25E-4");
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_EXPOSURE_TIME, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(0.000625);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_EXPOSURE_TIME))
+                .isEqualTo("6.25E-4");
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_EXPOSURE_TIME, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(0.000625);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetExposureTime_rationalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        exifInterface.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, "1/1600");
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_EXPOSURE_TIME))
+                .isEqualTo("6.25E-4");
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_EXPOSURE_TIME, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(0.000625);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_EXPOSURE_TIME))
+                .isEqualTo("6.25E-4");
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_EXPOSURE_TIME, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(0.000625);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetSubjectDistance_decimalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        String value = "3.5";
+        exifInterface.setAttribute(ExifInterface.TAG_SUBJECT_DISTANCE, value);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_SUBJECT_DISTANCE)).isEqualTo(value);
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_SUBJECT_DISTANCE, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(3.5);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_SUBJECT_DISTANCE)).isEqualTo(value);
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_SUBJECT_DISTANCE, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(3.5);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetSubjectDistance_rationalString() throws Exception {
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_byte_order_ii, "jpeg_with_exif_byte_order_ii.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        exifInterface.setAttribute(ExifInterface.TAG_SUBJECT_DISTANCE, "7/2");
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_SUBJECT_DISTANCE)).isEqualTo("3.5");
+        double result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_SUBJECT_DISTANCE, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(3.5);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_SUBJECT_DISTANCE)).isEqualTo("3.5");
+        result =
+                exifInterface.getAttributeDouble(
+                        ExifInterface.TAG_SUBJECT_DISTANCE, /* defaultValue= */ -1);
+        assertThat(result).isEqualTo(3.5);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetGpsTimestamp_integers() throws Exception {
+        // Deliberately use an image with an existing GPS timestamp value to overwrite.
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_with_xmp, "jpeg_with_exif_with_xmp.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        String timestamp = "11:06:52";
+        exifInterface.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, timestamp);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP))
+                .isEqualTo(timestamp);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP))
+                .isEqualTo(timestamp);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetGpsTimestamp_rationals_failsSilently() throws Exception {
+        // Deliberately use an image with an existing GPS timestamp value to overwrite.
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_with_xmp, "jpeg_with_exif_with_xmp.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        exifInterface.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, "11/2:06/5:52/8");
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP))
+                .isEqualTo(ExpectedAttributes.JPEG_WITH_EXIF_WITH_XMP.gpsTimestamp);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP))
+                .isEqualTo(ExpectedAttributes.JPEG_WITH_EXIF_WITH_XMP.gpsTimestamp);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetGpsTimestamp_decimals_failsSilently() throws Exception {
+        // Deliberately use an image with an existing GPS timestamp value to overwrite.
+        File imageFile =
+                copyFromResourceToFile(
+                        R.raw.jpeg_with_exif_with_xmp, "jpeg_with_exif_with_xmp.jpg");
+        ExifInterface exifInterface = new ExifInterface(imageFile);
+
+        exifInterface.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, "11.5:06.3:52.8");
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP))
+                .isEqualTo(ExpectedAttributes.JPEG_WITH_EXIF_WITH_XMP.gpsTimestamp);
+
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile);
+
+        assertThat(exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP))
+                .isEqualTo(ExpectedAttributes.JPEG_WITH_EXIF_WITH_XMP.gpsTimestamp);
+    }
+
+    @Test
     @LargeTest
     public void testAddDefaultValuesForCompatibility() throws Exception {
         File imageFile =
@@ -1143,6 +1427,136 @@
         assertThat(exif.getAttribute(newTag)).isEqualTo(isoValue);
     }
 
+    @Test
+    @SmallTest
+    public void testRationalFromDouble() {
+        double value = 0.12345678;
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(150549);
+        expect.that(result.denominator).isEqualTo(1219447);
+        expect.that((double) result.numerator / result.denominator)
+                .isWithin(0.00000000001)
+                .of(value);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_niceFraction() {
+        double value = 1.0 / 1600;
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(1);
+        expect.that(result.denominator).isEqualTo(1600);
+        expect.that((double) result.numerator / result.denominator).isEqualTo(value);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_recurringDecimal() {
+        double value = 1.0 / 3;
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(1);
+        expect.that(result.denominator).isEqualTo(3);
+        expect.that((double) result.numerator / result.denominator).isEqualTo(value);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_negative() {
+        double value = -0.12345678;
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(-150549);
+        expect.that(result.denominator).isEqualTo(1219447);
+        expect.that((double) result.numerator / result.denominator)
+                .isWithin(0.00000000001)
+                .of(value);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_maxLong() {
+        double value = Long.MAX_VALUE;
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(Long.MAX_VALUE);
+        expect.that(result.denominator).isEqualTo(1);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_justLargerThanMaxLong() {
+        double value = Math.nextUp(Long.MAX_VALUE);
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(Long.MAX_VALUE);
+        expect.that(result.denominator).isEqualTo(1);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_muchLargerThanMaxLong() {
+        double value = Long.MAX_VALUE + 10000.0;
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(Long.MAX_VALUE);
+        expect.that(result.denominator).isEqualTo(1);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_minLong() {
+        double value = Math.nextDown(Long.MIN_VALUE);
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(Long.MIN_VALUE);
+        expect.that(result.denominator).isEqualTo(1);
+    }
+
+    // Ensure that a very large negative number, which is just higher (closer to positive infinity)
+    // than Long.MIN_VALUE doesn't cause overflow.
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_justHigherThanMinLong() {
+        double value = Math.nextUp(Long.MIN_VALUE);
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        long expectedNumerator = Math.round(value);
+        expect.that(result.numerator).isEqualTo(expectedNumerator);
+        expect.that(result.denominator).isEqualTo(1);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_justLowerThanMinLong() {
+        double value = Math.nextDown(Long.MIN_VALUE);
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(Long.MIN_VALUE);
+        expect.that(result.denominator).isEqualTo(1);
+    }
+
+    @Test
+    @SmallTest
+    public void testRationalFromDouble_muchLowerThanMinLong() {
+        double value = Long.MIN_VALUE - 1000.0;
+
+        ExifInterface.Rational result = ExifInterface.Rational.createFromDouble(value);
+
+        expect.that(result.numerator).isEqualTo(Long.MIN_VALUE);
+        expect.that(result.denominator).isEqualTo(1);
+    }
+
     private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
         // Prints thumbnail information.
         if (exifInterface.hasThumbnail()) {
@@ -1215,7 +1629,9 @@
         if (expectedAttributes.hasLatLong) {
             expect.that(latLong)
                     .usingExactEquality()
-                    .containsExactly(expectedAttributes.latitude, expectedAttributes.longitude)
+                    .containsExactly(
+                            expectedAttributes.computedLatitude,
+                            expectedAttributes.computedLongitude)
                     .inOrder();
             expect.that(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE)).isTrue();
             expect.that(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE)).isTrue();
@@ -1224,7 +1640,7 @@
             expect.that(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE)).isFalse();
             expect.that(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE)).isFalse();
         }
-        expect.that(exifInterface.getAltitude(.0)).isEqualTo(expectedAttributes.altitude);
+        expect.that(exifInterface.getAltitude(.0)).isEqualTo(expectedAttributes.computedAltitude);
 
         // Checks values.
         expectStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedAttributes.make);
@@ -1424,7 +1840,8 @@
             expect.that(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE))
                     .asList()
                     .containsExactly(
-                            expectedAttributes.latitudeOffset, expectedAttributes.latitudeLength)
+                            expectedAttributes.gpsLatitudeOffset,
+                            expectedAttributes.gpsLatitudeLength)
                     .inOrder();
             // TODO: Add code for retrieving raw latitude data using offset and length
         } else {
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java
index c5611c4..6a4b30e 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExpectedAttributes.java
@@ -39,7 +39,7 @@
                     .setThumbnailSize(512, 288)
                     .setIsThumbnailCompressed(true)
                     .setMake("SAMSUNG")
-                    .setMakeOffsetAndLength(160, 8)
+                    .setMakeOffset(160)
                     .setModel("SM-N900S")
                     .setAperture(2.2)
                     .setDateTimeOriginal("2016:01:29 18:32:27")
@@ -64,11 +64,11 @@
     /** Expected attributes for {@link R.raw#jpeg_with_exif_byte_order_mm}. */
     public static final ExpectedAttributes JPEG_WITH_EXIF_BYTE_ORDER_MM =
             new Builder()
-                    .setLatitudeOffsetAndLength(584, 24)
-                    .setLatLong(0, 0)
-                    .setAltitude(0)
+                    .setGpsLatitudeOffsetAndLength(584, 24)
+                    .setComputedLatLong(0, 0)
+                    .setComputedAltitude(0)
                     .setMake("LGE")
-                    .setMakeOffsetAndLength(414, 4)
+                    .setMakeOffset(414)
                     .setModel("Nexus 5")
                     .setAperture(2.4)
                     .setDateTimeOriginal("2016:01:29 15:44:58")
@@ -94,7 +94,7 @@
     public static final ExpectedAttributes JPEG_WITH_EXIF_BYTE_ORDER_MM_STANDALONE =
             JPEG_WITH_EXIF_BYTE_ORDER_MM
                     .buildUpon()
-                    .setLatitudeOffset(JPEG_WITH_EXIF_BYTE_ORDER_MM.latitudeOffset - 6)
+                    .setGpsLatitudeOffset(JPEG_WITH_EXIF_BYTE_ORDER_MM.gpsLatitudeOffset - 6)
                     .setMakeOffset(JPEG_WITH_EXIF_BYTE_ORDER_MM.makeOffset - 6)
                     .setImageSize(0, 0)
                     .build();
@@ -116,11 +116,11 @@
                     .setThumbnailOffsetAndLength(12570, 15179)
                     .setThumbnailSize(256, 144)
                     .setIsThumbnailCompressed(true)
-                    .setLatitudeOffsetAndLength(12486, 24)
-                    .setLatLong(53.83450833333334, 10.69585)
-                    .setAltitude(0)
+                    .setGpsLatitudeOffsetAndLength(12486, 24)
+                    .setComputedLatLong(53.83450833333334, 10.69585)
+                    .setComputedAltitude(0)
                     .setMake("LGE")
-                    .setMakeOffsetAndLength(102, 4)
+                    .setMakeOffset(102)
                     .setModel("LG-H815")
                     .setAperture(1.8)
                     .setDateTimeOriginal("2015:11:12 16:46:18")
@@ -143,7 +143,7 @@
             DNG_WITH_EXIF_WITH_XMP
                     .buildUpon()
                     .clearThumbnail()
-                    .setLatitudeOffset(1692)
+                    .setGpsLatitudeOffset(1692)
                     .setMakeOffset(84)
                     .setOrientation(ExifInterface.ORIENTATION_NORMAL)
                     .setXmpResourceId(R.raw.jpeg_xmp)
@@ -175,7 +175,7 @@
     public static final ExpectedAttributes HEIF_WITH_EXIF_BELOW_API_31 =
             new Builder()
                     .setMake("LGE")
-                    .setMakeOffsetAndLength(3519, 4)
+                    .setMakeOffset(3519)
                     .setModel("Nexus 5")
                     .setImageSize(1920, 1080)
                     .setOrientation(ExifInterface.ORIENTATION_NORMAL)
@@ -203,14 +203,22 @@
 
         // GPS information.
         private boolean mHasLatLong;
-        private long mLatitudeOffset;
-        private long mLatitudeLength;
-        private double mLatitude;
-        private double mLongitude;
-        private double mAltitude;
+        private double mComputedLatitude;
+        private double mComputedLongitude;
+        private double mComputedAltitude;
+        @Nullable private String mGpsAltitude;
+        @Nullable private String mGpsAltitudeRef;
+        @Nullable private String mGpsDatestamp;
+        @Nullable private String mGpsLatitude;
+        private long mGpsLatitudeOffset;
+        private long mGpsLatitudeLength;
+        @Nullable private String mGpsLatitudeRef;
+        @Nullable private String mGpsLongitude;
+        @Nullable private String mGpsLongitudeRef;
+        @Nullable private String mGpsProcessingMethod;
+        @Nullable private String mGpsTimestamp;
 
         // Make information
-        private boolean mHasMake;
         private long mMakeOffset;
         private long mMakeLength;
         @Nullable private String mMake;
@@ -222,15 +230,6 @@
         private double mExposureTime;
         private double mFlash;
         @Nullable private String mFocalLength;
-        @Nullable private String mGpsAltitude;
-        @Nullable private String mGpsAltitudeRef;
-        @Nullable private String mGpsDatestamp;
-        @Nullable private String mGpsLatitude;
-        @Nullable private String mGpsLatitudeRef;
-        @Nullable private String mGpsLongitude;
-        @Nullable private String mGpsLongitudeRef;
-        @Nullable private String mGpsProcessingMethod;
-        @Nullable private String mGpsTimestamp;
         private int mImageLength;
         private int mImageWidth;
         @Nullable private String mIso;
@@ -254,12 +253,20 @@
             mThumbnailHeight = attributes.thumbnailHeight;
             mIsThumbnailCompressed = attributes.isThumbnailCompressed;
             mHasLatLong = attributes.hasLatLong;
-            mLatitude = attributes.latitude;
-            mLatitudeOffset = attributes.latitudeOffset;
-            mLatitudeLength = attributes.latitudeLength;
-            mLongitude = attributes.longitude;
-            mAltitude = attributes.altitude;
-            mHasMake = attributes.hasMake;
+            mComputedLatitude = attributes.computedLatitude;
+            mComputedLongitude = attributes.computedLongitude;
+            mComputedAltitude = attributes.computedAltitude;
+            mGpsAltitude = attributes.gpsAltitude;
+            mGpsAltitudeRef = attributes.gpsAltitudeRef;
+            mGpsDatestamp = attributes.gpsDatestamp;
+            mGpsLatitude = attributes.gpsLatitude;
+            mGpsLatitudeOffset = attributes.gpsLatitudeOffset;
+            mGpsLatitudeLength = attributes.gpsLatitudeLength;
+            mGpsLatitudeRef = attributes.gpsLatitudeRef;
+            mGpsLongitude = attributes.gpsLongitude;
+            mGpsLongitudeRef = attributes.gpsLongitudeRef;
+            mGpsProcessingMethod = attributes.gpsProcessingMethod;
+            mGpsTimestamp = attributes.gpsTimestamp;
             mMakeOffset = attributes.makeOffset;
             mMakeLength = attributes.makeLength;
             mMake = attributes.make;
@@ -268,15 +275,6 @@
             mDateTimeOriginal = attributes.dateTimeOriginal;
             mExposureTime = attributes.exposureTime;
             mFocalLength = attributes.focalLength;
-            mGpsAltitude = attributes.gpsAltitude;
-            mGpsAltitudeRef = attributes.gpsAltitudeRef;
-            mGpsDatestamp = attributes.gpsDatestamp;
-            mGpsLatitude = attributes.gpsLatitude;
-            mGpsLatitudeRef = attributes.gpsLatitudeRef;
-            mGpsLongitude = attributes.gpsLongitude;
-            mGpsLongitudeRef = attributes.gpsLongitudeRef;
-            mGpsProcessingMethod = attributes.gpsProcessingMethod;
-            mGpsTimestamp = attributes.gpsTimestamp;
             mImageLength = attributes.imageLength;
             mImageWidth = attributes.imageWidth;
             mIso = attributes.iso;
@@ -328,68 +326,101 @@
             return this;
         }
 
-        public Builder setLatLong(double latitude, double longitude) {
+        public Builder setComputedLatLong(double computedLatitude, double computedLongitude) {
             mHasLatLong = true;
-            mLatitude = latitude;
-            mLongitude = longitude;
+            mComputedLatitude = computedLatitude;
+            mComputedLongitude = computedLongitude;
             return this;
         }
 
-        public Builder setLatitudeOffsetAndLength(long offset, long length) {
-            mHasLatLong = true;
-            mLatitudeOffset = offset;
-            mLatitudeLength = length;
+        public Builder clearComputedLatLong() {
+            mHasLatLong = false;
+            mComputedLatitude = 0;
+            mComputedLongitude = 0;
             return this;
         }
 
-        public Builder setLatitudeOffset(long offset) {
+        public Builder setGpsAltitude(@Nullable String gpsAltitude) {
+            mGpsAltitude = gpsAltitude;
+            return this;
+        }
+
+        public Builder setGpsAltitudeRef(@Nullable String gpsAltitudeRef) {
+            mGpsAltitudeRef = gpsAltitudeRef;
+            return this;
+        }
+
+        public Builder setGpsDatestamp(@Nullable String gpsDatestamp) {
+            mGpsDatestamp = gpsDatestamp;
+            return this;
+        }
+
+        public Builder setGpsLatitude(@Nullable String gpsLatitude) {
+            mGpsLatitude = gpsLatitude;
+            return this;
+        }
+
+        public Builder setGpsLatitudeOffsetAndLength(long offset, long length) {
+            mHasLatLong = true;
+            mGpsLatitudeOffset = offset;
+            mGpsLatitudeLength = length;
+            return this;
+        }
+
+        public Builder setGpsLatitudeOffset(long offset) {
             if (!mHasLatLong) {
                 throw new IllegalStateException(
                         "Latitude position in the file must first be "
                                 + "set with setLatitudeOffsetAndLength(...)");
             }
-            mLatitudeOffset = offset;
+            mGpsLatitudeOffset = offset;
             return this;
         }
 
-        public Builder clearLatLong() {
-            mHasLatLong = false;
-            mLatitude = 0;
-            mLongitude = 0;
+        public Builder setGpsLatitudeRef(@Nullable String gpsLatitudeRef) {
+            mGpsLatitudeRef = gpsLatitudeRef;
             return this;
         }
 
-        public Builder setAltitude(double altitude) {
-            mAltitude = altitude;
+        public Builder setGpsLongitude(@Nullable String gpsLongitude) {
+            mGpsLongitude = gpsLongitude;
+            return this;
+        }
+
+        public Builder setGpsLongitudeRef(@Nullable String gpsLongitudeRef) {
+            mGpsLongitudeRef = gpsLongitudeRef;
+            return this;
+        }
+
+        public Builder setGpsProcessingMethod(@Nullable String gpsProcessingMethod) {
+            mGpsProcessingMethod = gpsProcessingMethod;
+            return this;
+        }
+
+        public Builder setGpsTimestamp(@Nullable String gpsTimestamp) {
+            mGpsTimestamp = gpsTimestamp;
+            return this;
+        }
+
+        public Builder setComputedAltitude(double computedAltitude) {
+            mComputedAltitude = computedAltitude;
             return this;
         }
 
         public Builder setMake(@Nullable String make) {
             if (make == null) {
-                mHasMake = false;
                 mMakeOffset = 0;
                 mMakeLength = 0;
             } else {
-                mHasMake = true;
                 mMake = make;
+                mMakeLength = make.length() + 1;
             }
             return this;
         }
 
-        // TODO: b/270554381 - consider deriving length automatically from `make.length() + 1`
-        //  (since the string is null-terminated in the format).
-        public Builder setMakeOffsetAndLength(long offset, long length) {
-            mHasMake = true;
-            mMakeOffset = offset;
-            mMakeLength = length;
-            return this;
-        }
-
         public Builder setMakeOffset(long offset) {
-            if (!mHasMake) {
-                throw new IllegalStateException(
-                        "Make position in the file must first be set with"
-                                + " setMakeOffsetAndLength(...)");
+            if (mMake == null) {
+                throw new IllegalStateException("Make must first be set with setMake(...)");
             }
             mMakeOffset = offset;
             return this;
@@ -425,51 +456,6 @@
             return this;
         }
 
-        public Builder setGpsAltitude(@Nullable String gpsAltitude) {
-            mGpsAltitude = gpsAltitude;
-            return this;
-        }
-
-        public Builder setGpsAltitudeRef(@Nullable String gpsAltitudeRef) {
-            mGpsAltitudeRef = gpsAltitudeRef;
-            return this;
-        }
-
-        public Builder setGpsDatestamp(@Nullable String gpsDatestamp) {
-            mGpsDatestamp = gpsDatestamp;
-            return this;
-        }
-
-        public Builder setGpsLatitude(@Nullable String gpsLatitude) {
-            mGpsLatitude = gpsLatitude;
-            return this;
-        }
-
-        public Builder setGpsLatitudeRef(@Nullable String gpsLatitudeRef) {
-            mGpsLatitudeRef = gpsLatitudeRef;
-            return this;
-        }
-
-        public Builder setGpsLongitude(@Nullable String gpsLongitude) {
-            mGpsLongitude = gpsLongitude;
-            return this;
-        }
-
-        public Builder setGpsLongitudeRef(@Nullable String gpsLongitudeRef) {
-            mGpsLongitudeRef = gpsLongitudeRef;
-            return this;
-        }
-
-        public Builder setGpsProcessingMethod(@Nullable String gpsProcessingMethod) {
-            mGpsProcessingMethod = gpsProcessingMethod;
-            return this;
-        }
-
-        public Builder setGpsTimestamp(@Nullable String gpsTimestamp) {
-            mGpsTimestamp = gpsTimestamp;
-            return this;
-        }
-
         public Builder setImageSize(int imageWidth, int imageLength) {
             mImageWidth = imageWidth;
             mImageLength = imageLength;
@@ -546,81 +532,77 @@
         }
     }
 
-    // TODO: b/270554381 - Add nullability annotations below.
-
     // Thumbnail information.
     public final boolean hasThumbnail;
     public final int thumbnailWidth;
     public final int thumbnailHeight;
     public final boolean isThumbnailCompressed;
-    // TODO: b/270554381 - Merge these offset and length (and others) into long[] arrays, and
-    //  move them down to their own section. This may also allow removing some of the hasXXX
-    // fields.
     public final long thumbnailOffset;
     public final long thumbnailLength;
 
     // GPS information.
     public final boolean hasLatLong;
-    // TODO: b/270554381 - Merge this and longitude into a double[]
-    public final double latitude;
-    public final long latitudeOffset;
-    public final long latitudeLength;
-    public final double longitude;
-    public final double altitude;
+    public final double computedLatitude;
+    public final double computedLongitude;
+    public final double computedAltitude;
+    @Nullable public final String gpsAltitude;
+    @Nullable public final String gpsAltitudeRef;
+    @Nullable public final String gpsDatestamp;
+    @Nullable public final String gpsLatitude;
+    public final long gpsLatitudeOffset;
+    public final long gpsLatitudeLength;
+    @Nullable public final String gpsLatitudeRef;
+    @Nullable public final String gpsLongitude;
+    @Nullable public final String gpsLongitudeRef;
+    @Nullable public final String gpsProcessingMethod;
+    @Nullable public final String gpsTimestamp;
 
     // Make information
-    public final boolean hasMake;
     public final long makeOffset;
     public final long makeLength;
-    public final String make;
+    @Nullable public final String make;
 
     // Values.
-    public final String model;
+    @Nullable public final String model;
     public final double aperture;
-    public final String dateTimeOriginal;
+    @Nullable public final String dateTimeOriginal;
     public final double exposureTime;
-    public final String focalLength;
-    // TODO: b/270554381 - Rename these to make them clear they're strings, or original values,
-    //  and move them closer to the (computed) latitude/longitude/altitude values. Consider
-    //  also having a verification check that they are consistent with latitude/longitude (but
-    //  not sure how to reconcile that with "don't duplicate business logic in tests").
-    public final String gpsAltitude;
-    public final String gpsAltitudeRef;
-    public final String gpsDatestamp;
-    public final String gpsLatitude;
-    public final String gpsLatitudeRef;
-    public final String gpsLongitude;
-    public final String gpsLongitudeRef;
-    public final String gpsProcessingMethod;
-    public final String gpsTimestamp;
+    @Nullable public final String focalLength;
     public final int imageLength;
     public final int imageWidth;
-    public final String iso;
+    @Nullable public final String iso;
     public final int orientation;
 
     // XMP information.
+    public final boolean hasXmp;
     @Nullable private final String mXmp;
     @Nullable private final Integer mXmpResourceId;
     @Nullable private String mMemoizedXmp;
-    public final boolean hasXmp;
     public final long xmpOffset;
     public final long xmpLength;
 
     private ExpectedAttributes(Builder builder) {
-        // TODO: b/270554381 - Re-order these assignments to match the fields above.
         hasThumbnail = builder.mHasThumbnail;
-        thumbnailOffset = builder.mThumbnailOffset;
-        thumbnailLength = builder.mThumbnailLength;
         thumbnailWidth = builder.mThumbnailWidth;
         thumbnailHeight = builder.mThumbnailHeight;
         isThumbnailCompressed = builder.mIsThumbnailCompressed;
+        thumbnailOffset = builder.mThumbnailOffset;
+        thumbnailLength = builder.mThumbnailLength;
         hasLatLong = builder.mHasLatLong;
-        latitudeOffset = builder.mLatitudeOffset;
-        latitudeLength = builder.mLatitudeLength;
-        latitude = builder.mLatitude;
-        longitude = builder.mLongitude;
-        altitude = builder.mAltitude;
-        hasMake = builder.mHasMake;
+        computedLatitude = builder.mComputedLatitude;
+        computedLongitude = builder.mComputedLongitude;
+        computedAltitude = builder.mComputedAltitude;
+        gpsAltitude = builder.mGpsAltitude;
+        gpsAltitudeRef = builder.mGpsAltitudeRef;
+        gpsDatestamp = builder.mGpsDatestamp;
+        gpsLatitude = builder.mGpsLatitude;
+        gpsLatitudeOffset = builder.mGpsLatitudeOffset;
+        gpsLatitudeLength = builder.mGpsLatitudeLength;
+        gpsLatitudeRef = builder.mGpsLatitudeRef;
+        gpsLongitude = builder.mGpsLongitude;
+        gpsLongitudeRef = builder.mGpsLongitudeRef;
+        gpsProcessingMethod = builder.mGpsProcessingMethod;
+        gpsTimestamp = builder.mGpsTimestamp;
         makeOffset = builder.mMakeOffset;
         makeLength = builder.mMakeLength;
         make = builder.mMake;
@@ -629,15 +611,6 @@
         dateTimeOriginal = builder.mDateTimeOriginal;
         exposureTime = builder.mExposureTime;
         focalLength = builder.mFocalLength;
-        gpsAltitude = builder.mGpsAltitude;
-        gpsAltitudeRef = builder.mGpsAltitudeRef;
-        gpsDatestamp = builder.mGpsDatestamp;
-        gpsLatitude = builder.mGpsLatitude;
-        gpsLatitudeRef = builder.mGpsLatitudeRef;
-        gpsLongitude = builder.mGpsLongitude;
-        gpsLongitudeRef = builder.mGpsLongitudeRef;
-        gpsProcessingMethod = builder.mGpsProcessingMethod;
-        gpsTimestamp = builder.mGpsTimestamp;
         imageLength = builder.mImageLength;
         imageWidth = builder.mImageWidth;
         iso = builder.mIso;
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 5eb80b4..863584a0 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -42,6 +42,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
 import androidx.exifinterface.media.ExifInterfaceUtils.Api21Impl;
 import androidx.exifinterface.media.ExifInterfaceUtils.Api23Impl;
 
@@ -69,6 +70,7 @@
 import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -875,27 +877,41 @@
 
     // G. Tags related to picture-taking condition
     /**
-     *  <p>Exposure time, given in seconds.</p>
+     * Exposure time, given in seconds.
      *
-     *  <ul>
-     *      <li>Tag = 33434</li>
-     *      <li>Type = Unsigned rational</li>
-     *      <li>Count = 1</li>
-     *      <li>Default = None</li>
-     *  </ul>
+     * <p>Note: For backwards compatibility this attribute is returned from {@link
+     * #getAttribute(String)} in decimal form (i.e. the format produced by {@link
+     * Double#toString(double)}). It is accepted into {@link #setAttribute(String, String)} in both
+     * rational (e.g. {@code "1/3"}) and decimal forms. The decimal format is anything accepted by
+     * {@link Double#parseDouble(String)}, e.g. {@code "0.125"}.
+     *
+     * <ul>
+     *   <li>Tag = 33434
+     *   <li>Type = Unsigned rational
+     *   <li>Count = 1
+     *   <li>Default = None
+     * </ul>
      */
     public static final String TAG_EXPOSURE_TIME = "ExposureTime";
+
     /**
-     *  <p>The F number.</p>
+     * The F number.
      *
-     *  <ul>
-     *      <li>Tag = 33437</li>
-     *      <li>Type = Unsigned rational</li>
-     *      <li>Count = 1</li>
-     *      <li>Default = None</li>
-     *  </ul>
+     * <p>Note: For backwards compatibility this attribute is returned from {@link
+     * #getAttribute(String)} in decimal form (i.e. the format produced by {@link
+     * Double#toString(double)}). It is accepted into {@link #setAttribute(String, String)} in both
+     * rational (e.g. {@code "1/3"}) and decimal forms. The decimal format is anything accepted by
+     * {@link Double#parseDouble(String)}, e.g. {@code "0.125"}.
+     *
+     * <ul>
+     *   <li>Tag = 33437
+     *   <li>Type = Unsigned rational
+     *   <li>Count = 1
+     *   <li>Default = None
+     * </ul>
      */
     public static final String TAG_F_NUMBER = "FNumber";
+
     /**
      *  <p>The class of the program used by the camera to set exposure when the picture is taken.
      *  The tag values are as follows.</p>
@@ -1109,19 +1125,28 @@
      *  </ul>
      */
     public static final String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
+
     /**
-     *  <p>The distance to the subject, given in meters. Note that if the numerator of the recorded
-     *  value is 0xFFFFFFFF, Infinity shall be indicated; and if the numerator is 0, Distance
-     *  unknown shall be indicated.</p>
+     * The distance to the subject, given in meters.
      *
-     *  <ul>
-     *      <li>Tag = 37382</li>
-     *      <li>Type = Unsigned rational</li>
-     *      <li>Count = 1</li>
-     *      <li>Default = None</li>
-     *  </ul>
+     * <p>Note that if the numerator of the recorded value is 0xFFFFFFFF, Infinity shall be
+     * indicated; and if the numerator is 0, Distance unknown shall be indicated.
+     *
+     * <p>Note: For backwards compatibility this attribute is returned from {@link
+     * #getAttribute(String)} in decimal form (i.e. the format produced by {@link
+     * Double#toString(double)}). It is accepted into {@link #setAttribute(String, String)} in both
+     * rational (e.g. {@code "1/3"}) and decimal forms. The decimal format is anything accepted by
+     * {@link Double#parseDouble(String)}, e.g. {@code "0.125"}.
+     *
+     * <ul>
+     *   <li>Tag = 37382
+     *   <li>Type = Unsigned rational
+     *   <li>Count = 1
+     *   <li>Default = None
+     * </ul>
      */
     public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance";
+
     /**
      *  <p>The metering mode.</p>
      *
@@ -1443,18 +1468,26 @@
      *  @see #WHITEBALANCE_MANUAL
      */
     public static final String TAG_WHITE_BALANCE = "WhiteBalance";
+
     /**
-     *  <p>This tag indicates the digital zoom ratio when the image was shot. If the numerator of
-     *  the recorded value is 0, this indicates that digital zoom was not used.</p>
+     * This tag indicates the digital zoom ratio when the image was shot. If the numerator of the
+     * recorded value is 0, this indicates that digital zoom was not used.
      *
-     *  <ul>
-     *      <li>Tag = 41988</li>
-     *      <li>Type = Unsigned rational</li>
-     *      <li>Count = 1</li>
-     *      <li>Default = None</li>
-     *  </ul>
+     * <p>Note: For backwards compatibility this attribute is returned from {@link
+     * #getAttribute(String)} in decimal form (i.e. the format produced by {@link
+     * Double#toString(double)}). It is accepted into {@link #setAttribute(String, String)} in both
+     * rational (e.g. {@code "1/3"}) and decimal forms. The decimal format is anything accepted by
+     * {@link Double#parseDouble(String)}, e.g. {@code "0.125"}.
+     *
+     * <ul>
+     *   <li>Tag = 41988
+     *   <li>Type = Unsigned rational
+     *   <li>Count = 1
+     *   <li>Default = None
+     * </ul>
      */
     public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
+
     /**
      *  <p>This tag indicates the equivalent focal length assuming a 35mm film camera, in mm.
      *  A value of 0 means the focal length is unknown. Note that this tag differs from
@@ -1784,18 +1817,24 @@
      *  </ul>
      */
     public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
+
     /**
-     *  <p>Indicates the time as UTC (Coordinated Universal Time). TimeStamp is expressed as three
-     *  unsigned rational values giving the hour, minute, and second.</p>
+     * Indicates the time as UTC (Coordinated Universal Time). TimeStamp is expressed as three
+     * unsigned rational values giving the hour, minute, and second.
      *
-     *  <ul>
-     *      <li>Tag = 7</li>
-     *      <li>Type = Unsigned rational</li>
-     *      <li>Count = 3</li>
-     *      <li>Default = None</li>
-     *  </ul>
+     * <p>Note: This attribute is returned from {@link #getAttribute(String)} and accepted into
+     * {@link #setAttribute(String, String)} as 3 colon-separated integers, e.g. {@code "11:05:32"}.
+     * Decimal or rational hours, minutes or seconds parts are not supported.
+     *
+     * <ul>
+     *   <li>Tag = 7
+     *   <li>Type = Unsigned rational
+     *   <li>Count = 3
+     *   <li>Default = None
+     * </ul>
      */
     public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
+
     /**
      *  <p>Indicates the GPS satellites used for measurements. This tag may be used to describe
      *  the number of satellites, their ID number, angle of elevation, azimuth, SNR and other
@@ -2997,12 +3036,9 @@
             (byte) 0x47, (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a};
     // See "Extensions to the PNG 1.2 Specification, Version 1.5.0",
     // 3.7. eXIf Exchangeable Image File (Exif) Profile
-    private static final byte[] PNG_CHUNK_TYPE_EXIF = new byte[]{(byte) 0x65, (byte) 0x58,
-            (byte) 0x49, (byte) 0x66};
-    private static final byte[] PNG_CHUNK_TYPE_IHDR = new byte[]{(byte) 0x49, (byte) 0x48,
-            (byte) 0x44, (byte) 0x52};
-    private static final byte[] PNG_CHUNK_TYPE_IEND = new byte[]{(byte) 0x49, (byte) 0x45,
-            (byte) 0x4e, (byte) 0x44};
+    private static final int PNG_CHUNK_TYPE_EXIF = intFromBytes('e', 'X', 'I', 'f');
+    private static final int PNG_CHUNK_TYPE_IHDR = intFromBytes('I', 'H', 'D', 'R');
+    private static final int PNG_CHUNK_TYPE_IEND = intFromBytes('I', 'E', 'N', 'D');
     private static final int PNG_CHUNK_TYPE_BYTE_LENGTH = 4;
     private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4;
 
@@ -3074,16 +3110,13 @@
     };
 
     // A class for indicating EXIF rational type.
-    private static class Rational {
+    // TODO: b/308978831 - Migrate to android.util.Rational when the min API is 21.
+    @VisibleForTesting
+    static class Rational {
         public final long numerator;
         public final long denominator;
 
         @SuppressWarnings("WeakerAccess") /* synthetic access */
-        Rational(double value) {
-            this((long) (value * 10000), 10000);
-        }
-
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
         Rational(long numerator, long denominator) {
             // Handle erroneous case
             if (denominator == 0) {
@@ -3095,6 +3128,43 @@
             this.denominator = denominator;
         }
 
+        /**
+         * Creates a new {@code Rational} which approximates the provided {@code double} value by
+         * using <a href="https://en.wikipedia.org/wiki/Continued_fraction">continued fractions</a>.
+         */
+        @NonNull
+        public static Rational createFromDouble(double value) {
+            if (value >= Long.MAX_VALUE || value <= Long.MIN_VALUE) {
+                // value is too large to represent as a long, so just return the max/min value.
+                return new Rational(
+                        /* numerator= */ value > 0 ? Long.MAX_VALUE : Long.MIN_VALUE,
+                        /* denominator= */ 1);
+            }
+
+            double absoluteValue = Math.abs(value);
+            double threshold = 0.00000001 * absoluteValue;
+            double remainingValue = absoluteValue;
+            long numerator = 1;
+            long previousNumerator = 0;
+            long denominator = 0;
+            long previousDenominator = 1;
+            do {
+                double remainder = remainingValue % 1;
+                long wholePart = (long) (remainingValue - remainder);
+                long tmp = numerator;
+                numerator = wholePart * numerator + previousNumerator;
+                previousNumerator = tmp;
+
+                tmp = denominator;
+                denominator = wholePart * denominator + previousDenominator;
+                previousDenominator = tmp;
+
+                remainingValue = 1 / remainder;
+            } while ((Math.abs(absoluteValue - numerator / (double) denominator) > threshold));
+
+            return new Rational(value < 0 ? -numerator : numerator, denominator);
+        }
+
         @NonNull
         @Override
         public String toString() {
@@ -3793,9 +3863,30 @@
     @SuppressWarnings("unchecked")
     private static final HashMap<String, ExifTag>[] sExifTagMapsForWriting =
             new HashMap[EXIF_TAGS.length];
-    private static final HashSet<String> sTagSetForCompatibility = new HashSet<>(Arrays.asList(
-            TAG_F_NUMBER, TAG_DIGITAL_ZOOM_RATIO, TAG_EXPOSURE_TIME, TAG_SUBJECT_DISTANCE,
-            TAG_GPS_TIMESTAMP));
+
+    /**
+     * These are tags of type 'Unsigned rational' but which are handled in decimal form.
+     *
+     * <p>This means they are output from {@link #getAttribute(String)}, and accepted into {@link
+     * #setAttribute(String, String)}, as strings in decimal form (e.g. {@code "0.125"}, {@code
+     * "6.25E-4"}).
+     *
+     * <p>This is to maintain backwards compatibility with a previous implementation of the {@link
+     * android.media.ExifInterface} (the platform variant of this class).
+     *
+     * <p>See <a
+     * href="http://ag/c/platform/frameworks/base/+/909922/2..9/api/current.txt#b20093">this
+     * internal code review comment from 2016</a> for more details.
+     */
+    private static final Set<String> RATIONAL_TAGS_HANDLED_AS_DECIMALS_FOR_COMPATIBILITY =
+            Collections.unmodifiableSet(
+                    new HashSet<>(
+                            Arrays.asList(
+                                    TAG_F_NUMBER,
+                                    TAG_DIGITAL_ZOOM_RATIO,
+                                    TAG_EXPOSURE_TIME,
+                                    TAG_SUBJECT_DISTANCE)));
+
     // Mappings from tag number to IFD type for pointer tags.
     private static final HashMap<Integer, Integer> sExifPointerTagMap = new HashMap<>();
 
@@ -4116,34 +4207,35 @@
             throw new NullPointerException("tag shouldn't be null");
         }
         ExifAttribute attribute = getExifAttribute(tag);
-        if (attribute != null) {
-            if (!sTagSetForCompatibility.contains(tag)) {
-                return attribute.getStringValue(mExifByteOrder);
+        if (attribute == null) {
+            return null;
+        }
+        if (tag.equals(TAG_GPS_TIMESTAMP)) {
+            // Convert GPS timestamp value to a custom format for backwards compatibility.
+            if (attribute.format != IFD_FORMAT_URATIONAL
+                    && attribute.format != IFD_FORMAT_SRATIONAL) {
+                Log.w(TAG, "GPS Timestamp format is not rational. format=" + attribute.format);
+                return null;
             }
-            if (tag.equals(TAG_GPS_TIMESTAMP)) {
-                // Convert the rational values to the custom formats for backwards compatibility.
-                if (attribute.format != IFD_FORMAT_URATIONAL
-                        && attribute.format != IFD_FORMAT_SRATIONAL) {
-                    Log.w(TAG, "GPS Timestamp format is not rational. format=" + attribute.format);
-                    return null;
-                }
-                Rational[] array = (Rational[]) attribute.getValue(mExifByteOrder);
-                if (array == null || array.length != 3) {
-                    Log.w(TAG, "Invalid GPS Timestamp array. array=" + Arrays.toString(array));
-                    return null;
-                }
-                return String.format("%02d:%02d:%02d",
-                        (int) ((float) array[0].numerator / array[0].denominator),
-                        (int) ((float) array[1].numerator / array[1].denominator),
-                        (int) ((float) array[2].numerator / array[2].denominator));
+            Rational[] array = (Rational[]) attribute.getValue(mExifByteOrder);
+            if (array == null || array.length != 3) {
+                Log.w(TAG, "Invalid GPS Timestamp array. array=" + Arrays.toString(array));
+                return null;
             }
+            return String.format("%02d:%02d:%02d",
+                    (int) ((float) array[0].numerator / array[0].denominator),
+                    (int) ((float) array[1].numerator / array[1].denominator),
+                    (int) ((float) array[2].numerator / array[2].denominator));
+        } else if (RATIONAL_TAGS_HANDLED_AS_DECIMALS_FOR_COMPATIBILITY.contains(tag)) {
+            // Convert the rational values to the custom formats for backwards compatibility.
             try {
                 return Double.toString(attribute.getDoubleValue(mExifByteOrder));
             } catch (NumberFormatException e) {
                 return null;
             }
+        } else {
+            return attribute.getStringValue(mExifByteOrder);
         }
-        return null;
     }
 
     /**
@@ -4205,10 +4297,45 @@
         if (tag == null) {
             throw new NullPointerException("tag shouldn't be null");
         }
-        // Validate and convert if necessary.
-        if (TAG_DATETIME.equals(tag) || TAG_DATETIME_ORIGINAL.equals(tag)
-                || TAG_DATETIME_DIGITIZED.equals(tag)) {
-            if (value != null) {
+
+        // Maintain compatibility.
+        if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
+            if (DEBUG) {
+                Log.d(TAG, "setAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
+                        + "TAG_PHOTOGRAPHIC_SENSITIVITY.");
+            }
+            tag = TAG_PHOTOGRAPHIC_SENSITIVITY;
+        }
+        // Maybe convert the given value for backwards compatibility.
+        if (value != null) {
+            if (RATIONAL_TAGS_HANDLED_AS_DECIMALS_FOR_COMPATIBILITY.contains(tag)
+                    && !value.contains("/")) {
+                // Convert floating point values to rational for rational tags that are emitted and
+                // consumed as floating point values for backwards compatibility.
+                try {
+                    double doubleValue = Double.parseDouble(value);
+                    value = Rational.createFromDouble(doubleValue).toString();
+                } catch (NumberFormatException e) {
+                    Log.w(TAG, "Invalid value for " + tag + " : " + value);
+                    return;
+                }
+            } else if (tag.equals(TAG_GPS_TIMESTAMP)) {
+                Matcher m = GPS_TIMESTAMP_PATTERN.matcher(value);
+                if (!m.find()) {
+                    Log.w(TAG, "Invalid value for " + tag + " : " + value);
+                    return;
+                }
+                value =
+                        Integer.parseInt(m.group(1))
+                                + "/1,"
+                                + Integer.parseInt(m.group(2))
+                                + "/1,"
+                                + Integer.parseInt(m.group(3))
+                                + "/1";
+            } else if (TAG_DATETIME.equals(tag)
+                    || TAG_DATETIME_ORIGINAL.equals(tag)
+                    || TAG_DATETIME_DIGITIZED.equals(tag)) {
+                // Validate and convert datetime values if necessary.
                 boolean isPrimaryFormat = DATETIME_PRIMARY_FORMAT_PATTERN.matcher(value).find();
                 boolean isSecondaryFormat = DATETIME_SECONDARY_FORMAT_PATTERN.matcher(value).find();
                 // Validate
@@ -4218,8 +4345,8 @@
                     return;
                 }
                 // If datetime value has secondary format (e.g. 2020-01-01 00:00:00), convert it to
-                // primary format (e.g. 2020:01:01 00:00:00) since it is the format in the
-                // official documentation.
+                // primary format (e.g. 2020:01:01 00:00:00) since it is the format in the official
+                // documentation.
                 // See JEITA CP-3451C Section 4.6.4. D. Other Tags, DateTime
                 if (isSecondaryFormat) {
                     // Replace "-" with ":" to match the primary format.
@@ -4227,34 +4354,6 @@
                 }
             }
         }
-        // Maintain compatibility.
-        if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
-            if (DEBUG) {
-                Log.d(TAG, "setAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
-                        + "TAG_PHOTOGRAPHIC_SENSITIVITY.");
-            }
-            tag = TAG_PHOTOGRAPHIC_SENSITIVITY;
-        }
-        // Convert the given value to rational values for backwards compatibility.
-        if (value != null && sTagSetForCompatibility.contains(tag)) {
-            if (tag.equals(TAG_GPS_TIMESTAMP)) {
-                Matcher m = GPS_TIMESTAMP_PATTERN.matcher(value);
-                if (!m.find()) {
-                    Log.w(TAG, "Invalid value for " + tag + " : " + value);
-                    return;
-                }
-                value = Integer.parseInt(m.group(1)) + "/1," + Integer.parseInt(m.group(2)) + "/1,"
-                        + Integer.parseInt(m.group(3)) + "/1";
-            } else {
-                try {
-                    double doubleValue = Double.parseDouble(value);
-                    value = new Rational(doubleValue).toString();
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "Invalid value for " + tag + " : " + value);
-                    return;
-                }
-            }
-        }
 
         for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
             if (i == IFD_TYPE_THUMBNAIL && !mHasThumbnail) {
@@ -5058,8 +5157,10 @@
         setAltitude(location.getAltitude());
         // Location objects store speeds in m/sec. Translates it to km/hr here.
         setAttribute(TAG_GPS_SPEED_REF, "K");
-        setAttribute(TAG_GPS_SPEED, new Rational(location.getSpeed()
-                * TimeUnit.HOURS.toSeconds(1) / 1000).toString());
+        setAttribute(
+                TAG_GPS_SPEED,
+                Rational.createFromDouble(location.getSpeed() * TimeUnit.HOURS.toSeconds(1) / 1000)
+                        .toString());
         String[] dateTime = sFormatterPrimary.format(
                 new Date(location.getTime())).split("\\s+", -1);
         setAttribute(ExifInterface.TAG_GPS_DATESTAMP, dateTime[0]);
@@ -5111,7 +5212,7 @@
      */
     public void setAltitude(double altitude) {
         String ref = altitude >= 0 ? "0" : "1";
-        setAttribute(TAG_GPS_ALTITUDE, new Rational(Math.abs(altitude)).toString());
+        setAttribute(TAG_GPS_ALTITUDE, Rational.createFromDouble(Math.abs(altitude)).toString());
         setAttribute(TAG_GPS_ALTITUDE_REF, ref);
     }
 
@@ -5312,7 +5413,7 @@
             }
         } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
             // Not valid
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException(e);
         }
     }
 
@@ -5953,7 +6054,7 @@
                 }
             } catch (RuntimeException e) {
                 throw new UnsupportedOperationException("Failed to read EXIF from HEIF file. "
-                        + "Given stream is either malformed or unsupported.");
+                        + "Given stream is either malformed or unsupported.", e);
             } finally {
                 try {
                     retriever.release();
@@ -6115,11 +6216,10 @@
         // 2.1. Integers and byte order
         in.setByteOrder(BIG_ENDIAN);
 
-        int bytesRead = 0;
+        int startPosition = in.position();
 
         // Skip the signature bytes
         in.skipFully(PNG_SIGNATURE.length);
-        bytesRead += PNG_SIGNATURE.length;
 
         // Each chunk is made up of four parts:
         //   1) Length: 4-byte unsigned integer indicating the number of bytes in the
@@ -6134,22 +6234,23 @@
         try {
             while (true) {
                 int length = in.readInt();
-                bytesRead += 4;
 
-                byte[] type = new byte[PNG_CHUNK_TYPE_BYTE_LENGTH];
-                in.readFully(type);
-                bytesRead += PNG_CHUNK_TYPE_BYTE_LENGTH;
+                int type = in.readInt();
 
                 // The first chunk must be the IHDR chunk
-                if (bytesRead == 16 && !Arrays.equals(type, PNG_CHUNK_TYPE_IHDR)) {
-                    throw new IOException("Encountered invalid PNG file--IHDR chunk should appear"
-                            + "as the first chunk");
+                if (in.position() - startPosition == 16 && type != PNG_CHUNK_TYPE_IHDR) {
+                    throw new IOException(
+                            "Encountered invalid PNG file--IHDR chunk should appear as the first "
+                                    + "chunk");
                 }
 
-                if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
+                if (type == PNG_CHUNK_TYPE_IEND) {
                     // IEND marks the end of the image.
                     break;
-                } else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
+                } else if (type == PNG_CHUNK_TYPE_EXIF) {
+                    // Save offset to EXIF data for handling thumbnail and attribute offsets.
+                    mOffsetToExifData = in.position() - startPosition;
+
                     // TODO: Need to handle potential OutOfMemoryError
                     byte[] data = new byte[length];
                     in.readFully(data);
@@ -6158,15 +6259,13 @@
                     int dataCrcValue = in.readInt();
                     // Cyclic Redundancy Code used to check for corruption of the data
                     CRC32 crc = new CRC32();
-                    crc.update(type);
+                    updateCrcWithInt(crc, type);
                     crc.update(data);
                     if ((int) crc.getValue() != dataCrcValue) {
                         throw new IOException("Encountered invalid CRC value for PNG-EXIF chunk."
                                 + "\n recorded CRC value: " + dataCrcValue + ", calculated CRC "
                                 + "value: " + crc.getValue());
                     }
-                    // Save offset to EXIF data for handling thumbnail and attribute offsets.
-                    mOffsetToExifData = bytesRead;
                     readExifSegment(data, IFD_TYPE_PRIMARY);
                     validateImages();
 
@@ -6175,16 +6274,22 @@
                 } else {
                     // Skip to next chunk
                     in.skipFully(length + PNG_CHUNK_CRC_BYTE_LENGTH);
-                    bytesRead += length + PNG_CHUNK_CRC_BYTE_LENGTH;
                 }
             }
         } catch (EOFException e) {
             // Should not reach here. Will only reach here if the file is corrupted or
             // does not follow the PNG specifications
-            throw new IOException("Encountered corrupt PNG file.");
+            throw new IOException("Encountered corrupt PNG file.", e);
         }
     }
 
+    private static void updateCrcWithInt(CRC32 crc, int value) {
+        crc.update(value >>> 24);
+        crc.update(value >>> 16);
+        crc.update(value >>> 8);
+        crc.update(value);
+    }
+
     // WebP contains EXIF data as a RIFF File Format Chunk
     // All references below can be found in the following link.
     // https://developers.google.com/speed/webp/docs/riff_container
@@ -6264,7 +6369,7 @@
         } catch (EOFException e) {
             // Should not reach here. Will only reach here if the file is corrupted or
             // does not follow the WebP specifications
-            throw new IOException("Encountered corrupt WebP file.");
+            throw new IOException("Encountered corrupt WebP file.", e);
         }
     }
 
@@ -7535,7 +7640,7 @@
             case IMAGE_TYPE_PNG:
                 // Write PNG specific data (chunk size, chunk type)
                 dataOutputStream.writeInt(totalSize);
-                dataOutputStream.write(PNG_CHUNK_TYPE_EXIF);
+                dataOutputStream.writeInt(PNG_CHUNK_TYPE_EXIF);
                 break;
             case IMAGE_TYPE_WEBP:
                 // Write WebP specific data (chunk type, chunk size)
@@ -8149,4 +8254,12 @@
         }
         return false;
     }
+
+    /*
+     * Combines the lower eight bits of each parameter into a 32-bit int. {@code b1} is the highest
+     * byte of the result, {@code b4} is the lowest.
+     */
+    private static int intFromBytes(int b1, int b2, int b3, int b4) {
+        return ((b1 & 0xFF) << 24) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 8) | (b4 & 0xFF);
+    }
 }
diff --git a/libraryversions.toml b/libraryversions.toml
index 0616576..9d9a809 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -24,9 +24,9 @@
 COLLECTION = "1.5.0-alpha01"
 COMPOSE = "1.7.0-alpha08"
 COMPOSE_COMPILER = "1.5.12"  # Update when preparing for a release
-COMPOSE_MATERIAL3 = "1.3.0-alpha05"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha11"
-COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha06"
+COMPOSE_MATERIAL3 = "1.3.0-alpha06"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha12"
+COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha07"
 COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
 COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
 CONSTRAINTLAYOUT = "2.2.0-alpha13"
@@ -157,8 +157,8 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.1.0-rc01"
 WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.4.0-alpha07"
-WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha21"
+WEAR_COMPOSE = "1.4.0-alpha08"
+WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha22"
 WEAR_CORE = "1.0.0-alpha01"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
index 68bdfd0..bf75d84 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
@@ -1065,8 +1065,10 @@
                         route.getProviderInstance()
                                 .onCreateRouteController(
                                         route.mDescriptorId, mSelectedRoute.mDescriptorId);
-                controller.onSelect();
-                mRouteControllerMap.put(route.mUniqueId, controller);
+                if (controller != null) {
+                    controller.onSelect();
+                    mRouteControllerMap.put(route.mUniqueId, controller);
+                }
             }
         }
     }
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index 099e27d..d8aade0e 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -227,6 +227,7 @@
     method public final String? getRoute();
     method public boolean hasDeepLink(android.net.Uri deepLink);
     method public boolean hasDeepLink(androidx.navigation.NavDeepLinkRequest deepLinkRequest);
+    method public static final <T> boolean hasRoute(androidx.navigation.NavDestination, kotlin.reflect.KClass<T> route);
     method @CallSuper public void onInflate(android.content.Context context, android.util.AttributeSet attrs);
     method protected static final <C> Class<? extends C?> parseClassFromName(android.content.Context context, String name, Class<? extends C?> expectedClassType);
     method public final void putAction(@IdRes int actionId, androidx.navigation.NavAction action);
@@ -253,6 +254,7 @@
   public static final class NavDestination.Companion {
     method public kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
     method public inline <reified T> boolean hasRoute(androidx.navigation.NavDestination);
+    method public <T> boolean hasRoute(androidx.navigation.NavDestination, kotlin.reflect.KClass<T> route);
     method protected <C> Class<? extends C?> parseClassFromName(android.content.Context context, String name, Class<? extends C?> expectedClassType);
   }
 
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index 099e27d..d8aade0e 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -227,6 +227,7 @@
     method public final String? getRoute();
     method public boolean hasDeepLink(android.net.Uri deepLink);
     method public boolean hasDeepLink(androidx.navigation.NavDeepLinkRequest deepLinkRequest);
+    method public static final <T> boolean hasRoute(androidx.navigation.NavDestination, kotlin.reflect.KClass<T> route);
     method @CallSuper public void onInflate(android.content.Context context, android.util.AttributeSet attrs);
     method protected static final <C> Class<? extends C?> parseClassFromName(android.content.Context context, String name, Class<? extends C?> expectedClassType);
     method public final void putAction(@IdRes int actionId, androidx.navigation.NavAction action);
@@ -253,6 +254,7 @@
   public static final class NavDestination.Companion {
     method public kotlin.sequences.Sequence<androidx.navigation.NavDestination> getHierarchy(androidx.navigation.NavDestination);
     method public inline <reified T> boolean hasRoute(androidx.navigation.NavDestination);
+    method public <T> boolean hasRoute(androidx.navigation.NavDestination, kotlin.reflect.KClass<T> route);
     method protected <C> Class<? extends C?> parseClassFromName(android.content.Context context, String name, Class<? extends C?> expectedClassType);
   }
 
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index f1515cd..468e617 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -31,6 +31,7 @@
 import androidx.navigation.common.R
 import java.util.regex.Pattern
 import kotlin.reflect.KClass
+import kotlinx.serialization.InternalSerializationApi
 import kotlinx.serialization.serializer
 
 /**
@@ -828,7 +829,18 @@
          * @param T the route from KClass
          */
         @JvmStatic
-        public inline fun <reified T : Any> NavDestination.hasRoute() =
-            serializer<T>().hashCode() == id
+        public inline fun <reified T : Any> NavDestination.hasRoute() = hasRoute(T::class)
+
+        /**
+         * Checks if the NavDestination's route was generated from [T]
+         *
+         * Returns true if equal, false otherwise.
+         *
+         * @param route the route from KClass
+         */
+        @OptIn(InternalSerializationApi::class)
+        @JvmStatic
+        public fun <T : Any> NavDestination.hasRoute(route: KClass<T>) =
+            route.serializer().hashCode() == id
     }
 }
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle b/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle
index 140b0aa..03a6aca 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/build.gradle
@@ -28,10 +28,12 @@
     id("com.android.library")
     id("AndroidXComposePlugin")
     id("org.jetbrains.kotlin.android")
+    alias(libs.plugins.kotlinSerialization)
 }
 
 dependencies {
     implementation(libs.kotlinStdlib)
+    implementation(libs.kotlinSerializationCore)
 
     implementation(project(":compose:integration-tests:demos:common"))
     implementation(project(":compose:foundation:foundation"))
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
index cc5f036..7c130b8 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
@@ -27,15 +27,18 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavDestination.Companion.hasRoute
 import androidx.navigation.NavDestination.Companion.hierarchy
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.dialog
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.samples.Dashboard
+import androidx.navigation.compose.samples.Destination
+import androidx.navigation.compose.samples.DialogContent
 import androidx.navigation.compose.samples.Profile
-import androidx.navigation.compose.samples.Screen
 import androidx.navigation.compose.samples.Scrollable
 
 @Composable
@@ -43,9 +46,9 @@
     val navController = rememberNavController()
 
     val items = listOf(
-        stringResource(R.string.profile) to Screen.Profile.route,
-        stringResource(R.string.dashboard) to Screen.Dashboard.route,
-        stringResource(R.string.scrollable) to Screen.Scrollable.route
+        stringResource(R.string.profile) to Destination.Profile,
+        stringResource(R.string.dashboard) to Destination.Dashboard(),
+        stringResource(R.string.scrollable) to Destination.Scrollable
     )
 
     Scaffold(
@@ -53,13 +56,15 @@
             BottomNavigation {
                 val navBackStackEntry = navController.currentBackStackEntryAsState().value
                 val currentDestination = navBackStackEntry?.destination
-                items.forEach { (name, route) ->
+                items.forEach { (name, destination) ->
                     BottomNavigationItem(
                         icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
                         label = { Text(name) },
-                        selected = currentDestination?.hierarchy?.any { it.route == route } == true,
+                        selected = currentDestination?.hierarchy?.any {
+                            it.hasRoute(destination.route)
+                        } == true,
                         onClick = {
-                            navController.navigate(route) {
+                            navController.navigate(destination) {
                                 launchSingleTop = true
                                 restoreState = true
                                 popUpTo(navController.graph.findStartDestination().id) {
@@ -72,10 +77,11 @@
             }
         }
     ) { innerPadding ->
-        NavHost(navController, Screen.Profile.route, Modifier.padding(innerPadding)) {
-            composable(Screen.Profile.route) { Profile(navController) }
-            composable(Screen.Dashboard.route) { Dashboard(navController) }
-            composable(Screen.Scrollable.route) { Scrollable(navController) }
+        NavHost(navController, Destination.Profile.route, Modifier.padding(innerPadding)) {
+            composable<Destination.Profile> { Profile(navController) }
+            composable<Destination.Dashboard> { Dashboard(navController) }
+            composable<Destination.Scrollable> { Scrollable(navController) }
+            dialog<Destination.Dialog> { DialogContent(navController) }
         }
     }
 }
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt
index 9951b1d..8eccc0d2 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavByDeepLinkDemo.kt
@@ -39,23 +39,27 @@
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.samples.Dashboard
-import androidx.navigation.compose.samples.Screen
-import androidx.navigation.navArgument
+import androidx.navigation.compose.samples.Destination
 import androidx.navigation.navDeepLink
+import androidx.navigation.toRoute
 
 @Composable
-@Suppress("DEPRECATION")
 fun NavByDeepLinkDemo() {
     val navController = rememberNavController()
-    val uri = "https://example.com/dashboard?userId="
-    NavHost(navController, startDestination = Screen.Profile.route) {
-        composable(Screen.Profile.route) { ProfileWithDeepLink(navController, uri) }
-        composable(
-            Screen.Dashboard.route,
-            arguments = listOf(navArgument("userId") { defaultValue = "no value given" }),
-            deepLinks = listOf(navDeepLink { uriPattern = "$uri{userId}" })
+    val basePath = "https://example.com"
+    NavHost(navController, startDestination = Destination.Profile.route) {
+        composable<Destination.Profile> {
+            ProfileWithDeepLink(
+                navController,
+                "$basePath?userId="
+            )
+        }
+        composable<Destination.Dashboard>(
+            // use the same args from Destination.Dashboard with custom uri base path
+            deepLinks = listOf(navDeepLink<Destination.Dashboard>(basePath))
         ) { backStackEntry ->
-            Dashboard(navController, backStackEntry.arguments?.get("userId") as? String)
+            val dashboard = backStackEntry.toRoute<Destination.Dashboard>()
+            Dashboard(navController, dashboard.userId)
         }
     }
 }
@@ -63,7 +67,7 @@
 @Composable
 fun ProfileWithDeepLink(navController: NavController, uri: String) {
     Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
-        Text(text = stringResource(Screen.Profile.resourceId))
+        Text(text = stringResource(Destination.Profile.resourceId))
         Divider(color = Color.Black)
         val state = rememberSaveable { mutableStateOf("") }
         Box {
@@ -75,6 +79,7 @@
         }
         Divider(color = Color.Black)
         Button(
+            // navigate with deeplink
             onClick = { navController.navigate(Uri.parse(uri + state.value)) },
             colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
             modifier = Modifier.fillMaxWidth()
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
index a39e8ab..eed030c 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
@@ -31,16 +31,19 @@
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
+import androidx.navigation.toRoute
+import kotlinx.serialization.Serializable
+
+@Serializable
+class NumberedDestination(val number: Int)
 
 @Composable
 fun NavPopUpToDemo() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = "1") {
-        composable("1") { NumberedScreen(navController, 1) }
-        composable("2") { NumberedScreen(navController, 2) }
-        composable("3") { NumberedScreen(navController, 3) }
-        composable("4") { NumberedScreen(navController, 4) }
-        composable("5") { NumberedScreen(navController, 5) }
+    NavHost(navController, startDestination = NumberedDestination(1)) {
+        composable<NumberedDestination> {
+            NumberedScreen(navController, it.toRoute<NumberedDestination>().number)
+        }
     }
 }
 
@@ -50,7 +53,7 @@
         val next = number + 1
         if (number < 5) {
             Button(
-                onClick = { navController.navigate("$next") },
+                onClick = { navController.navigate(NumberedDestination(next)) },
                 colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
                 modifier = Modifier.fillMaxWidth()
             ) {
@@ -59,8 +62,11 @@
         }
         Text("This is screen $number", Modifier.weight(1f))
         if (navController.previousBackStackEntry != null) {
+            val firstScreen = NumberedDestination(1)
             Button(
-                onClick = { navController.navigate("1") { popUpTo("1") { inclusive = true } } },
+                onClick = { navController.navigate(firstScreen) {
+                    popUpTo(firstScreen) { inclusive = true }
+                } },
                 colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray),
                 modifier = Modifier.fillMaxWidth()
             ) {
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
index 051d40b..6250ce4 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
@@ -32,6 +32,8 @@
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.samples.NavigateButton
+import androidx.navigation.toRoute
+import kotlinx.serialization.Serializable
 
 @Composable
 fun NavSingleTopDemo() {
@@ -44,21 +46,28 @@
             placeholder = { Text("Search") }
         )
         NavigateButton("Search") {
-            navController.navigate("search/" + query.value) {
+            navController.navigate(SearchScreen(query.value)) {
                 launchSingleTop = true
             }
         }
-        NavHost(navController, startDestination = "start") {
-            composable("start") { StartScreen() }
-            composable("search/{query}") { backStackEntry ->
+        NavHost(navController, startDestination = StartScreen::class) {
+            composable<StartScreen> { StartScreen() }
+            composable<SearchScreen> { backStackEntry ->
+                val args = backStackEntry.toRoute<SearchScreen>()
                 SearchResultScreen(
-                    backStackEntry.arguments!!.getString("query", "no query entered")
+                    args.query
                 )
             }
         }
     }
 }
 
+@Serializable
+object StartScreen
+
+@Serializable
+data class SearchScreen(val query: String)
+
 @Composable
 fun StartScreen() {
     Divider(color = Color.Black)
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
index 3b6d9f1..025a5e1 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
@@ -16,56 +16,23 @@
 
 package androidx.navigation.compose.demos
 
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.Divider
-import androidx.compose.material.Text
-import androidx.compose.material.TextField
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.samples.Dashboard
-import androidx.navigation.compose.samples.NavigateButton
-import androidx.navigation.compose.samples.Screen
+import androidx.navigation.compose.samples.Destination
+import androidx.navigation.compose.samples.ProfileWithArgs
+import androidx.navigation.toRoute
 
 @Composable
-@Suppress("DEPRECATION")
 fun NavWithArgsDemo() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Screen.Profile.route) {
-        composable(Screen.Profile.route) { ProfileWithArgs(navController) }
-        composable(Screen.Dashboard.route + "?userId={userId}") { backStackEntry ->
-            Dashboard(navController, backStackEntry.arguments?.get("userId") as? String)
-        }
-    }
-}
-
-@Composable
-fun ProfileWithArgs(navController: NavController) {
-    Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
-        Text(text = stringResource(Screen.Profile.resourceId))
-        Divider(color = Color.Black)
-        val state = rememberSaveable { mutableStateOf("") }
-        Box {
-            TextField(
-                value = state.value,
-                onValueChange = { state.value = it },
-                placeholder = { Text("Enter userId here") }
-            )
-        }
-        Divider(color = Color.Black)
-        NavigateButton("Dashboard with userId") {
-            navController.navigate(Screen.Dashboard.route + "?userId=" + state.value)
+    NavHost(navController, startDestination = Destination.Profile.route) {
+        composable<Destination.Profile> { ProfileWithArgs(navController) }
+        composable<Destination.Dashboard> { backStackEntry ->
+            val dashboard = backStackEntry.toRoute<Destination.Dashboard>()
+            Dashboard(navController, dashboard.userId)
         }
     }
 }
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsInNestedGraphDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsInNestedGraphDemo.kt
new file mode 100644
index 0000000..fe91ed6
--- /dev/null
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsInNestedGraphDemo.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.compose.demos
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.compose.samples.NavWithArgsInNestedGraph
+
+@Composable
+fun NavWithArgsInNestedGraphDemo() {
+    NavWithArgsInNestedGraph()
+}
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt
index 113d02d..6e11a87 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavigationDemos.kt
@@ -27,6 +27,7 @@
         ComposableDemo("Nested Nav In Graph Demo") { NestNavInGraphDemo() },
         ComposableDemo("Bottom Bar Nav Demo") { BottomBarNavDemo() },
         ComposableDemo("Navigation with Args") { NavWithArgsDemo() },
+        ComposableDemo("Navigation with Args in Nested Graph") { NavWithArgsInNestedGraphDemo() },
         ComposableDemo("Navigation by DeepLink") { NavByDeepLinkDemo() },
         ComposableDemo("Navigation PopUpTo") { NavPopUpToDemo() },
         ComposableDemo("Navigation SingleTop") { NavSingleTopDemo() },
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
index d2b50f3e..d2db6e0 100644
--- a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
@@ -19,7 +19,6 @@
 import android.os.Bundle
 import android.os.Parcelable
 import androidx.annotation.Sampled
-import androidx.annotation.StringRes
 import androidx.compose.animation.AnimatedContentTransitionScope
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
@@ -36,8 +35,11 @@
 import androidx.compose.material.Divider
 import androidx.compose.material.Scaffold
 import androidx.compose.material.Text
+import androidx.compose.material.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color.Companion.LightGray
@@ -49,6 +51,7 @@
 import androidx.compose.ui.unit.sp
 import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.navigation.NavController
+import androidx.navigation.NavDestination.Companion.hasRoute
 import androidx.navigation.NavHostController
 import androidx.navigation.NavType
 import androidx.navigation.compose.NavHost
@@ -56,28 +59,53 @@
 import androidx.navigation.compose.dialog
 import androidx.navigation.compose.navigation
 import androidx.navigation.compose.rememberNavController
-import androidx.navigation.navArgument
+import androidx.navigation.toRoute
+import kotlin.reflect.KClass
 import kotlinx.parcelize.Parcelize
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.decodeFromString
 import kotlinx.serialization.json.Json
 
-sealed class Screen(val route: String, @StringRes val resourceId: Int) {
-    object Profile : Screen("profile", R.string.profile)
-    object Dashboard : Screen("dashboard", R.string.dashboard)
-    object Scrollable : Screen("scrollable", R.string.scrollable)
-    object Dialog : Screen("dialog", R.string.dialog)
+interface Destination {
+    val route: KClass<out Destination>
+
+    @Serializable object Profile : Destination {
+        override val route = this::class
+        val resourceId: Int = R.string.profile
+    }
+    @Serializable object Scrollable : Destination {
+        override val route = this::class
+        val resourceId: Int = R.string.scrollable
+    }
+    @Serializable object Dialog : Destination {
+        override val route = this::class
+        val resourceId: Int = R.string.dialog
+    }
+    @Serializable data class Dashboard(val userId: String? = "no value given") : Destination {
+        override val route: KClass<out Destination>
+            get() = this::class
+        companion object {
+            val resourceId: Int = R.string.dashboard
+        }
+    }
+    @Serializable
+    object Nested : Destination { override val route = this::class }
+
+    @Serializable
+    data class NestedWithArg(val userId: String? = "default nested arg") : Destination {
+        override val route: KClass<out Destination>
+            get() = this::class
+    }
 }
 
 @Composable
 fun BasicNav() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Screen.Profile.route) {
-        composable(Screen.Profile.route) { Profile(navController) }
-        composable(
-            Screen.Dashboard.route,
+    NavHost(navController, startDestination = Destination.Profile.route) {
+        composable<Destination.Profile> { Profile(navController) }
+        composable<Destination.Dashboard>(
             enterTransition = {
-                if (initialState.destination.route == Screen.Scrollable.route) {
+                if (initialState.destination.hasRoute<Destination.Scrollable>()) {
                     // Slide in when entering from Scrollable
                     slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start)
                 } else {
@@ -85,7 +113,7 @@
                 }
             },
             popExitTransition = {
-                if (targetState.destination.route == Screen.Scrollable.route) {
+                if (targetState.destination.hasRoute<Destination.Scrollable>()) {
                     // Slide out when popping back to Scrollable
                     slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End)
                 } else {
@@ -95,10 +123,9 @@
         ) {
             Dashboard(navController)
         }
-        composable(
-            Screen.Scrollable.route,
+        composable<Destination.Scrollable>(
             exitTransition = {
-                if (targetState.destination.route == Screen.Dashboard.route) {
+                if (targetState.destination.hasRoute<Destination.Dashboard>()) {
                     // Slide out when navigating to Dashboard
                     slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start)
                 } else {
@@ -106,7 +133,7 @@
                 }
             },
             popEnterTransition = {
-                if (initialState.destination.route == Screen.Dashboard.route) {
+                if (initialState.destination.hasRoute<Destination.Dashboard>()) {
                     // Slide back in when returning from Dashboard
                     slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End)
                 } else {
@@ -116,33 +143,33 @@
         ) {
             Scrollable(navController)
         }
-        dialog(Screen.Dialog.route) { DialogContent(navController) }
+        dialog<Destination.Dialog> { DialogContent(navController) }
     }
 }
 
 @Composable
 fun NestedNavStartDestination() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = "nested") {
-        navigation(startDestination = Screen.Profile.route, route = "nested") {
-            composable(Screen.Profile.route) { Profile(navController) }
+    NavHost(navController, startDestination = Destination.Nested.route) {
+        navigation<Destination.Nested>(startDestination = Destination.Profile.route) {
+            composable<Destination.Profile> { Profile(navController) }
         }
-        composable(Screen.Dashboard.route) { Dashboard(navController) }
-        composable(Screen.Scrollable.route) { Scrollable(navController) }
-        dialog(Screen.Dialog.route) { DialogContent(navController) }
+        composable<Destination.Dashboard> { Dashboard(navController) }
+        composable<Destination.Scrollable> { Scrollable(navController) }
+        dialog<Destination.Dialog> { DialogContent(navController) }
     }
 }
 
 @Composable
 fun NestedNavInGraph() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Screen.Profile.route) {
-        composable(Screen.Profile.route) { Profile(navController) }
-        navigation(startDestination = "nested", route = Screen.Dashboard.route) {
-            composable("nested") { Dashboard(navController) }
+    NavHost(navController, startDestination = Destination.Profile.route) {
+        composable<Destination.Profile> { Profile(navController) }
+        navigation<Destination.Dashboard>(startDestination = Destination.Nested.route) {
+            composable<Destination.Nested> { Dashboard(navController) }
         }
-        composable(Screen.Scrollable.route) { Scrollable(navController) }
-        dialog(Screen.Dialog.route) { DialogContent(navController) }
+        composable<Destination.Scrollable> { Scrollable(navController) }
+        dialog<Destination.Dialog> { DialogContent(navController) }
     }
 }
 
@@ -151,49 +178,27 @@
 fun NavScaffold() {
     val navController = rememberNavController()
     Scaffold { innerPadding ->
-        NavHost(navController, Screen.Profile.route, Modifier.padding(innerPadding)) {
-            composable(Screen.Profile.route) { Profile(navController) }
-            composable(Screen.Dashboard.route) { Dashboard(navController) }
-            composable(Screen.Scrollable.route) { Scrollable(navController) }
-            dialog(Screen.Dialog.route) { DialogContent(navController) }
-        }
-    }
-}
-
-@Composable
-fun NavWithArgs() {
-    val navController = rememberNavController()
-    NavHost(navController, startDestination = Screen.Profile.route) {
-        composable(Screen.Profile.route) { Profile(navController) }
-        composable(
-            Screen.Dashboard.route,
-            arguments = listOf(navArgument("userId") { defaultValue = "no value given" })
-        ) { backStackEntry ->
-            Dashboard(navController, backStackEntry.arguments?.getString("userId"))
+        NavHost(navController, Destination.Profile.route, Modifier.padding(innerPadding)) {
+            composable<Destination.Profile> { Profile(navController) }
+            composable<Destination.Dashboard> { Dashboard(navController) }
+            composable<Destination.Scrollable> { Scrollable(navController) }
+            dialog<Destination.Dialog> { DialogContent(navController) }
         }
     }
 }
 
 @Sampled
 @Composable
-fun NestedNavInGraphWithArgs() {
+fun NavWithArgsInNestedGraph() {
     val navController = rememberNavController()
-    NavHost(navController, startDestination = Screen.Profile.route) {
-        composable(Screen.Profile.route) { Profile(navController) }
-        navigation(
-            startDestination = "nested",
-            route = Screen.Dashboard.route,
-            // This value will be sent to the start destination of the graph when you navigate to
-            // this graph
-            arguments = listOf(navArgument("userId") { defaultValue = "no value given" })
-        ) {
-            composable(
-                "nested",
-                // We don't need to set a default value here because the start destination will
-                // automatically receive the arguments of its parent graph
-                arguments = listOf(navArgument("userId") { })
-            ) {
-                Dashboard(navController)
+    NavHost(navController, startDestination = Destination.Profile.route) {
+        composable<Destination.Profile> { ProfileWithArgs(navController) }
+        navigation<Destination.Dashboard>(startDestination = Destination.NestedWithArg::class) {
+            composable<Destination.NestedWithArg> {
+                // argument from parent graph Destination.Dashboard will automatically be
+                // bundled into the start destination's arguments
+                val userId = it.toRoute<Destination.NestedWithArg>().userId
+                Dashboard(navController, userId)
             }
         }
     }
@@ -202,17 +207,17 @@
 @Composable
 fun Profile(navController: NavHostController) {
     Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
-        Text(text = stringResource(Screen.Profile.resourceId))
-        NavigateButton(stringResource(Screen.Dashboard.resourceId)) {
-            navController.navigate(Screen.Dashboard.route)
+        Text(text = stringResource(Destination.Profile.resourceId))
+        NavigateButton(stringResource(Destination.Dashboard.resourceId)) {
+            navController.navigate(Destination.Dashboard())
         }
         Divider(color = Color.Black)
-        NavigateButton(stringResource(Screen.Scrollable.resourceId)) {
-            navController.navigate(Screen.Scrollable.route)
+        NavigateButton(stringResource(Destination.Scrollable.resourceId)) {
+            navController.navigate(Destination.Scrollable)
         }
         Divider(color = Color.Black)
-        NavigateButton(stringResource(Screen.Dialog.resourceId)) {
-            navController.navigate(Screen.Dialog.route)
+        NavigateButton(stringResource(Destination.Dialog.resourceId)) {
+            navController.navigate(Destination.Dialog)
         }
         Spacer(Modifier.weight(1f))
         NavigateBackButton(navController)
@@ -220,9 +225,29 @@
 }
 
 @Composable
+fun ProfileWithArgs(navController: NavController) {
+    Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
+        Text(text = stringResource(Destination.Profile.resourceId))
+        Divider(color = Color.Black)
+        val state = rememberSaveable { mutableStateOf("") }
+        Box {
+            TextField(
+                value = state.value,
+                onValueChange = { state.value = it },
+                placeholder = { Text("Enter userId here") }
+            )
+        }
+        Divider(color = Color.Black)
+        NavigateButton("Dashboard with userId") {
+            navController.navigate(Destination.Dashboard(state.value))
+        }
+    }
+}
+
+@Composable
 fun Dashboard(navController: NavController, title: String? = null) {
     Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
-        Text(text = title ?: stringResource(Screen.Dashboard.resourceId))
+        Text(text = title ?: stringResource(Destination.Dashboard.resourceId))
         Spacer(Modifier.weight(1f))
         NavigateBackButton(navController)
     }
@@ -231,8 +256,8 @@
 @Composable
 fun Scrollable(navController: NavController) {
     Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
-        NavigateButton(stringResource(Screen.Dashboard.resourceId)) {
-            navController.navigate(Screen.Dashboard.route)
+        NavigateButton(stringResource(Destination.Dashboard.resourceId)) {
+            navController.navigate(Destination.Dashboard())
         }
         LazyColumn(modifier = Modifier.weight(1f)) {
             items(phrases) { phrase ->
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/SizeTransformSample.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/SizeTransformSample.kt
index ce55fdb..a8779ce 100644
--- a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/SizeTransformSample.kt
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/SizeTransformSample.kt
@@ -33,14 +33,15 @@
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
+import kotlinx.serialization.Serializable
 
 @Sampled
 @Composable
 fun SizeTransformNav() {
     val navController = rememberNavController()
     Box {
-        NavHost(navController, startDestination = "collapsed") {
-            composable("collapsed",
+        NavHost(navController, startDestination = Collapsed) {
+            composable<Collapsed>(
                 enterTransition = { EnterTransition.None },
                 exitTransition = { ExitTransition.None },
                 sizeTransform = {
@@ -52,9 +53,9 @@
                         }
                     }
                 }) {
-                CollapsedScreen { navController.navigate("expanded") }
+                CollapsedScreen { navController.navigate(Expanded) }
             }
-            composable("expanded",
+            composable<Expanded>(
                 enterTransition = { EnterTransition.None },
                 exitTransition = { ExitTransition.None },
                 sizeTransform = {
@@ -71,6 +72,12 @@
     }
 }
 
+@Serializable
+object Collapsed
+
+@Serializable
+object Expanded
+
 @Composable
 fun CollapsedScreen(onNavigate: () -> Unit) {
     Box(Modifier.clickable { onNavigate() }.size(40.dp).background(Green))
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
index 6c75048..fb4e5d5 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
@@ -223,7 +223,7 @@
 /**
  * Construct a nested [NavGraph]
  *
- * @sample androidx.navigation.compose.samples.NestedNavInGraphWithArgs
+ * @sample androidx.navigation.compose.samples.NavWithArgsInNestedGraph
  *
  * @param startDestination the starting destination's route for this NavGraph
  * @param route the destination's unique route
diff --git a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
index 2c07c85..11bf649 100644
--- a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
+++ b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
@@ -152,7 +152,7 @@
     userSwipeEnabled: Boolean = true,
     state: SwipeDismissableNavHostState = rememberSwipeDismissableNavHostState(),
 ) {
-    val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
+    val lifecycleOwner = LocalLifecycleOwner.current
     val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
         "SwipeDismissableNavHost requires a ViewModelStoreOwner to be provided " +
             "via LocalViewModelStoreOwner"
@@ -272,11 +272,11 @@
                 } else {
                     Modifier.graphicsLayer {
                         val scaleProgression = NAV_HOST_ENTER_TRANSITION_EASING_STANDARD
-                            .transform((animationProgress.value / 0.75f).coerceAtMost(1f))
+                            .transform((animationProgress.value / 0.75f))
                         val opacityProgression = NAV_HOST_ENTER_TRANSITION_EASING_STANDARD
-                            .transform((animationProgress.value / 0.25f).coerceAtMost(1f))
-                        val scale = lerp(0.75f, 1f, scaleProgression)
-                        val opacity = lerp(0.1f, 1f, opacityProgression)
+                            .transform((animationProgress.value / 0.25f))
+                        val scale = lerp(0.75f, 1f, scaleProgression).coerceAtMost(1f)
+                        val opacity = lerp(0.1f, 1f, opacityProgression).coerceIn(0f, 1f)
                         scaleX = scale
                         scaleY = scale
                         alpha = opacity
@@ -491,10 +491,10 @@
                 if (layerColor != Color.Unspecified) {
                     Canvas(Modifier.fillMaxSize()) {
                         val absoluteProgression =
-                            ((animatable.value - 0.25f).coerceAtLeast(0f) / 0.75f).coerceAtMost(1f)
+                            ((animatable.value - 0.25f) / 0.75f).coerceIn(0f, 1f)
                         val easedProgression = NAV_HOST_ENTER_TRANSITION_EASING_STANDARD
                             .transform(absoluteProgression)
-                        val alpha = lerp(0.07f, 0f, easedProgression)
+                        val alpha = lerp(0.07f, 0f, easedProgression).coerceIn(0f, 1f)
                         if (isRoundDevice) {
                             drawCircle(color = layerColor.copy(alpha))
                         } else {
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 9774ec0..28d0392 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,8 +25,8 @@
     defaultConfig {
         applicationId "androidx.wear.compose.integration.demos"
         minSdk 25
-        versionCode 22
-        versionName "1.22"
+        versionCode 23
+        versionName "1.23"
     }
 
     buildTypes {
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/ProtoLayoutExtensionViewProvider.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/ProtoLayoutExtensionViewProvider.java
index ca4f923..42df804 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/ProtoLayoutExtensionViewProvider.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/ProtoLayoutExtensionViewProvider.java
@@ -1,5 +1,3 @@
-package androidx.wear.protolayout.renderer;
-
 /*
  * Copyright 2023 The Android Open Source Project
  *
@@ -15,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package androidx.wear.protolayout.renderer;
 
 import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
 
 /**
  * View provider for a View ExtensionLayoutElement. This should check that the given renderer
@@ -28,7 +28,7 @@
  * payload. The returned View will be measured using the width/height from the {@link
  * androidx.wear.protolayout.LayoutElementBuilders.ExtensionLayoutElement} message.
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
+@RestrictTo(Scope.LIBRARY)
 public interface ProtoLayoutExtensionViewProvider {
     /**
      * Return an Android View from the given renderer extension. In case of an error, this method
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutThemeImpl.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutThemeImpl.java
index 819b270..b540781 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutThemeImpl.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutThemeImpl.java
@@ -106,15 +106,6 @@
         return new ProtoLayoutThemeImpl(context.getResources(), R.style.ProtoLayoutBaseTheme);
     }
 
-    /**
-     * Creates a ProtoLayoutTheme for the default theme, based on R.style.ProtoLayoutBaseTheme and
-     * R.attr.protoLayoutFallbackAppearance from the local package.
-     */
-    @NonNull
-    public static ProtoLayoutTheme defaultTheme(@NonNull Resources resources) {
-        return new ProtoLayoutThemeImpl(resources, R.style.ProtoLayoutBaseTheme);
-    }
-
     private final Map<Integer, FontSet> mVariantToFontSet = new ArrayMap<>();
     private final Theme mTheme;
     @AttrRes private final int mFallbackTextAppearanceAttrId;
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
index b5d91a6..e6bf97c 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
@@ -50,14 +50,13 @@
  */
 class TouchDelegateComposite extends TouchDelegate {
 
-    @NonNull
-    private final WeakHashMap<View, DelegateInfo> mDelegates = new WeakHashMap<>();
+    @NonNull private final WeakHashMap<View, DelegateInfo> mDelegates = new WeakHashMap<>();
 
     /**
      * Constructor
      *
-     * @param delegateView   The view that should receive motion events.
-     * @param actualBounds   The hit rect of the view.
+     * @param delegateView The view that should receive motion events.
+     * @param actualBounds The hit rect of the view.
      * @param extendedBounds The hit rect to be delegated.
      */
     TouchDelegateComposite(
@@ -110,13 +109,11 @@
             }
             return checkNotNull(mDelegates.get(view)).mTouchDelegate.onTouchEvent(event);
         } else {
-            // For other motion event, forward to ALL the delegate view whose extended bounds
-          // with touch
-            // slop contains the touch point.
+            // For other motion event, forward to ALL the delegate view whose extended bounds with
+            // touch slop contains the touch point.
             for (DelegateInfo delegateInfo : mDelegates.values()) {
-                // set the event location back to the original coordinates, which might get
-              // offset by the
-                // previous TouchDelegate#onTouchEvent call
+                // set the event location back to the original coordinates, which might get offset
+                // by the previous TouchDelegate#onTouchEvent call.
                 event.setLocation(x, y);
                 eventForwarded |= delegateInfo.mTouchDelegate.onTouchEvent(event);
             }
@@ -151,15 +148,13 @@
     }
 
     private static final class DelegateInfo {
-        @NonNull
-        final Rect mActualBounds;
-        @NonNull
-        final Rect mExtendedBounds;
-        @NonNull
-        final TouchDelegate mTouchDelegate;
+        @NonNull final Rect mActualBounds;
+        @NonNull final Rect mExtendedBounds;
+        @NonNull final TouchDelegate mTouchDelegate;
 
         DelegateInfo(
-                @NonNull View delegateView, @NonNull Rect actualBounds,
+                @NonNull View delegateView,
+                @NonNull Rect actualBounds,
                 @NonNull Rect extendedBounds) {
             mActualBounds = actualBounds;
             mExtendedBounds = extendedBounds;
diff --git a/wear/tiles/tiles/api/current.txt b/wear/tiles/tiles/api/current.txt
index cbec097..7cd96a0 100644
--- a/wear/tiles/tiles/api/current.txt
+++ b/wear/tiles/tiles/api/current.txt
@@ -255,22 +255,22 @@
   public final class EventBuilders {
   }
 
-  public static final class EventBuilders.TileAddEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileAddEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileAddEvent.Builder {
-    ctor public EventBuilders.TileAddEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileAddEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileAddEvent build();
     method public androidx.wear.tiles.EventBuilders.TileAddEvent.Builder setTileId(int);
   }
 
-  public static final class EventBuilders.TileEnterEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileEnterEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileEnterEvent.Builder {
-    ctor public EventBuilders.TileEnterEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileEnterEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileEnterEvent build();
     method public androidx.wear.tiles.EventBuilders.TileEnterEvent.Builder setTileId(int);
   }
@@ -290,22 +290,22 @@
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.tiles.EventBuilders.TileInteractionEvent.Builder setTimestamp(java.time.Instant);
   }
 
-  public static final class EventBuilders.TileLeaveEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileLeaveEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileLeaveEvent.Builder {
-    ctor public EventBuilders.TileLeaveEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileLeaveEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileLeaveEvent build();
     method public androidx.wear.tiles.EventBuilders.TileLeaveEvent.Builder setTileId(int);
   }
 
-  public static final class EventBuilders.TileRemoveEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileRemoveEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileRemoveEvent.Builder {
-    ctor public EventBuilders.TileRemoveEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileRemoveEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileRemoveEvent build();
     method public androidx.wear.tiles.EventBuilders.TileRemoveEvent.Builder setTileId(int);
   }
@@ -914,7 +914,7 @@
   public final class RequestBuilders {
   }
 
-  public static final class RequestBuilders.ResourcesRequest {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class RequestBuilders.ResourcesRequest {
     method public androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters getDeviceConfiguration();
     method @Deprecated public androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters? getDeviceParameters();
     method public java.util.List<java.lang.String!> getResourceIds();
@@ -923,16 +923,16 @@
   }
 
   public static final class RequestBuilders.ResourcesRequest.Builder {
-    ctor public RequestBuilders.ResourcesRequest.Builder();
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder addResourceId(String);
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public RequestBuilders.ResourcesRequest.Builder();
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder addResourceId(String);
     method public androidx.wear.tiles.RequestBuilders.ResourcesRequest build();
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method @Deprecated public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setDeviceParameters(androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters);
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setTileId(int);
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setVersion(String);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setTileId(int);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setVersion(String);
   }
 
-  public static final class RequestBuilders.TileRequest {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class RequestBuilders.TileRequest {
     method public androidx.wear.protolayout.StateBuilders.State getCurrentState();
     method public androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters getDeviceConfiguration();
     method @Deprecated public androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters? getDeviceParameters();
@@ -941,13 +941,13 @@
   }
 
   public static final class RequestBuilders.TileRequest.Builder {
-    ctor public RequestBuilders.TileRequest.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public RequestBuilders.TileRequest.Builder();
     method public androidx.wear.tiles.RequestBuilders.TileRequest build();
-    method public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setCurrentState(androidx.wear.protolayout.StateBuilders.State);
-    method public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setCurrentState(androidx.wear.protolayout.StateBuilders.State);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method @Deprecated public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setDeviceParameters(androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters);
     method @Deprecated public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setState(androidx.wear.tiles.StateBuilders.State);
-    method public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setTileId(int);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setTileId(int);
   }
 
   @Deprecated public final class ResourceBuilders {
@@ -1022,7 +1022,7 @@
   public final class TileBuilders {
   }
 
-  public static final class TileBuilders.Tile {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class TileBuilders.Tile {
     method public long getFreshnessIntervalMillis();
     method public String getResourcesVersion();
     method public androidx.wear.protolayout.StateBuilders.State? getState();
@@ -1031,12 +1031,12 @@
   }
 
   public static final class TileBuilders.Tile.Builder {
-    ctor public TileBuilders.Tile.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public TileBuilders.Tile.Builder();
     method public androidx.wear.tiles.TileBuilders.Tile build();
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setFreshnessIntervalMillis(long);
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setResourcesVersion(String);
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setState(androidx.wear.protolayout.StateBuilders.State);
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setTileTimeline(androidx.wear.protolayout.TimelineBuilders.Timeline);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.TileBuilders.Tile.Builder setFreshnessIntervalMillis(long);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.TileBuilders.Tile.Builder setResourcesVersion(String);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.tiles.TileBuilders.Tile.Builder setState(androidx.wear.protolayout.StateBuilders.State);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.TileBuilders.Tile.Builder setTileTimeline(androidx.wear.protolayout.TimelineBuilders.Timeline);
     method @Deprecated public androidx.wear.tiles.TileBuilders.Tile.Builder setTimeline(androidx.wear.tiles.TimelineBuilders.Timeline);
   }
 
diff --git a/wear/tiles/tiles/api/restricted_current.txt b/wear/tiles/tiles/api/restricted_current.txt
index cbec097..7cd96a0 100644
--- a/wear/tiles/tiles/api/restricted_current.txt
+++ b/wear/tiles/tiles/api/restricted_current.txt
@@ -255,22 +255,22 @@
   public final class EventBuilders {
   }
 
-  public static final class EventBuilders.TileAddEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileAddEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileAddEvent.Builder {
-    ctor public EventBuilders.TileAddEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileAddEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileAddEvent build();
     method public androidx.wear.tiles.EventBuilders.TileAddEvent.Builder setTileId(int);
   }
 
-  public static final class EventBuilders.TileEnterEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileEnterEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileEnterEvent.Builder {
-    ctor public EventBuilders.TileEnterEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileEnterEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileEnterEvent build();
     method public androidx.wear.tiles.EventBuilders.TileEnterEvent.Builder setTileId(int);
   }
@@ -290,22 +290,22 @@
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public androidx.wear.tiles.EventBuilders.TileInteractionEvent.Builder setTimestamp(java.time.Instant);
   }
 
-  public static final class EventBuilders.TileLeaveEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileLeaveEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileLeaveEvent.Builder {
-    ctor public EventBuilders.TileLeaveEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileLeaveEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileLeaveEvent build();
     method public androidx.wear.tiles.EventBuilders.TileLeaveEvent.Builder setTileId(int);
   }
 
-  public static final class EventBuilders.TileRemoveEvent {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class EventBuilders.TileRemoveEvent {
     method public int getTileId();
   }
 
   public static final class EventBuilders.TileRemoveEvent.Builder {
-    ctor public EventBuilders.TileRemoveEvent.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public EventBuilders.TileRemoveEvent.Builder();
     method public androidx.wear.tiles.EventBuilders.TileRemoveEvent build();
     method public androidx.wear.tiles.EventBuilders.TileRemoveEvent.Builder setTileId(int);
   }
@@ -914,7 +914,7 @@
   public final class RequestBuilders {
   }
 
-  public static final class RequestBuilders.ResourcesRequest {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class RequestBuilders.ResourcesRequest {
     method public androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters getDeviceConfiguration();
     method @Deprecated public androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters? getDeviceParameters();
     method public java.util.List<java.lang.String!> getResourceIds();
@@ -923,16 +923,16 @@
   }
 
   public static final class RequestBuilders.ResourcesRequest.Builder {
-    ctor public RequestBuilders.ResourcesRequest.Builder();
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder addResourceId(String);
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public RequestBuilders.ResourcesRequest.Builder();
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder addResourceId(String);
     method public androidx.wear.tiles.RequestBuilders.ResourcesRequest build();
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method @Deprecated public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setDeviceParameters(androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters);
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setTileId(int);
-    method public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setVersion(String);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setTileId(int);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.ResourcesRequest.Builder setVersion(String);
   }
 
-  public static final class RequestBuilders.TileRequest {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class RequestBuilders.TileRequest {
     method public androidx.wear.protolayout.StateBuilders.State getCurrentState();
     method public androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters getDeviceConfiguration();
     method @Deprecated public androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters? getDeviceParameters();
@@ -941,13 +941,13 @@
   }
 
   public static final class RequestBuilders.TileRequest.Builder {
-    ctor public RequestBuilders.TileRequest.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public RequestBuilders.TileRequest.Builder();
     method public androidx.wear.tiles.RequestBuilders.TileRequest build();
-    method public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setCurrentState(androidx.wear.protolayout.StateBuilders.State);
-    method public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setCurrentState(androidx.wear.protolayout.StateBuilders.State);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setDeviceConfiguration(androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method @Deprecated public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setDeviceParameters(androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters);
     method @Deprecated public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setState(androidx.wear.tiles.StateBuilders.State);
-    method public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setTileId(int);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.RequestBuilders.TileRequest.Builder setTileId(int);
   }
 
   @Deprecated public final class ResourceBuilders {
@@ -1022,7 +1022,7 @@
   public final class TileBuilders {
   }
 
-  public static final class TileBuilders.Tile {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class TileBuilders.Tile {
     method public long getFreshnessIntervalMillis();
     method public String getResourcesVersion();
     method public androidx.wear.protolayout.StateBuilders.State? getState();
@@ -1031,12 +1031,12 @@
   }
 
   public static final class TileBuilders.Tile.Builder {
-    ctor public TileBuilders.Tile.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public TileBuilders.Tile.Builder();
     method public androidx.wear.tiles.TileBuilders.Tile build();
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setFreshnessIntervalMillis(long);
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setResourcesVersion(String);
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setState(androidx.wear.protolayout.StateBuilders.State);
-    method public androidx.wear.tiles.TileBuilders.Tile.Builder setTileTimeline(androidx.wear.protolayout.TimelineBuilders.Timeline);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.TileBuilders.Tile.Builder setFreshnessIntervalMillis(long);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.TileBuilders.Tile.Builder setResourcesVersion(String);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.tiles.TileBuilders.Tile.Builder setState(androidx.wear.protolayout.StateBuilders.State);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.tiles.TileBuilders.Tile.Builder setTileTimeline(androidx.wear.protolayout.TimelineBuilders.Timeline);
     method @Deprecated public androidx.wear.tiles.TileBuilders.Tile.Builder setTimeline(androidx.wear.tiles.TimelineBuilders.Timeline);
   }
 
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ActionBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ActionBuilders.java
index 0dd9ce1..37ad14f 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ActionBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ActionBuilders.java
@@ -1,19 +1,18 @@
 /*
-* 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.
-*/
+ * 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.wear.tiles;
 
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java
index b870020..7e72ffb 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java
@@ -33,6 +33,7 @@
     private EventBuilders() {}
 
     /** Event fired when a tile has been added to the carousel. */
+    @RequiresSchemaVersion(major = 1, minor = 0)
     public static final class TileAddEvent {
         private final EventProto.TileAddEvent mImpl;
 
@@ -74,6 +75,8 @@
             private final EventProto.TileAddEvent.Builder mImpl =
                     EventProto.TileAddEvent.newBuilder();
 
+            /** Creates an instance of {@link Builder}. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             public Builder() {}
 
             /**
@@ -96,6 +99,7 @@
     }
 
     /** Event fired when a tile has been removed from the carousel. */
+    @RequiresSchemaVersion(major = 1, minor = 0)
     public static final class TileRemoveEvent {
         private final EventProto.TileRemoveEvent mImpl;
 
@@ -137,6 +141,8 @@
             private final EventProto.TileRemoveEvent.Builder mImpl =
                     EventProto.TileRemoveEvent.newBuilder();
 
+            /** Creates an instance of {@link Builder}. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             public Builder() {}
 
             /**
@@ -158,6 +164,7 @@
     }
 
     /** Event fired when a tile is swiped to by the user (i.e. it's visible on screen). */
+    @RequiresSchemaVersion(major = 1, minor = 0)
     public static final class TileEnterEvent {
         private final EventProto.TileEnterEvent mImpl;
 
@@ -199,6 +206,8 @@
             private final EventProto.TileEnterEvent.Builder mImpl =
                     EventProto.TileEnterEvent.newBuilder();
 
+            /** Creates an instance of {@link Builder}. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             public Builder() {}
 
             /**
@@ -224,6 +233,7 @@
      * Event fired when a tile is swiped away from by the user (i.e. it's no longer visible on
      * screen).
      */
+    @RequiresSchemaVersion(major = 1, minor = 0)
     public static final class TileLeaveEvent {
         private final EventProto.TileLeaveEvent mImpl;
 
@@ -265,6 +275,8 @@
             private final EventProto.TileLeaveEvent.Builder mImpl =
                     EventProto.TileLeaveEvent.newBuilder();
 
+            /** Creates an instance of {@link Builder}. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             public Builder() {}
 
             /**
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java
index a0b6980..b12b8ff2 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java
@@ -22,6 +22,7 @@
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
 import androidx.wear.protolayout.StateBuilders.State;
+import androidx.wear.protolayout.expression.RequiresSchemaVersion;
 import androidx.wear.protolayout.proto.DeviceParametersProto;
 import androidx.wear.protolayout.proto.StateProto;
 import androidx.wear.tiles.proto.RequestProto;
@@ -36,6 +37,7 @@
      * Parameters passed to a {@link androidx.wear.tiles.TileBuilders.Tile} Service when the
      * renderer is requesting a new version of the tile.
      */
+    @RequiresSchemaVersion(major = 1, minor = 0)
     public static final class TileRequest {
         private final RequestProto.TileRequest mImpl;
 
@@ -147,6 +149,8 @@
             private final RequestProto.TileRequest.Builder mImpl =
                     RequestProto.TileRequest.newBuilder();
 
+            /** Creates an instance of {@link Builder}. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             public Builder() {}
 
             /**
@@ -154,6 +158,7 @@
              * object describing the device requesting the tile update. If not set, a default empty
              * instance is used.
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setDeviceConfiguration(@NonNull DeviceParameters deviceConfiguration) {
                 mImpl.setDeviceConfiguration(deviceConfiguration.toProto());
@@ -164,15 +169,15 @@
              * Sets the {@link androidx.wear.protolayout.StateBuilders.State} that should be used
              * when building the tile.
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setCurrentState(@NonNull State currentState) {
                 mImpl.setCurrentState(currentState.toProto());
                 return this;
             }
 
-            /**
-             * Sets the ID of the tile being requested.
-             */
+            /** Sets the ID of the tile being requested. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setTileId(int tileId) {
                 mImpl.setTileId(tileId);
@@ -220,6 +225,7 @@
      * Parameters passed to a {@link androidx.wear.tiles.TileBuilders.Tile} Service when the
      * renderer is requesting a specific resource version.
      */
+    @RequiresSchemaVersion(major = 1, minor = 0)
     public static final class ResourcesRequest {
         private final RequestProto.ResourcesRequest mImpl;
 
@@ -325,13 +331,15 @@
             private final RequestProto.ResourcesRequest.Builder mImpl =
                     RequestProto.ResourcesRequest.newBuilder();
 
+            /** Creates an instance of {@link Builder}. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             public Builder() {}
 
             /**
              * Sets the version of the resources being fetched. This is the same as the requested
              * resource version, passed in {@link androidx.wear.tiles.TileBuilders.Tile}.
-             *
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setVersion(@NonNull String version) {
                 mImpl.setVersion(version);
@@ -348,6 +356,7 @@
              * used in {@link androidx.wear.protolayout.ResourceBuilders.Resources}.idToImage), not
              * Android resource names or similar.
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder addResourceId(@NonNull String resourceId) {
                 mImpl.addResourceIds(resourceId);
@@ -358,15 +367,15 @@
              * Sets the {@link androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters}
              * object describing the device requesting the resources.
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setDeviceConfiguration(@NonNull DeviceParameters deviceConfiguration) {
                 mImpl.setDeviceConfiguration(deviceConfiguration.toProto());
                 return this;
             }
 
-            /**
-             * Sets the ID of the tile for which resources are being requested.
-             */
+            /** Sets the ID of the tile for which resources are being requested. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setTileId(int tileId) {
                 mImpl.setTileId(tileId);
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java
index f81f833..7d9a635 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java
@@ -22,6 +22,7 @@
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.StateBuilders.State;
 import androidx.wear.protolayout.TimelineBuilders.Timeline;
+import androidx.wear.protolayout.expression.RequiresSchemaVersion;
 import androidx.wear.protolayout.expression.proto.VersionProto.VersionInfo;
 import androidx.wear.tiles.proto.TileProto;
 
@@ -33,6 +34,7 @@
      * A holder for a tile. This specifies the resources to use for this delivery of the tile, and
      * the timeline for the tile.
      */
+    @RequiresSchemaVersion(major = 1, minor = 0)
     public static final class Tile {
         private final TileProto.Tile mImpl;
 
@@ -81,9 +83,7 @@
             return mImpl.getFreshnessIntervalMillis();
         }
 
-        /**
-         * Gets {@link androidx.wear.protolayout.StateBuilders.State} for this tile.
-         */
+        /** Gets {@link androidx.wear.protolayout.StateBuilders.State} for this tile. */
         @Nullable
         public State getState() {
             if (mImpl.hasState()) {
@@ -144,6 +144,8 @@
         public static final class Builder {
             private final TileProto.Tile.Builder mImpl = TileProto.Tile.newBuilder();
 
+            /** Creates an instance of {@link Builder}. */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             public Builder() {}
 
             /**
@@ -152,6 +154,7 @@
              * androidx.wear.tiles.RequestBuilders.ResourcesRequest} if the system does not have a
              * copy of the specified resource version.
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setResourcesVersion(@NonNull String resourcesVersion) {
                 mImpl.setResourcesVersion(resourcesVersion);
@@ -162,6 +165,7 @@
              * Sets the {@link androidx.wear.protolayout.TimelineBuilders.Timeline} containing the
              * layouts for the tiles to show in the carousel, along with their validity periods.
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setTileTimeline(@NonNull Timeline tileTimeline) {
                 mImpl.setTileTimeline(tileTimeline.toProto());
@@ -181,15 +185,15 @@
              * tile if it is on-screen, or about to be on-screen, although this is not guaranteed
              * due to system-level optimizations.
              */
+            @RequiresSchemaVersion(major = 1, minor = 0)
             @NonNull
             public Builder setFreshnessIntervalMillis(long freshnessIntervalMillis) {
                 mImpl.setFreshnessIntervalMillis(freshnessIntervalMillis);
                 return this;
             }
 
-            /**
-             * Sets {@link androidx.wear.protolayout.StateBuilders.State} for this tile.
-             */
+            /** Sets {@link androidx.wear.protolayout.StateBuilders.State} for this tile. */
+            @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
             public Builder setState(@NonNull State state) {
                 mImpl.setState(state.toProto());
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileLeaveEventData.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileLeaveEventData.java
index 97005c8..476cd9a 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileLeaveEventData.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileLeaveEventData.java
@@ -20,7 +20,7 @@
 import androidx.annotation.RestrictTo;
 
 /**
- * Holder for Tiles' TileLeaveEvent class, to be parceled and transferred to a tile service.
+ * Holder for Tiles' TileLeaveEvent class, to be parceled and transferred to a Tile Service.
  *
  * <p>All this does is to serialize TileLeaveEvent as a protobuf and transmit it.
  */
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileUpdateRequestData.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileUpdateRequestData.java
index 3198815..1907b70 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileUpdateRequestData.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileUpdateRequestData.java
@@ -26,6 +26,7 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public final class TileUpdateRequestData extends ProtoParcelable {
     public static final int VERSION_PLACEHOLDER = 1;
+
     public static final Creator<TileUpdateRequestData> CREATOR =
             newCreator(TileUpdateRequestData.class, TileUpdateRequestData::new);
 
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/CompositeTileUpdateRequesterTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/CompositeTileUpdateRequesterTest.java
index 500a3ed..5c0133e 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/CompositeTileUpdateRequesterTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/CompositeTileUpdateRequesterTest.java
@@ -24,19 +24,18 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.concurrent.futures.ResolvableFuture;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.protolayout.ResourceBuilders;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 @DoNotInstrument
 public class CompositeTileUpdateRequesterTest {
     private FakeUpdateRequester mFakeUpdateRequester1;
@@ -51,7 +50,7 @@
 
         mCompositeTileUpdateRequesterUnderTest =
                 new CompositeTileUpdateRequester(
-                        List.of(mFakeUpdateRequester1, mFakeUpdateRequester2));
+                    ImmutableList.of(mFakeUpdateRequester1, mFakeUpdateRequester2));
     }
 
     @Test
@@ -62,7 +61,7 @@
         assertThat(mFakeUpdateRequester2.mCalledService).isEqualTo(FakeService.class);
     }
 
-    private class FakeUpdateRequester implements TileUpdateRequester {
+    private static class FakeUpdateRequester implements TileUpdateRequester {
         @Nullable Class<? extends TileService> mCalledService = null;
 
         @Override
@@ -71,7 +70,7 @@
         }
     }
 
-    private class FakeService extends TileService {
+    private static class FakeService extends TileService {
         @NonNull
         @Override
         protected ListenableFuture<TileBuilders.Tile> onTileRequest(
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ProtoParcelableTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ProtoParcelableTest.java
index 17a2330..d0032c7 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ProtoParcelableTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ProtoParcelableTest.java
@@ -20,15 +20,16 @@
 
 import android.os.Parcel;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.tiles.proto.RequestProto;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
+
+@RunWith(AndroidJUnit4.class)
+@DoNotInstrument // See http://g/robolectric-users/fTi2FRXgyGA/m/PkB0wYuwBgAJ
 public final class ProtoParcelableTest {
     public static class Wrapper extends ProtoParcelable {
         public static final int VERSION = 1;
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesDataTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesDataTest.java
index cdbb5a9..7dfa440 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesDataTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesDataTest.java
@@ -20,14 +20,14 @@
 
 import android.os.Parcel;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.protolayout.proto.ResourceProto.Resources;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 @DoNotInstrument
 public final class ResourcesDataTest {
     @Test
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesRequestDataTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesRequestDataTest.java
index 61dd9f1..3837ce0 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesRequestDataTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/ResourcesRequestDataTest.java
@@ -20,14 +20,14 @@
 
 import android.os.Parcel;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.tiles.proto.RequestProto;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 @DoNotInstrument
 public final class ResourcesRequestDataTest {
     @Test
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileDataTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileDataTest.java
index f1938b6..100c0d3 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileDataTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileDataTest.java
@@ -20,14 +20,14 @@
 
 import android.os.Parcel;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.tiles.proto.TileProto.Tile;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 @DoNotInstrument
 public final class TileDataTest {
     @Test
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileRequestDataTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileRequestDataTest.java
index bf8ad22..6128a33 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileRequestDataTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileRequestDataTest.java
@@ -20,14 +20,14 @@
 
 import android.os.Parcel;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.tiles.proto.RequestProto;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 @DoNotInstrument
 public final class TileRequestDataTest {
     @Test