Merge "Put back default value to `ellipsis: Boolean` overload of (Multi)Paragraph ctor" into androidx-main
diff --git a/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/ActivityComposeIssueRegistry.kt b/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/ActivityComposeIssueRegistry.kt
index 5eb0a32..586ba0c 100644
--- a/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/ActivityComposeIssueRegistry.kt
+++ b/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/ActivityComposeIssueRegistry.kt
@@ -31,7 +31,8 @@
         get() =
             listOf(
                 ActivityResultLaunchDetector.LaunchDuringComposition,
-                CollectProgressDetector.NoCollectCallFound
+                CollectProgressDetector.NoCollectCallFound,
+                LocalContextCastIssueDetector.ContextCastToActivity
             )
 
     override val vendor =
diff --git a/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/LocalContextCastIssueDetector.kt b/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/LocalContextCastIssueDetector.kt
new file mode 100644
index 0000000..fa22ec7
--- /dev/null
+++ b/activity/activity-compose-lint/src/main/java/androidx/activity/compose/lint/LocalContextCastIssueDetector.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.activity.compose.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.Package
+import androidx.compose.lint.inheritsFrom
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiClassOwner
+import java.util.EnumSet
+import org.jetbrains.uast.UBinaryExpressionWithType
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.tryResolveNamed
+import org.jetbrains.uast.util.isTypeCast
+
+class LocalContextCastIssueDetector : Detector(), SourceCodeScanner {
+    private val activityType = Name(Package("android.app"), "Activity")
+    private val contextType = Name(Package("android.content"), "Context")
+
+    override fun getApplicableUastTypes() = listOf(UBinaryExpressionWithType::class.java)
+
+    override fun createUastHandler(context: JavaContext) =
+        object : UElementHandler() {
+            override fun visitBinaryExpressionWithType(node: UBinaryExpressionWithType) {
+                // Cast expression
+                if (node.isTypeCast()) {
+                    // RHS is Activity
+                    if (node.type.inheritsFrom(activityType)) {
+                        // LHS is Context
+                        if (node.operand.getExpressionType()?.inheritsFrom(contextType) == true) {
+                            // Check to see if LHS is a call to LocalContext.current - the receiver
+                            // will be LocalContext
+                            val resolvedReceiver =
+                                (node.operand as? UQualifiedReferenceExpression)
+                                    ?.receiver
+                                    ?.tryResolveNamed() ?: return
+                            if (
+                                resolvedReceiver.name == "LocalContext" &&
+                                    (resolvedReceiver.containingFile as? PsiClassOwner)
+                                        ?.packageName == "androidx.compose.ui.platform"
+                            ) {
+                                context.report(
+                                    ContextCastToActivity,
+                                    node,
+                                    context.getNameLocation(node),
+                                    DESCRIPTION
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+    companion object {
+        private const val DESCRIPTION =
+            "LocalContext should not be cast to Activity, use LocalActivity instead"
+        val ContextCastToActivity =
+            Issue.create(
+                "ContextCastToActivity",
+                DESCRIPTION,
+                "Casting Context to Activity is an error as Contexts are not always Activities. Use LocalActivity instead",
+                Category.CORRECTNESS,
+                3,
+                Severity.ERROR,
+                Implementation(
+                    LocalContextCastIssueDetector::class.java,
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+                )
+            )
+    }
+}
diff --git a/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/LocalContextCastIssueDetectorTest.kt b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/LocalContextCastIssueDetectorTest.kt
new file mode 100644
index 0000000..fe8da96
--- /dev/null
+++ b/activity/activity-compose-lint/src/test/java/androidx/activity/compose/lint/LocalContextCastIssueDetectorTest.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.activity.compose.lint
+
+import androidx.compose.lint.test.Stubs
+import androidx.compose.lint.test.bytecodeStub
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class LocalContextCastIssueDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = LocalContextCastIssueDetector()
+
+    override fun getIssues(): MutableList =
+        mutableListOf(LocalContextCastIssueDetector.ContextCastToActivity)
+
+    val LocalActivityStub =
+        bytecodeStub(
+            "LocalActivity.kt",
+            "androidx/activity/compose",
+            0xfc1de7e2,
+            """
+            package androidx.activity.compose
+
+            import android.app.Activity
+            import androidx.compose.runtime.compositionLocalOf
+
+            val LocalActivity = compositionLocalOf()
+        """
+                .trimIndent(),
+            """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2XLTQrCMBAF4BFBcFwUAoKCK5dS4hmKy7r0AiGNMJA/kqnU
+                2xticOPAbN77HgCsAWBVfgvtsMej8lMKNC1SaaYX8Vvq4GLIRnT3oJUdWjwy
+                Sjz8dEMyzZ7JGSFuNSCm4Ouu+Cue/vxMMlrFz5Cc6IZvW30ugz3uipNmUS5a
+                IzYPk3nkM1zgA8GrRwe+AAAA
+                """,
+            """
+                androidx/activity/compose/LocalActivityKt.class:
+                H4sIAAAAAAAA/61STW/TQBB967itY9I2LdCPlM+0QAJSN0UgJFJVqipVskgT
+                RFEvPW1sN9o0sSt7HZVbfgu/gI8L4oAijvwoxKyTgJrCpcKSd2fezns7Mzs/
+                fn79BuAZHjOUReBFofTOuXCV7En1jrth9yyMfV4LXdHZHaGv1AwYQ74teoJ3
+                RNDijWbbdwnNENry1YVohhelcu239FgxSgIluz5/HYU96Ylmx99LT6SSYZAK
+                VBkaV2Nuj0lcnJ3xcSLVHVJcr4VRi7d91YyEDGIugiBUQjNjXg9VPenoe2cn
+                Knh+pfxzyMLOwsA1Bmvb7chAqh2GTKl8xPDkn4qTOrrd8wwFdwJvnGx4/olI
+                OoqhXaqdhopu4O1el58kgTssaX9kVapObfK5qld7lRwWsGgjj+sMB//5dRbG
+                VRz4SnhCCcKMbi9DE8r0ktULGNipNgw6PJfaqpDlbTFsDvpz9qBvGyvG8LeM
+                4nJ+0C9Yi+aiUTEq7Pv7aYsiCqaVyZua9ZRh6e8J0SxfGIPNU2p09lC2AqGS
+                yGdYezOs1Ql6MpZU6e6fYWIw90KPguZrMvDrSbfpR291NxjswzCJXH9famd1
+                pHF0SQFbNDlmWm5BDxJ5G7po3MQD2qcJt1J/FVPkZfCQvFuE6s/8hNyHlPto
+                FKvXIX/mAt/CLObI1uwi0v7CZib7ghufkft4ScNAKVVZR5n2l4Qu0f3Lx8g4
+                WHGw6lC2aw4lctvBHdw9BotxD/ePMRUjG8OOUYy1Pf0LK8v1wXoEAAA=
+                """
+        )
+
+    val LocalContextStub =
+        bytecodeStub(
+            "AndroidLocals.kt",
+            "androidx/compose/ui/platform",
+            0xac8d2426,
+            """
+            package androidx.compose.ui.platform
+
+            import android.content.Context
+            import androidx.compose.runtime.staticCompositionLocalOf
+
+            val LocalContext = staticCompositionLocalOf()
+           """,
+            """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijg0uOSSMxLKcrPTKnQS87PLcgvTtUr
+                Ks0rycxNFRJyBgtklmTm5/nkJyfmeJdw6XPJYKgvzdQryEksScsvyhXid4TI
+                gtUXAzWIcnED1emlViTmFuSkCrGFpBaXeJcoMWgxAACzjsPdkAAAAA==
+                """,
+            """
+                androidx/compose/ui/platform/AndroidLocalsKt.class:
+                H4sIAAAAAAAA/61Ty07bQBQ94xhw3JQE+oBAHxRoG9oKh6pVpQYhISQkq+Eh
+                qNiwmthONMGZQfY4Ysm39Av62FRdVKjLflTVO05QBYgNqiV77uucuWfm+vef
+                Hz8BvMELhldchokS4YkXqN6xSiMvE95xzHVbJT1vfZBsqoDH6Qc9BsZQ6fI+
+                92IuO95OqxsFFC0wlDuRzss2lNTRiWZ4V1tqXiFPMqlFL/J2E9UXIW/F0Uae
+                EVoomeMbDHs3Q66egwhDPUjtDXtprBHpQlMlHa8b6VbChUw9LqXS3IBTb1vp
+                7Sw2W5cuanh7IwUlFOEWYeEWg7MaxEIKvcZQqC0dMLy8lvEyjznvMsNcatoM
+                Lmd32oth1OZZTG12a80jpWkfr9vvee1MBgNdm0Or3vCbl2+tcbPbKWECky4q
+                uMOw+/9vaeJcyFakecg1p5jV6xdoXJn5FM0HDOzIGBYlT4Sx6mSFKwzLZ6fj
+                7tmpa01bg9ex5qcqZ6czzqQ9adWtOvv1adShihnbKVRsg3rNUL22Jxr3C//A
+                8hEdd3FfdCTXWRIxzO4NFPuyL1JBetf/zRWDvaFCKio3hYy2s14rSj6aM2Fw
+                91WWBNGmME51yHFwhQErNEV2rnjGDBV5i0Y37uEpraMUd3K/ihHyCnhG3gOK
+                msf+itLnHPt8WAuMDfFjF/AObmOcbIOeR37EcJnNvuPuN5S+XOGwUMtZFrBE
+                63uK3qf9pw5R8DHto+pTt7M+NfLQxyM8PgRLMYcnhxhJUUzhpphPjT36F/cv
+                7rmKBAAA
+                """
+        )
+
+    val ContextStub =
+        bytecodeStub(
+            "Context.kt",
+            "android/content",
+            0x7e746f3f,
+            """
+            package android.content
+
+            class Context()
+        """
+                .trimIndent(),
+            """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijg0uOSSMxLKcrPTKnQS87PLcgvTtUr
+                Ks0rycxNFRJyBgtklmTm5/nkJyfmeJdw6XPJYKgvzdQryEksScsvyhXid4TI
+                gtUXAzWIcnED1emlViTmFuSkCrGFpBaXeJcoMWgxAACzjsPdkAAAAA==
+                """,
+            """
+                android/content/Context.class:
+                H4sIAAAAAAAA/3VRy04CMRQ9twODjigPH4CvtbpwgLjTmCiJCQlqooYNq8JM
+                tDw6CVMIS77FP3Bl4sIQl36U8XZk6+b0PO5tb9vvn49PAGc4IJSkDsaRCvxe
+                pE2ojd+w68xkQIR8X06lP5T62b/v9sMeuw7BvVBamUuCc3TcziIN10MKGULK
+                vKiYUGn9s+c5odAaRGaotH8bGhlII9kTo6nD45CFVQsg0ID9mbKqyiyoEQ4X
+                c88TZeGJPLPFvLyY10WVrtNfr67IC1tVJ9vrLY87HRieqREFISHXUjq8m4y6
+                4fhJdofsFFtRTw7bcqysXpreYzQZ98IbZUXlYaKNGoVtFStOr7SOjDQq0jFq
+                EHzl5aj2BRjLrPxEA+mTd6y8MRGoMLqJ6WCXMftXgFV4Sb6XYAn7yY8Q1jjL
+                duA0sd7ERhM55Jmi0EQRmx1QjC1scx7Di7ETw/0FYlRJz84BAAA=
+                """
+        )
+
+    @Test
+    fun errors() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                package com.example
+
+                import android.app.Activity
+                import androidx.compose.runtime.Composable
+                import androidx.compose.ui.platform.LocalContext
+
+                class MyActivity: Activity()
+
+                @Composable
+                fun Test() {
+                    val activity: Activity = LocalContext.current as Activity
+                    val activity2 = LocalContext.current as? Activity
+                    val activity3 = LocalContext.current as? MyActivity
+                }
+            """
+                        .trimIndent()
+                ),
+                Stubs.Composable,
+                Stubs.CompositionLocal,
+                LocalContextStub,
+                ContextStub
+            )
+            .run()
+            .expect(
+                """
+                src/com/example/MyActivity.kt:11: Error: LocalContext should not be cast to Activity, use LocalActivity instead [ContextCastToActivity]
+                    val activity: Activity = LocalContext.current as Activity
+                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                src/com/example/MyActivity.kt:12: Error: LocalContext should not be cast to Activity, use LocalActivity instead [ContextCastToActivity]
+                    val activity2 = LocalContext.current as? Activity
+                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                src/com/example/MyActivity.kt:13: Error: LocalContext should not be cast to Activity, use LocalActivity instead [ContextCastToActivity]
+                    val activity3 = LocalContext.current as? MyActivity
+                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                3 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun clean() {
+        lint()
+            .files(
+                kotlin(
+                    """
+                package com.example
+
+                import android.app.Activity
+                import androidx.activity.ComponentActivity
+                import androidx.activity.compose.LocalActivity
+                import androidx.compose.runtime.Composable
+                import androidx.compose.ui.platform.LocalContext
+                
+                @Composable
+                fun Test() {
+                    val context = LocalContext.current
+                    val activity: Activity = LocalActivity.current as Activity
+                    val activity2 = LocalContext.current as ComponentActivity
+                }
+                """
+                        .trimIndent()
+                ),
+                Stubs.Composable,
+                Stubs.CompositionLocal,
+                LocalActivityStub,
+                LocalContextStub,
+                ContextStub,
+                COMPONENT_ACTIVITY
+            )
+            .run()
+            .expectClean()
+    }
+}
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 9a77496..7922f58 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -649,6 +649,7 @@
     method public boolean isNumericSearchEnabled();
     method public boolean isVerbatimSearchEnabled();
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
+    field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT = 0; // 0x0
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT = 2; // 0x2
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN = 3; // 0x3
     field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2
@@ -880,6 +881,7 @@
     field public static final String FUNCTION_NAME_HAS_PROPERTY = "hasProperty";
     field public static final String FUNCTION_NAME_PROPERTY_DEFINED = "propertyDefined";
     field public static final String FUNCTION_NAME_SEARCH = "search";
+    field public static final String FUNCTION_NAME_SEMANTIC_SEARCH = "semanticSearch";
   }
 
   @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class NegationNode implements androidx.appsearch.ast.Node {
@@ -984,6 +986,21 @@
     method public void setProperties(java.util.List);
   }
 
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class SemanticSearchNode implements androidx.appsearch.ast.FunctionNode {
+    ctor public SemanticSearchNode(int);
+    ctor public SemanticSearchNode(int, float);
+    ctor public SemanticSearchNode(int, float, float);
+    ctor public SemanticSearchNode(int, float, float, int);
+    method public int getDistanceMetric();
+    method public String getFunctionName();
+    method public float getLowerBound();
+    method public float getUpperBound();
+    method public int getVectorIndex();
+    method public void setBounds(float, float);
+    method public void setDistanceMetric(int);
+    method public void setVectorIndex(int);
+  }
+
 }
 
 package androidx.appsearch.exceptions {
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 9a77496..7922f58 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -649,6 +649,7 @@
     method public boolean isNumericSearchEnabled();
     method public boolean isVerbatimSearchEnabled();
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1; // 0x1
+    field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT = 0; // 0x0
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT = 2; // 0x2
     field public static final int EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN = 3; // 0x3
     field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2
@@ -880,6 +881,7 @@
     field public static final String FUNCTION_NAME_HAS_PROPERTY = "hasProperty";
     field public static final String FUNCTION_NAME_PROPERTY_DEFINED = "propertyDefined";
     field public static final String FUNCTION_NAME_SEARCH = "search";
+    field public static final String FUNCTION_NAME_SEMANTIC_SEARCH = "semanticSearch";
   }
 
   @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class NegationNode implements androidx.appsearch.ast.Node {
@@ -984,6 +986,21 @@
     method public void setProperties(java.util.List);
   }
 
+  @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public final class SemanticSearchNode implements androidx.appsearch.ast.FunctionNode {
+    ctor public SemanticSearchNode(int);
+    ctor public SemanticSearchNode(int, float);
+    ctor public SemanticSearchNode(int, float, float);
+    ctor public SemanticSearchNode(int, float, float, int);
+    method public int getDistanceMetric();
+    method public String getFunctionName();
+    method public float getLowerBound();
+    method public float getUpperBound();
+    method public int getVectorIndex();
+    method public void setBounds(float, float);
+    method public void setDistanceMetric(int);
+    method public void setVectorIndex(int);
+  }
+
 }
 
 package androidx.appsearch.exceptions {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java
index b7ed1f8..87882cf 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java
@@ -62,6 +62,10 @@
 
         e = Assert.assertThrows(IllegalArgumentException.class, () -> new PropertyPath("a[0]b"));
         assertThat(e.getMessage()).startsWith("Malformed path (']' not followed by '.')");
+
+        e = Assert.assertThrows(IllegalArgumentException.class,
+                () -> new PropertyPath("foo AND bar:(baz >= 0)"));
+        assertThat(e.getMessage()).startsWith("Malformed path (non alphanumeric character)");
     }
 
     @Test
@@ -81,6 +85,19 @@
     }
 
     @Test
+    public void testPropertyPathCJKTValid() {
+        PropertyPath path = new PropertyPath("我.每天.走路.上班[2]");
+        assertThat(path.size()).isEqualTo(4);
+        assertThat(path.get(0).getPropertyName()).isEqualTo("我");
+        assertThat(path.get(0).getPropertyIndex())
+                .isEqualTo(NON_REPEATED_CARDINALITY);
+        assertThat(path.get(1).getPropertyName()).isEqualTo("每天");
+        assertThat(path.get(2).getPropertyName()).isEqualTo("走路");
+        assertThat(path.get(3).getPropertyName()).isEqualTo("上班");
+        assertThat(path.get(3).getPropertyIndex()).isEqualTo(2);
+    }
+
+    @Test
     public void testPropertyPathIterator() {
         PropertyPath path = new PropertyPath("foo.bar[2].bat.baz");
         Iterator iterator = path.iterator();
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SemanticSearchNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SemanticSearchNodeCtsTest.java
new file mode 100644
index 0000000..5bd4b88
--- /dev/null
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SemanticSearchNodeCtsTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.appsearch.cts.ast.query;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.appsearch.app.SearchSpec;
+import androidx.appsearch.ast.query.SemanticSearchNode;
+import androidx.appsearch.flags.CheckFlagsRule;
+import androidx.appsearch.flags.DeviceFlagsValueProvider;
+import androidx.appsearch.flags.Flags;
+import androidx.appsearch.flags.RequiresFlagsEnabled;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@RequiresFlagsEnabled(Flags.FLAG_ENABLE_ABSTRACT_SYNTAX_TREES)
+public class SemanticSearchNodeCtsTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Test
+    public void testConstructor_throwsOnNegativeIndex() {
+        assertThrows(IllegalArgumentException.class, () -> new SemanticSearchNode(-1));
+    }
+
+    @Test
+    public void testConstructor_throwsOnInvalidBounds() {
+        IllegalArgumentException thrown =  assertThrows(IllegalArgumentException.class,
+                () -> new SemanticSearchNode(0, 1, -1));
+
+        assertThat(thrown).hasMessageThat().contains("lower bound must be less than or equal");
+    }
+
+    @Test
+    public void testConstructor_throwsOnInvalidDistanceMetric() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SemanticSearchNode(0, -1, 1, -1));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> new SemanticSearchNode(0, -1, 1, 4));
+    }
+
+    @Test
+    public void testConstructor_allDefaultValues() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+
+        assertThat(semanticSearchNode.getLowerBound()).isEqualTo(Float.NEGATIVE_INFINITY);
+        assertThat(semanticSearchNode.getUpperBound()).isEqualTo(Float.POSITIVE_INFINITY);
+        assertThat(semanticSearchNode.getDistanceMetric())
+                .isEqualTo(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT);
+    }
+
+    @Test
+    public void testConstructor_lowerBoundSet_defaultValues() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0, -1);
+
+        assertThat(semanticSearchNode.getUpperBound()).isEqualTo(Float.POSITIVE_INFINITY);
+        assertThat(semanticSearchNode.getDistanceMetric())
+                .isEqualTo(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT);
+    }
+
+    @Test
+    public void testConstructor_boundsSet_defaultValues() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0, -1, 1);
+
+        assertThat(semanticSearchNode.getDistanceMetric())
+                .isEqualTo(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT);
+    }
+
+    @Test
+    public void testGetFunctionName_functionNameCorrect() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+        assertThat(semanticSearchNode.getFunctionName()).isEqualTo("semanticSearch");
+    }
+
+    @Test
+    public void testGetChildren_returnsEmptyList() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+        assertThat(semanticSearchNode.getChildren()).isEmpty();
+    }
+
+    @Test
+    public void testSetDistanceMetric_setDistanceMetricCorrectly() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+        semanticSearchNode.setDistanceMetric(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
+        assertThat(semanticSearchNode.getDistanceMetric())
+                .isEqualTo(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
+    }
+
+    @Test
+    public void testSetDistanceMetric_throwsOnInvalidDistanceMetric() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SemanticSearchNode(0).setDistanceMetric(-1));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> new SemanticSearchNode(0).setDistanceMetric(4));
+    }
+
+    @Test
+    public void testSetBounds_setLowerBoundUpperBoundCorrectly() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+        semanticSearchNode.setBounds(0, 1);
+        assertThat(semanticSearchNode.getLowerBound()).isEqualTo(0);
+        assertThat(semanticSearchNode.getUpperBound()).isEqualTo(1);
+    }
+
+    @Test
+    public void testSetBounds_throwsOnInvalidInputs() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> semanticSearchNode.setBounds(1, -1));
+        assertThat(thrown).hasMessageThat().contains("lower bound must be less than or equal");
+    }
+
+    @Test
+    public void testSetVectorIndex_setVectorIndexCorrectly() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+        semanticSearchNode.setVectorIndex(1);
+        assertThat(semanticSearchNode.getVectorIndex()).isEqualTo(1);
+    }
+
+    @Test
+    public void testSetVectorIndex_throwsOnNegativeInput() {
+        SemanticSearchNode semanticSearchNode = new SemanticSearchNode(0);
+        assertThrows(IllegalArgumentException.class, () -> semanticSearchNode.setVectorIndex(-1));
+
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyPath.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyPath.java
index 0e69136..6e418fd 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyPath.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyPath.java
@@ -91,6 +91,9 @@
                 controlIsIndex = c == '[';
                 break;
             }
+            if (!Character.isLetterOrDigit(c)) {
+                throw new IllegalArgumentException("Malformed path (non alphanumeric character)");
+            }
         }
 
         if (controlPos == 0 || path.isEmpty()) {
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index ca736d9..2bd18e7 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -322,6 +322,7 @@
     // {@link SearchSpecProto.EmbeddingQueryMetricType.Code}
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     @IntDef(value = {
+            EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT,
             EMBEDDING_SEARCH_METRIC_TYPE_COSINE,
             EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT,
             EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN,
@@ -331,6 +332,13 @@
     }
 
     /**
+     * Use the default metric set in {@link SearchSpec#getDefaultEmbeddingSearchMetricType()} for
+     * embedding search and ranking.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
+    public static final int EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT = 0;
+
+    /**
      * Cosine similarity as metric for embedding search and ranking.
      */
     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/FunctionNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/FunctionNode.java
index a539eef..a9f201b 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/FunctionNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/FunctionNode.java
@@ -47,6 +47,7 @@
             FUNCTION_NAME_HAS_PROPERTY,
             FUNCTION_NAME_PROPERTY_DEFINED,
             FUNCTION_NAME_SEARCH,
+            FUNCTION_NAME_SEMANTIC_SEARCH
     })
     @interface FunctionName {}
 
@@ -74,6 +75,12 @@
     String FUNCTION_NAME_SEARCH = "search";
 
     /**
+     * Name of the query function represented by
+     * {@link androidx.appsearch.ast.query.SemanticSearchNode}.
+     */
+    String FUNCTION_NAME_SEMANTIC_SEARCH = "semanticSearch";
+
+    /**
      * Gets the name of the node that extends the {@link FunctionNode}.
      */
     @NonNull
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SemanticSearchNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SemanticSearchNode.java
new file mode 100644
index 0000000..c4d93b2
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SemanticSearchNode.java
@@ -0,0 +1,212 @@
+/*
+ * 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.appsearch.ast.query;
+
+import androidx.annotation.NonNull;
+import androidx.appsearch.app.ExperimentalAppSearchApi;
+import androidx.appsearch.app.SearchSpec;
+import androidx.appsearch.ast.FunctionNode;
+import androidx.appsearch.flags.FlaggedApi;
+import androidx.appsearch.flags.Flags;
+import androidx.core.util.Preconditions;
+
+/**
+ * {@link FunctionNode} that represents the semanticSearch function.
+ *
+ * 

The semanticSearch function matches all documents that have at least one embedding vector with + * a matching model signature + * (see {@link androidx.appsearch.app.EmbeddingVector#getModelSignature()}) and a similarity score + * within the range specified based on the provided metric. + * + *

This node can be used to build a query that contains the semanticSearch function. For example, + * the node {@code SemanticSearchNode(0, -0.5, 0.5, DOT_PRODUCT)} is equivalent + * to the query `semanticSearch(getEmbeddingParameter(0), -0.5, 0.5, "DOT_PRODUCT")`. + */ +@ExperimentalAppSearchApi +@FlaggedApi(Flags.FLAG_ENABLE_ABSTRACT_SYNTAX_TREES) +public final class SemanticSearchNode implements FunctionNode { + private int mVectorIndex; + private float mLowerBound; + private float mUpperBound; + private @SearchSpec.EmbeddingSearchMetricType int mDistanceMetric; + + /** + * Constructor for {@link SemanticSearchNode} representing the semanticSearch function in a + * query. + * + * @param vectorIndex The index of the embedding vector in the list of vectors returned by + * {@link SearchSpec#getEmbeddingParameters()} to use in the search. + * @param lowerBound The lower bound on similarity score for a embedding vector such that the + * associated document will be returned. + * @param upperBound The upper bound on similarity score for a embedding vector such that the + * associated document will be returned. + * @param distanceMetric How distance between embedding vectors will be calculated. + */ + public SemanticSearchNode(int vectorIndex, float lowerBound, float upperBound, + @SearchSpec.EmbeddingSearchMetricType int distanceMetric) { + Preconditions.checkArgument(vectorIndex >= 0, + "Vector index must be non-negative."); + Preconditions.checkArgument(lowerBound <= upperBound, + "Provided lower bound must be less than or equal to" + + " the provided upper bound."); + Preconditions.checkArgumentInRange(distanceMetric, + SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT, + SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN, + "Embedding search metric type"); + mVectorIndex = vectorIndex; + mLowerBound = lowerBound; + mUpperBound = upperBound; + mDistanceMetric = distanceMetric; + } + + /** + * Constructor for {@link SemanticSearchNode} representing the semanticSearch function in a + * query. + * + *

By default: + *

    + *
  • The default set by the user and returned by + * {@link SearchSpec#getDefaultEmbeddingSearchMetricType()} will be used to determine + * similarity between embedding vectors. If no default is set, cosine similarity will be + * used. + *
+ * + *

See {@link #SemanticSearchNode(int, float, float, int)} for an explanation of the + * parameters. + */ + public SemanticSearchNode(int vectorIndex, float lowerBound, float upperBound) { + this(vectorIndex, lowerBound, upperBound, SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT); + } + + /** + * Constructor for {@link SemanticSearchNode} representing the semanticSearch function in a + * query. + * + *

By default: + *

    + *
  • The default set by the user and returned by + * {@link SearchSpec#getDefaultEmbeddingSearchMetricType()} will be used to determine + * similarity between embedding vectors. If no default is set, cosine similarity will be + * used. + *
  • The upper bound on similarity scores for an embedding vector such that the + * associated document will be returned is positive infinity. + *
+ * + *

See {@link #SemanticSearchNode(int, float, float, int)} for an explanation of the + * parameters. + */ + public SemanticSearchNode(int vectorIndex, float lowerBound) { + this(vectorIndex, lowerBound, Float.POSITIVE_INFINITY); + } + + /** + * Constructor for {@link SemanticSearchNode} representing the semanticSearch function in a + * query. + * + *

By default: + *

    + *
  • The default set by the user and returned by + * {@link SearchSpec#getDefaultEmbeddingSearchMetricType()} will be used to determine + * similarity between embedding vectors. If no default is set, cosine similarity will be + * used. + *
  • The upper bound on similarity scores for an embedding vector such that the + * associated document will be returned is positive infinity. + *
  • The lower bound on similarity scores for an embedding vector such that the + * associated document will be returned is negative infinity. + *
+ * + *

See {@link #SemanticSearchNode(int, float, float, int)} for an explanation of the + * parameters. + */ + public SemanticSearchNode(int vectorIndex) { + this(vectorIndex, Float.NEGATIVE_INFINITY); + } + + /** + * Returns the name of the function represented by {@link SemanticSearchNode}. + */ + @NonNull + @Override + @FunctionName + public String getFunctionName() { + return FUNCTION_NAME_SEMANTIC_SEARCH; + } + + /** + * Returns the index of the embedding vector used in semanticSearch. + */ + public int getVectorIndex() { + return mVectorIndex; + } + + /** + * Returns the lower bound of the range of values similarity scores must fall in. + */ + public float getLowerBound() { + return mLowerBound; + } + + /** + * Returns the upper bound of the range of values similarity scores must fall in. + */ + public float getUpperBound() { + return mUpperBound; + } + + /** + * Returns the distance metric used to calculated similarity between embedding vectors. + */ + @SearchSpec.EmbeddingSearchMetricType + public int getDistanceMetric() { + return mDistanceMetric; + } + + /** + * Sets the index of the embedding vector that semanticSearch will use. + */ + public void setVectorIndex(int vectorIndex) { + Preconditions.checkArgument(vectorIndex >= 0, "Vector Index must be non-negative."); + mVectorIndex = vectorIndex; + } + + /** + * Sets the bounds of the range of values that semanticSearch will search against. + * + * @param lowerBound The lower bound of the range of values. + * @param upperBound The upper bound of the range of values. + */ + public void setBounds(float lowerBound, float upperBound) { + Preconditions.checkArgument(lowerBound <= upperBound, + "Provided lower bound must be less than or equal to" + + " the provided upper bound"); + mLowerBound = lowerBound; + mUpperBound = upperBound; + } + + /** + * Sets how similarity is calculated between embedding vectors. + * + * @param distanceMetric How similarity is calculated between embedding vectors. + */ + public void setDistanceMetric(@SearchSpec.EmbeddingSearchMetricType int distanceMetric) { + Preconditions.checkArgumentInRange(distanceMetric, + SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT, + SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN, + "Embedding search metric type"); + mDistanceMetric = distanceMetric; + } +}

diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 6eda71c..76acd5e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -49,8 +49,6 @@
 import com.android.build.api.attributes.BuildTypeAttr
 import com.android.build.api.dsl.ApplicationExtension
 import com.android.build.api.dsl.CommonExtension
-import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnDeviceCompilation
-import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnJvmCompilation
 import com.android.build.api.dsl.LibraryExtension
 import com.android.build.api.dsl.PrivacySandboxSdkExtension
 import com.android.build.api.dsl.TestBuildType
@@ -1177,13 +1175,13 @@
 
         lint.targetSdk = project.defaultAndroidConfig.targetSdk
         compilations
-            .withType(KotlinMultiplatformAndroidTestOnDeviceCompilation::class.java)
+            .withType(DeprecatedKotlinMultiplatformAndroidTestOnDeviceCompilation::class.java)
             .configureEach {
                 it.instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
                 it.animationsDisabled = true
             }
         compilations
-            .withType(KotlinMultiplatformAndroidTestOnJvmCompilation::class.java)
+            .withType(DeprecatedKotlinMultiplatformAndroidTestOnJvmCompilation::class.java)
             .configureEach {
                 it.isReturnDefaultValues = true
                 // Include resources in Robolectric tests as a workaround for b/184641296
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/DeprecatedKotlinMultiplatformAndroidTarget.kt b/buildSrc/private/src/main/kotlin/androidx/build/DeprecatedKotlinMultiplatformAndroidTargetAndCompilation.kt
similarity index 67%
rename from buildSrc/private/src/main/kotlin/androidx/build/DeprecatedKotlinMultiplatformAndroidTarget.kt
rename to buildSrc/private/src/main/kotlin/androidx/build/DeprecatedKotlinMultiplatformAndroidTargetAndCompilation.kt
index fbf04ac..700c36c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/DeprecatedKotlinMultiplatformAndroidTarget.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/DeprecatedKotlinMultiplatformAndroidTargetAndCompilation.kt
@@ -19,5 +19,13 @@
 package androidx.build
 
 import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
+import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnDeviceCompilation
+import com.android.build.api.dsl.KotlinMultiplatformAndroidTestOnJvmCompilation
 
 typealias DeprecatedKotlinMultiplatformAndroidTarget = KotlinMultiplatformAndroidTarget
+
+typealias DeprecatedKotlinMultiplatformAndroidTestOnDeviceCompilation =
+    KotlinMultiplatformAndroidTestOnDeviceCompilation
+
+typealias DeprecatedKotlinMultiplatformAndroidTestOnJvmCompilation =
+    KotlinMultiplatformAndroidTestOnJvmCompilation
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
index a6081aa..2da4d5f 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
@@ -96,9 +96,15 @@
                 Log.debug { "Empty capture session quirk completed" }
             }
         }
-        Threading.runBlockingChecked(threads.backgroundDispatcher, 5000L) {
+        Threading.runBlockingCheckedOrNull(threads.backgroundDispatcher, CAMERA_CLOSE_TIMEOUT_MS) {
             cameraDevice.closeWithTrace()
         }
+            ?: run {
+                Log.error {
+                    "Camera device close timed out after ${CAMERA_CLOSE_TIMEOUT_MS}ms. " +
+                        "The camera is likely in a bad state."
+                }
+            }
         if (camera2Quirks.shouldWaitForCameraDeviceOnClosed(cameraId)) {
             Log.debug { "Waiting for OnClosed from $cameraId" }
             if (androidCameraState.awaitCameraDeviceClosed(timeoutMillis = 5000)) {
@@ -158,4 +164,8 @@
         }
         sessionConfigured.await()
     }
+
+    companion object {
+        const val CAMERA_CLOSE_TIMEOUT_MS = 8_000L // 8s
+    }
 }
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProviderImpl.kt b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProviderImpl.kt
index be34019..82f132b 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProviderImpl.kt
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraProviderImpl.kt
@@ -55,7 +55,6 @@
 import androidx.camera.core.impl.utils.futures.FutureChain
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.core.internal.CameraUseCaseAdapter
-import androidx.concurrent.futures.CallbackToFutureAdapter
 import androidx.core.util.Preconditions
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
@@ -88,37 +87,29 @@
             cameraXConfig?.let { configure(it) }
             val cameraX = CameraX(context, cameraXConfigProvider)
 
-            cameraXInitializeFuture =
-                CallbackToFutureAdapter.getFuture { completer ->
-                    synchronized(lock) {
-                        val future: ListenableFuture =
-                            FutureChain.from(cameraXShutdownFuture)
-                                .transformAsync(
-                                    { cameraX.initializeFuture },
-                                    CameraXExecutors.directExecutor()
-                                )
-                        Futures.addCallback(
-                            future,
-                            object : FutureCallback {
-                                override fun onSuccess(result: Void?) {
-                                    [email protected] = cameraX
-                                    [email protected] =
-                                        ContextUtil.getApplicationContext(context)
-                                    completer.set(null)
-                                }
+            val initFuture =
+                FutureChain.from(cameraXShutdownFuture)
+                    .transformAsync({ cameraX.initializeFuture }, CameraXExecutors.directExecutor())
 
-                                override fun onFailure(t: Throwable) {
-                                    completer.setException(t)
-                                }
-                            },
-                            CameraXExecutors.directExecutor()
-                        )
+            cameraXInitializeFuture = initFuture
+
+            Futures.addCallback(
+                initFuture,
+                object : FutureCallback {
+                    override fun onSuccess(void: Void?) {
+                        [email protected] = cameraX
+                        [email protected] =
+                            ContextUtil.getApplicationContext(context)
                     }
 
-                    "LifecycleCameraProvider-initialize"
-                }
+                    override fun onFailure(t: Throwable) {
+                        shutdownAsync()
+                    }
+                },
+                CameraXExecutors.directExecutor()
+            )
 
-            return cameraXInitializeFuture as ListenableFuture
+            return Futures.nonCancellationPropagating(initFuture)
         }
     }
 
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraPipeConfigTestRule.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraPipeConfigTestRule.kt
index 07cb1de..8d63351 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraPipeConfigTestRule.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraPipeConfigTestRule.kt
@@ -60,9 +60,9 @@
             override fun evaluate() {
                 if (active) {
                     if (Log.isLoggable(CAMERA_PIPE_MH_FLAG, Log.DEBUG)) {
-                        testInLab()
+                        testInPipeLab()
                     } else {
-                        testNotInLab()
+                        testNotInPipeLab()
                     }
                 } else {
                     if (Log.isLoggable(CAMERA2_TEST_DISABLE, Log.DEBUG)) {
@@ -74,7 +74,7 @@
                 }
             }
 
-            private fun testInLab() {
+            private fun testInPipeLab() {
                 try {
                     log("started: ${description.displayName}")
                     logUncaughtExceptions()
@@ -91,9 +91,9 @@
                 }
             }
 
-            private fun testNotInLab() {
-                if (testInAllowList()) {
-                    // Run the test
+            private fun testNotInPipeLab() {
+                if (testInAllowList() && !Log.isLoggable(CAMERA_MH_FLAG, Log.DEBUG)) {
+                    // Run the test when (1) In the allow list && (2) It is not MH daily test.
                     base.evaluate()
                 } else {
                     throw AssumptionViolatedException(
@@ -130,6 +130,7 @@
         private const val CAMERA2_TEST_DISABLE = "CAMERA2_TEST_DISABLE"
         private const val CAMERA_PIPE_TEST_FLAG = "CAMERA_PIPE_TESTING"
         private const val CAMERA_PIPE_MH_FLAG = "CameraPipeMH"
+        private const val CAMERA_MH_FLAG = "MH"
         private const val LOG_TAG = "CameraPipeTest"
 
         private val allowPresubmitTests =
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 5611700..b606700 100644
--- a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -31,6 +31,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -46,6 +47,7 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.saveable.rememberSaveableStateHolder
@@ -60,6 +62,7 @@
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
@@ -92,6 +95,7 @@
 @LargeTest
 class AnimatedContentTest {
     val rule = createComposeRule()
+
     // Detect leaks BEFORE and AFTER compose rule work
     @get:Rule
     val ruleChain: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()).around(rule)
@@ -1154,6 +1158,76 @@
         }
     }
 
+    @Test
+    fun testRecreatingTransitionInAnimatedContent() {
+        var toggle by mutableStateOf(true)
+        var targetState by mutableStateOf(true)
+        var currentSize = IntSize(200, 200)
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                val transition = key(toggle) { updateTransition(targetState) }
+                Column {
+                    transition.AnimatedContent(
+                        modifier =
+                            Modifier.onSizeChanged {
+                                currentSize = it
+                                assertNotEquals(IntSize.Zero, it)
+                            },
+                        transitionSpec = { fadeIn() togetherWith fadeOut() }
+                    ) {
+                        if (it) {
+                            Box(Modifier.background(Color.Red).size(200.dp))
+                        } else {
+                            Box(Modifier.background(Color.Green).size(300.dp))
+                        }
+                    }
+                }
+            }
+        }
+        rule.runOnIdle { toggle = !toggle }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        targetState = !targetState
+        while (currentSize == IntSize(200, 200)) {
+            rule.mainClock.advanceTimeByFrame()
+            rule.waitForIdle()
+        }
+        var lastSize = IntSize(200, 200)
+        var frameCount = 0
+        while (currentSize.width < 290f && currentSize.height < 290f || frameCount < 10) {
+            // Assert that the size is monotonically increasing, never jumps to 0
+            assert(lastSize.width < currentSize.width)
+            assert(lastSize.height < currentSize.height)
+            lastSize = currentSize
+            rule.mainClock.advanceTimeByFrame()
+            frameCount++
+        }
+        rule.mainClock.autoAdvance = true
+        rule.waitForIdle()
+
+        // Now recreate the transition again
+        rule.runOnIdle { toggle = !toggle }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        targetState = !targetState
+        while (currentSize == IntSize(300, 300)) {
+            rule.mainClock.advanceTimeByFrame()
+            rule.waitForIdle()
+        }
+        lastSize = IntSize(300, 300)
+        frameCount = 0
+        while (currentSize.width > 210f && currentSize.height > 210f || frameCount < 10) {
+            // Assert that the size is monotonically increasing, never jumps to 0
+            assert(lastSize.width > currentSize.width)
+            assert(lastSize.height > currentSize.height)
+            lastSize = currentSize
+            rule.mainClock.advanceTimeByFrame()
+            frameCount++
+        }
+        rule.mainClock.autoAdvance = true
+        rule.waitForIdle()
+    }
+
     private fun assertOffsetEquals(expected: Offset, actual: Offset) {
         assertEquals(expected.x, actual.x, 0.00001f)
         assertEquals(expected.y, actual.y, 0.00001f)
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index a8d32ea..6a90501 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -580,7 +580,7 @@
                 // Keep the SizeModifier in the chain and switch between active animating and
                 // passive
                 // observing based on sizeAnimation's value
-                SizeModifierElement(sizeAnimation, sizeTransform)
+                SizeModifierElement(sizeAnimation, sizeTransform, this)
             )
     }
 
@@ -596,39 +596,44 @@
         }
     }
 
-    private inner class SizeModifierElement(
+    private class SizeModifierElement(
         val sizeAnimation: Transition.DeferredAnimation?,
-        val sizeTransform: State
-    ) : ModifierNodeElement() {
-        override fun create(): SizeModifierNode {
-            return SizeModifierNode(sizeAnimation, sizeTransform)
+        val sizeTransform: State,
+        val scope: AnimatedContentTransitionScopeImpl
+    ) : ModifierNodeElement>() {
+        override fun create(): SizeModifierNode {
+            return SizeModifierNode(sizeAnimation, sizeTransform, scope)
         }
 
         override fun hashCode(): Int {
-            return sizeAnimation.hashCode() * 31 + sizeTransform.hashCode()
+            return (31 * scope.hashCode() + sizeAnimation.hashCode()) * 31 +
+                sizeTransform.hashCode()
         }
 
         override fun equals(other: Any?): Boolean {
-            return other is AnimatedContentTransitionScopeImpl<*>.SizeModifierElement &&
+            return other is SizeModifierElement<*> &&
                 other.sizeAnimation == sizeAnimation &&
                 other.sizeTransform == sizeTransform
         }
 
-        override fun update(node: SizeModifierNode) {
+        override fun update(node: SizeModifierNode) {
             node.sizeAnimation = sizeAnimation
             node.sizeTransform = sizeTransform
+            node.scope = scope
         }
 
         override fun InspectorInfo.inspectableProperties() {
             name = "sizeTransform"
             properties["sizeAnimation"] = sizeAnimation
             properties["sizeTransform"] = sizeTransform
+            properties["scope"] = scope
         }
     }
 
-    private inner class SizeModifierNode(
+    private class SizeModifierNode(
         var sizeAnimation: Transition.DeferredAnimation?,
         var sizeTransform: State,
+        var scope: AnimatedContentTransitionScopeImpl,
     ) : LayoutModifierNodeWithPassThroughIntrinsics() {
         // This is used to track the on-going size change so that when the target state changes,
         // we always start from the last seen size to the new target size to ensure continuity.
@@ -637,6 +642,11 @@
         private fun lastContinuousSizeOrDefault(default: IntSize) =
             if (lastSize == UnspecifiedSize) default else lastSize
 
+        override fun onReset() {
+            super.onReset()
+            lastSize = UnspecifiedSize
+        }
+
         override fun MeasureScope.measure(
             measurable: Measurable,
             constraints: Constraints
@@ -655,33 +665,30 @@
                     sizeAnimation!!.animate(
                         transitionSpec = {
                             val initial =
-                                if (
-                                    initialState ==
-                                        [email protected]
-                                ) {
+                                if (initialState == scope.initialState) {
                                     lastContinuousSizeOrDefault(currentSize)
                                 } else {
-                                    targetSizeMap[initialState]?.value ?: IntSize.Zero
+                                    scope.targetSizeMap[initialState]?.value ?: IntSize.Zero
                                 }
-                            val target = targetSizeMap[targetState]?.value ?: IntSize.Zero
+                            val target = scope.targetSizeMap[targetState]?.value ?: IntSize.Zero
                             sizeTransform.value?.createAnimationSpec(initial, target)
                                 ?: spring(stiffness = Spring.StiffnessMediumLow)
                         }
                     ) {
                         // Animate from the approach size to the lookahead size.
-                        if (it == initialState) {
+                        if (it == scope.initialState) {
                             lastContinuousSizeOrDefault(currentSize)
                         } else {
-                            targetSizeMap[it]?.value ?: IntSize.Zero
+                            scope.targetSizeMap[it]?.value ?: IntSize.Zero
                         }
                     }
-                animatedSize = size
+                scope.animatedSize = size
                 measuredSize = size.value
                 lastSize = size.value
             }
             return layout(measuredSize.width, measuredSize.height) {
                 val offset =
-                    contentAlignment.align(
+                    scope.contentAlignment.align(
                         IntSize(placeable.width, placeable.height),
                         measuredSize,
                         LayoutDirection.Ltr
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsDeviceTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsDeviceTest.kt
index cd3b254..6614d35 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsDeviceTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsDeviceTest.kt
@@ -46,11 +46,10 @@
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
 import androidx.core.view.children
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -65,28 +64,23 @@
 @RunWith(AndroidJUnit4::class)
 class WindowInsetsDeviceTest {
     @get:Rule val rule = createAndroidComposeRule()
-    private lateinit var finishLatch: CountDownLatch
-    private val finishLatchGetter
-        get() = finishLatch
-
-    private val observer =
-        object : DefaultLifecycleObserver {
-            override fun onDestroy(owner: LifecycleOwner) {
-                finishLatchGetter.countDown()
-            }
-        }
 
     @Before
     fun setup() {
         rule.activity.createdLatch.await(1, TimeUnit.SECONDS)
-        finishLatch = CountDownLatch(1)
-        rule.runOnUiThread { rule.activity.lifecycle.addObserver(observer) }
     }
 
     @After
-    fun tearDown() {
-        rule.runOnUiThread { rule.activity.finish() }
-        assertThat(finishLatch.await(1, TimeUnit.SECONDS)).isTrue()
+    fun teardown() {
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        val activity = rule.activity
+        while (!activity.isDestroyed) {
+            instrumentation.runOnMainSync {
+                if (!activity.isDestroyed) {
+                    activity.finish()
+                }
+            }
+        }
     }
 
     @OptIn(ExperimentalLayoutApi::class)
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsPaddingTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsPaddingTest.kt
index a3a1bc5..2748f65 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsPaddingTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsPaddingTest.kt
@@ -58,15 +58,12 @@
 import androidx.core.view.DisplayCutoutCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.forEach
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 import kotlin.math.roundToInt
 import org.junit.After
 import org.junit.Before
@@ -81,29 +78,23 @@
 
     private lateinit var insetsView: InsetsView
 
-    private lateinit var finishLatch: CountDownLatch
-    private val finishLatchGetter
-        get() = finishLatch
-
-    private val observer =
-        object : DefaultLifecycleObserver {
-            override fun onDestroy(owner: LifecycleOwner) {
-                finishLatchGetter.countDown()
-            }
-        }
-
     @Before
     fun setup() {
         WindowInsetsHolder.setUseTestInsets(true)
-        finishLatch = CountDownLatch(1)
-        rule.runOnUiThread { rule.activity.lifecycle.addObserver(observer) }
     }
 
     @After
     fun teardown() {
         WindowInsetsHolder.setUseTestInsets(false)
-        rule.runOnUiThread { rule.activity.finish() }
-        assertThat(finishLatch.await(1, TimeUnit.SECONDS)).isTrue()
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        val activity = rule.activity
+        while (!activity.isDestroyed) {
+            instrumentation.runOnMainSync {
+                if (!activity.isDestroyed) {
+                    activity.finish()
+                }
+            }
+        }
     }
 
     @Test
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsSizeTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsSizeTest.kt
index 5b7c4ba..b397ecb 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsSizeTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsSizeTest.kt
@@ -36,14 +36,11 @@
 import androidx.core.graphics.Insets as AndroidXInsets
 import androidx.core.view.DisplayCutoutCompat
 import androidx.core.view.WindowInsetsCompat
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -57,29 +54,23 @@
 
     private lateinit var insetsView: InsetsView
 
-    private lateinit var finishLatch: CountDownLatch
-    private val finishLatchGetter
-        get() = finishLatch
-
-    private val observer =
-        object : DefaultLifecycleObserver {
-            override fun onDestroy(owner: LifecycleOwner) {
-                finishLatchGetter.countDown()
-            }
-        }
-
     @Before
     fun setup() {
         WindowInsetsHolder.setUseTestInsets(true)
-        finishLatch = CountDownLatch(1)
-        rule.runOnUiThread { rule.activity.lifecycle.addObserver(observer) }
     }
 
     @After
     fun teardown() {
         WindowInsetsHolder.setUseTestInsets(false)
-        rule.runOnUiThread { rule.activity.finish() }
-        assertThat(finishLatch.await(1, TimeUnit.SECONDS)).isTrue()
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        val activity = rule.activity
+        while (!activity.isDestroyed) {
+            instrumentation.runOnMainSync {
+                if (!activity.isDestroyed) {
+                    activity.finish()
+                }
+            }
+        }
     }
 
     @OptIn(ExperimentalLayoutApi::class)
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index f1a32dc..3ad373b 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -236,8 +236,17 @@
     property public abstract boolean isInProgress;
   }
 
+  public interface OverscrollFactory {
+    method public androidx.compose.foundation.OverscrollEffect createOverscrollEffect();
+    method public boolean equals(Object? other);
+    method public int hashCode();
+  }
+
   public final class OverscrollKt {
-    method public static androidx.compose.ui.Modifier overscroll(androidx.compose.ui.Modifier, androidx.compose.foundation.OverscrollEffect overscrollEffect);
+    method public static androidx.compose.runtime.ProvidableCompositionLocal getLocalOverscrollFactory();
+    method public static androidx.compose.ui.Modifier overscroll(androidx.compose.ui.Modifier, androidx.compose.foundation.OverscrollEffect? overscrollEffect);
+    method @androidx.compose.runtime.Composable public static androidx.compose.foundation.OverscrollEffect? rememberOverscrollEffect();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal LocalOverscrollFactory;
   }
 
   public final class PreferKeepClear_androidKt {
@@ -586,7 +595,6 @@
 
   public final class ScrollableDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior flingBehavior();
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.OverscrollEffect overscrollEffect();
     method public boolean reverseDirection(androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.foundation.gestures.Orientation orientation, boolean reverseScrolling);
     field public static final androidx.compose.foundation.gestures.ScrollableDefaults INSTANCE;
   }
@@ -1669,6 +1677,23 @@
     field public static final androidx.compose.foundation.text.AutoSizeDefaults INSTANCE;
   }
 
+  @kotlin.jvm.JvmInline public final value class AutofillHighlight {
+    ctor public AutofillHighlight(long autofillHighlightColor);
+    method public long getAutofillHighlightColor();
+    property public final long autofillHighlightColor;
+    field public static final androidx.compose.foundation.text.AutofillHighlight.Companion Companion;
+  }
+
+  public static final class AutofillHighlight.Companion {
+    method public long getDefault();
+    property public final long Default;
+  }
+
+  public final class AutofillHighlightKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal getLocalAutofillHighlight();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal LocalAutofillHighlight;
+  }
+
   public final class BasicSecureTextFieldKt {
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional kotlin.jvm.functions.Function2,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional int textObfuscationMode, optional char textObfuscationCharacter);
     method @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional kotlin.jvm.functions.Function2,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional int textObfuscationMode, optional char textObfuscationCharacter);
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 4d3121d..220870f 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -238,8 +238,17 @@
     property public abstract boolean isInProgress;
   }
 
+  public interface OverscrollFactory {
+    method public androidx.compose.foundation.OverscrollEffect createOverscrollEffect();
+    method public boolean equals(Object? other);
+    method public int hashCode();
+  }
+
   public final class OverscrollKt {
-    method public static androidx.compose.ui.Modifier overscroll(androidx.compose.ui.Modifier, androidx.compose.foundation.OverscrollEffect overscrollEffect);
+    method public static androidx.compose.runtime.ProvidableCompositionLocal getLocalOverscrollFactory();
+    method public static androidx.compose.ui.Modifier overscroll(androidx.compose.ui.Modifier, androidx.compose.foundation.OverscrollEffect? overscrollEffect);
+    method @androidx.compose.runtime.Composable public static androidx.compose.foundation.OverscrollEffect? rememberOverscrollEffect();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal LocalOverscrollFactory;
   }
 
   public final class PreferKeepClear_androidKt {
@@ -588,7 +597,6 @@
 
   public final class ScrollableDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior flingBehavior();
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.OverscrollEffect overscrollEffect();
     method public boolean reverseDirection(androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.foundation.gestures.Orientation orientation, boolean reverseScrolling);
     field public static final androidx.compose.foundation.gestures.ScrollableDefaults INSTANCE;
   }
@@ -1671,6 +1679,23 @@
     field public static final androidx.compose.foundation.text.AutoSizeDefaults INSTANCE;
   }
 
+  @kotlin.jvm.JvmInline public final value class AutofillHighlight {
+    ctor public AutofillHighlight(long autofillHighlightColor);
+    method public long getAutofillHighlightColor();
+    property public final long autofillHighlightColor;
+    field public static final androidx.compose.foundation.text.AutofillHighlight.Companion Companion;
+  }
+
+  public static final class AutofillHighlight.Companion {
+    method public long getDefault();
+    property public final long Default;
+  }
+
+  public final class AutofillHighlightKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal getLocalAutofillHighlight();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal LocalAutofillHighlight;
+  }
+
   public final class BasicSecureTextFieldKt {
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional kotlin.jvm.functions.Function2,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional int textObfuscationMode, optional char textObfuscationCharacter);
     method @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional kotlin.jvm.functions.Function2,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional int textObfuscationMode, optional char textObfuscationCharacter);
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/OverscrollBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/OverscrollBenchmark.kt
index 44e83a9..a39e403 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/OverscrollBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/OverscrollBenchmark.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.benchmark.lazy.MotionEventHelper
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
@@ -30,6 +29,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.overscroll
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -132,7 +132,7 @@
                         get() = true
                 }
             }
-        val overscrollEffect = ScrollableDefaults.overscrollEffect()
+        val overscrollEffect = rememberOverscrollEffect()
         Box(
             Modifier.scrollable(
                     wrappedScrollState,
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt
index 69bcd81..6e37486 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -59,7 +60,7 @@
         reverseArrangement: Boolean = false,
         flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
         userScrollEnabled: Boolean = true,
-        overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
         crossAxisSpacedBy: Dp = 0.dp,
         mainAxisSpacedBy: Dp = 0.dp,
         content: LazyGridScope.() -> Unit
@@ -89,7 +90,7 @@
         reverseArrangement: Boolean = false,
         flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
         userScrollEnabled: Boolean = true,
-        overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
         crossAxisSpacedBy: Dp = 0.dp,
         mainAxisSpacedBy: Dp = 0.dp,
         content: LazyGridScope.() -> Unit
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
index b236965..03a2d77 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
@@ -41,6 +41,7 @@
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -121,7 +122,7 @@
         reverseArrangement: Boolean = false,
         flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
         userScrollEnabled: Boolean = true,
-        overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
         spacedBy: Dp = 0.dp,
         isCrossAxis: Boolean = false,
         content: LazyListScope.() -> Unit
@@ -242,7 +243,7 @@
         isVertical = true,
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         beyondBoundsItemCount = beyondBoundsItemCount,
         content = content
     )
@@ -272,7 +273,7 @@
         flingBehavior = flingBehavior,
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         beyondBoundsItemCount = beyondBoundsItemCount,
         content = content
     )
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
index 048243f..ad45242 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
@@ -35,6 +35,7 @@
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -417,7 +418,7 @@
         isVertical = true,
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         beyondBoundsItemCount = beyondBoundsItemCount,
         content = content
     )
@@ -447,7 +448,7 @@
         flingBehavior = flingBehavior,
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         beyondBoundsItemCount = beyondBoundsItemCount,
         content = content
     )
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
index 2bee0bc..201eabc4 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
@@ -34,6 +34,7 @@
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -197,7 +198,7 @@
         isVertical = true,
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         beyondBoundsItemCount = beyondBoundsItemCount,
         content = content
     )
@@ -227,7 +228,7 @@
         flingBehavior = flingBehavior,
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         beyondBoundsItemCount = beyondBoundsItemCount,
         content = content
     )
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
index 73d90f7..5d836bc 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
@@ -21,10 +21,10 @@
 import androidx.compose.foundation.BaseLazyLayoutTestWithOrientation
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.Dp
@@ -56,7 +56,7 @@
         reverseLayout: Boolean = false,
         mainAxisSpacing: Dp = 0.dp,
         crossAxisArrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(0.dp),
-        overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
         content: LazyStaggeredGridScope.() -> Unit,
     ) {
         LazyStaggeredGrid(
@@ -93,7 +93,7 @@
         mainAxisSpacing: Dp = 0.dp,
         crossAxisArrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(0.dp),
         reverseLayout: Boolean = false,
-        overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
         content: LazyStaggeredGridScope.() -> Unit,
     ) {
         if (orientation == Orientation.Vertical) {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
index b84ee4c..3e5bfed 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.gestures.AnchoredDraggableState
 import androidx.compose.foundation.gestures.DraggableAnchors
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.anchoredDraggable
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.gestures.forEach
@@ -37,6 +36,7 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.overscroll
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.Center
 import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.End
 import androidx.compose.foundation.samples.AnchoredDraggableSampleValue.HalfEnd
@@ -185,7 +185,7 @@
         }
     val draggableSize = 80.dp
     val draggableSizePx = with(LocalDensity.current) { draggableSize.toPx() }
-    val overscrollEffect = ScrollableDefaults.overscrollEffect()
+    val overscrollEffect = rememberOverscrollEffect()
 
     Box(
         Modifier.fillMaxWidth().onSizeChanged { layoutSize ->
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/OverscrollSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/OverscrollSample.kt
index 6e7f875..5433f07 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/OverscrollSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/OverscrollSample.kt
@@ -22,7 +22,6 @@
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.gestures.rememberDraggableState
 import androidx.compose.foundation.gestures.rememberScrollableState
@@ -31,6 +30,7 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.overscroll
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -184,14 +184,13 @@
     val minPosition = -1000f
     val maxPosition = 1000f
 
-    val overscrollEffect = ScrollableDefaults.overscrollEffect()
+    val overscrollEffect = rememberOverscrollEffect()
 
     val draggableState = rememberDraggableState { delta ->
         // Horizontal, so convert the delta to a horizontal offset
         val deltaAsOffset = Offset(delta, 0f)
-        // Wrap the original logic inside applyToScroll
-        overscrollEffect.applyToScroll(deltaAsOffset, NestedScrollSource.UserInput) {
-            remainingOffset ->
+
+        val performDrag: (Offset) -> Offset = { remainingOffset ->
             val remainingDelta = remainingOffset.x
             val newPosition = (dragPosition + remainingDelta).coerceIn(minPosition, maxPosition)
             // Calculate how much delta we have consumed
@@ -200,6 +199,13 @@
             // Return how much offset we consumed, so that we can show overscroll for what is left
             Offset(consumed, 0f)
         }
+
+        if (overscrollEffect != null) {
+            // Wrap the original logic inside applyToScroll
+            overscrollEffect.applyToScroll(deltaAsOffset, NestedScrollSource.UserInput, performDrag)
+        } else {
+            performDrag(deltaAsOffset)
+        }
     }
 
     Box(
@@ -211,7 +217,7 @@
                 draggableState,
                 orientation = Orientation.Horizontal,
                 onDragStopped = {
-                    overscrollEffect.applyToFling(Velocity(it, 0f)) { velocity ->
+                    overscrollEffect?.applyToFling(Velocity(it, 0f)) { velocity ->
                         if (dragPosition == minPosition || dragPosition == maxPosition) {
                             // If we are at the min / max bound, give overscroll all of the velocity
                             Velocity.Zero
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
index 0ad3646..18945bb 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
@@ -30,6 +30,11 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.testutils.assertPixelColor
 import androidx.compose.testutils.assertPixels
 import androidx.compose.ui.Alignment
@@ -90,6 +95,71 @@
     private val boxTag = "box"
 
     @Test
+    fun rememberOverscrollEffect_defaultValue() {
+        lateinit var effect: OverscrollEffect
+        rule.setContent { effect = rememberOverscrollEffect()!! }
+        rule.runOnIdle {
+            assertThat(effect).isInstanceOf(AndroidEdgeEffectOverscrollEffect::class.java)
+        }
+    }
+
+    @Test
+    fun rememberOverscrollEffect_nullOverscrollFactory() {
+        var effect: OverscrollEffect? = null
+        rule.setContent {
+            CompositionLocalProvider(LocalOverscrollFactory provides null) {
+                effect = rememberOverscrollEffect()
+            }
+        }
+        rule.runOnIdle { assertThat(effect).isNull() }
+    }
+
+    @Test
+    fun rememberOverscrollEffect_ChangeOverscrollFactory() {
+        lateinit var effect: OverscrollEffect
+        val movableContent = movableContentOf { effect = rememberOverscrollEffect()!! }
+        var setCustomFactory by mutableStateOf(false)
+        class CustomEffect : OverscrollEffect {
+            override val isInProgress = false
+            override val effectModifier = Modifier
+
+            override fun applyToScroll(
+                delta: Offset,
+                source: NestedScrollSource,
+                performScroll: (Offset) -> Offset
+            ) = performScroll(delta)
+
+            override suspend fun applyToFling(
+                velocity: Velocity,
+                performFling: suspend (Velocity) -> Velocity
+            ) {}
+        }
+        val customFactory =
+            object : OverscrollFactory {
+                override fun createOverscrollEffect(): OverscrollEffect = CustomEffect()
+
+                override fun hashCode(): Int = -1
+
+                override fun equals(other: Any?) = other === this
+            }
+        rule.setContent {
+            if (setCustomFactory) {
+                CompositionLocalProvider(
+                    LocalOverscrollFactory provides customFactory,
+                    content = movableContent
+                )
+            } else {
+                movableContent()
+            }
+        }
+        rule.runOnIdle {
+            assertThat(effect).isInstanceOf(AndroidEdgeEffectOverscrollEffect::class.java)
+            setCustomFactory = true
+        }
+        rule.runOnIdle { assertThat(effect).isInstanceOf(CustomEffect::class.java) }
+    }
+
+    @Test
     fun overscrollEffect_scrollable_drag() {
         testDrag(reverseDirection = false)
     }
@@ -800,7 +870,7 @@
         lateinit var effect: OverscrollEffect
         rule.setContent {
             Box {
-                effect = rememberOverscrollEffect()
+                effect = rememberOverscrollEffect()!!
                 Box(Modifier.overscroll(effect).size(0.dp))
             }
         }
@@ -833,7 +903,7 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun notAttachedEffectIsNotConsumingOffsetsAndVelocity() {
         lateinit var effect: OverscrollEffect
-        rule.setContent { effect = rememberOverscrollEffect() }
+        rule.setContent { effect = rememberOverscrollEffect()!! }
 
         rule.runOnIdle {
             repeat(2) {
@@ -1172,7 +1242,7 @@
                 modifier =
                     Modifier.testTag(boxTag)
                         .size(100.dp)
-                        .overscroll(ScrollableDefaults.overscrollEffect())
+                        .overscroll(rememberOverscrollEffect())
                         .drawBehind { drawCount++ }
             )
         }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 2bdfaf3..fbf18b3 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -2725,9 +2725,7 @@
         rule.setContent {
             counter.value // just to trigger recomposition
             materialized =
-                currentComposer.materialize(
-                    Modifier.scrollable(state, Orientation.Vertical, NoOpOverscrollEffect)
-                )
+                currentComposer.materialize(Modifier.scrollable(state, Orientation.Vertical, null))
         }
 
         lateinit var first: Modifier
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/StretchOverscrollIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/StretchOverscrollIntegrationTest.kt
index 6ab3b28..5f46241 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/StretchOverscrollIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/StretchOverscrollIntegrationTest.kt
@@ -1111,8 +1111,7 @@
         val state = TestState()
         rule.setContent {
             WithTouchSlop(touchSlop = 0f) {
-                state.overscroll =
-                    ScrollableDefaults.overscrollEffect() as AndroidEdgeEffectOverscrollEffect
+                state.overscroll = rememberOverscrollEffect() as AndroidEdgeEffectOverscrollEffect
                 Box(
                     Modifier.testTag(OverscrollBox)
                         .size(250.dp)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
index 5e516de..ea9887d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
@@ -40,6 +40,7 @@
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -238,7 +239,7 @@
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
         beyondBoundsItemCount = beyondBoundsItemCount,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
@@ -268,7 +269,7 @@
         reverseLayout = reverseLayout,
         userScrollEnabled = userScrollEnabled,
         beyondBoundsItemCount = beyondBoundsItemCount,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
index 8b0cc87..caa3ced 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.TargetedFlingBehavior
 import androidx.compose.foundation.gestures.snapping.SnapPosition
 import androidx.compose.foundation.layout.Box
@@ -33,6 +32,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.lazy.layout.PrefetchScheduler
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -127,9 +127,7 @@
         beyondViewportPageCount: Int = config.beyondViewportPageCount,
         pageSize: () -> PageSize = { PageSize.Fill },
         userScrollEnabled: Boolean = true,
-        overscrollEffect: @Composable () -> OverscrollEffect = {
-            ScrollableDefaults.overscrollEffect()
-        },
+        overscrollEffect: @Composable () -> OverscrollEffect? = { rememberOverscrollEffect() },
         snappingPage: PagerSnapDistance = PagerSnapDistance.atMost(1),
         nestedScrollConnection: NestedScrollConnection = object : NestedScrollConnection {},
         additionalContent: @Composable () -> Unit = {},
@@ -310,7 +308,7 @@
         state: PagerState = rememberPagerState(pageCount = { DefaultPageCount }),
         modifier: Modifier = Modifier,
         userScrollEnabled: Boolean = true,
-        overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
         reverseLayout: Boolean = false,
         contentPadding: PaddingValues = PaddingValues(0.dp),
         beyondViewportPageCount: Int = 0,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
index 7fb6f34..acabc47 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
@@ -32,6 +32,7 @@
 import androidx.compose.foundation.lazy.LazyList
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.verticalScroll
@@ -81,7 +82,7 @@
                 reverseLayout = false,
                 state = rememberLazyListState(),
                 userScrollEnabled = true,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
                 verticalArrangement = Arrangement.Top,
                 horizontalArrangement = Arrangement.Start,
                 verticalAlignment = Alignment.Top,
@@ -127,7 +128,7 @@
                 flingBehavior = flingInspector,
                 state = rememberLazyListState(initialFirstVisibleItemIndex = 8),
                 userScrollEnabled = true,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
                 verticalArrangement = Arrangement.Top,
                 horizontalArrangement = Arrangement.Start,
                 verticalAlignment = Alignment.Top,
@@ -190,7 +191,7 @@
                 reverseLayout = false,
                 state = rememberLazyListState(),
                 userScrollEnabled = true,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
                 verticalArrangement = Arrangement.Top,
                 horizontalArrangement = Arrangement.Start,
                 verticalAlignment = Alignment.Top,
@@ -234,7 +235,7 @@
                 reverseLayout = false,
                 state = lazyListState,
                 userScrollEnabled = true,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
                 verticalArrangement = Arrangement.Top,
                 horizontalArrangement = Arrangement.Start,
                 verticalAlignment = Alignment.Top,
@@ -287,7 +288,7 @@
                 reverseLayout = false,
                 state = lazyListState,
                 userScrollEnabled = true,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
                 verticalArrangement = Arrangement.Top,
                 horizontalArrangement = Arrangement.Start,
                 verticalAlignment = Alignment.Top,
@@ -348,7 +349,7 @@
                 reverseLayout = false,
                 state = lazyListState,
                 userScrollEnabled = true,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
                 verticalArrangement = Arrangement.Top,
                 horizontalArrangement = Arrangement.Start,
                 verticalAlignment = Alignment.Top,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSessionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSessionTest.kt
index 83473b3..df846d6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSessionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSessionTest.kt
@@ -242,6 +242,7 @@
             composeImm = composeImm,
             receiveContentConfiguration = receiveContentConfiguration,
             onImeAction = onImeAction,
+            updateSelectionState = null,
             stylusHandwritingTrigger = null,
             viewConfiguration = null
         )
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt
index a9127f3..4becfb86 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.android.kt
@@ -31,10 +31,9 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalAccessorScope
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.geometry.Offset
@@ -64,17 +63,44 @@
 import androidx.compose.ui.util.fastFirstOrNull
 import kotlin.math.roundToInt
 
-@Composable
-internal actual fun rememberOverscrollEffect(): OverscrollEffect {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-    val config = LocalOverscrollConfiguration.current
-    return if (config != null) {
-        remember(context, density, config) {
-            AndroidEdgeEffectOverscrollEffect(context, density, config)
-        }
+internal actual fun CompositionLocalAccessorScope.defaultOverscrollFactory(): OverscrollFactory? {
+    val context = LocalContext.currentValue
+    val density = LocalDensity.currentValue
+    val config = LocalOverscrollConfiguration.currentValue
+    return if (config == null) {
+        null
     } else {
-        NoOpOverscrollEffect
+        AndroidEdgeEffectOverscrollFactory(context, density, config)
+    }
+}
+
+private class AndroidEdgeEffectOverscrollFactory(
+    private val context: Context,
+    private val density: Density,
+    private val configuration: OverscrollConfiguration
+) : OverscrollFactory {
+    override fun createOverscrollEffect(): OverscrollEffect {
+        return AndroidEdgeEffectOverscrollEffect(context, density, configuration)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as AndroidEdgeEffectOverscrollFactory
+
+        if (context != other.context) return false
+        if (density != other.density) return false
+        if (configuration != other.configuration) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = context.hashCode()
+        result = 31 * result + density.hashCode()
+        result = 31 * result + configuration.hashCode()
+        return result
     }
 }
 
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.android.kt
new file mode 100644
index 0000000..14c0130
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.android.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Represents the colors used for text selection by text and text field components.
+ *
+ * See [LocalAutofillHighlight] to provide new values for this throughout the hierarchy.
+ *
+ * @property autofillHighlightColor the color used to draw the background behind autofilled
+ *   elements.
+ */
+@JvmInline
+actual value class AutofillHighlight actual constructor(actual val autofillHighlightColor: Color) {
+    actual companion object {
+        /** Default color used is framework's "autofilled_highlight" color. */
+        private val DefaultAutofillColor = Color(0x4dffeb3b)
+
+        /** Default instance of [AutofillHighlight]. */
+        actual val Default = AutofillHighlight(DefaultAutofillColor)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
index 9fade05..36b4a88 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
@@ -53,6 +53,7 @@
     imeOptions: ImeOptions,
     receiveContentConfiguration: ReceiveContentConfiguration?,
     onImeAction: ((ImeAction) -> Unit)?,
+    updateSelectionState: (() -> Unit)?,
     stylusHandwritingTrigger: MutableSharedFlow?,
     viewConfiguration: ViewConfiguration?
 ): Nothing {
@@ -62,6 +63,7 @@
         imeOptions = imeOptions,
         receiveContentConfiguration = receiveContentConfiguration,
         onImeAction = onImeAction,
+        updateSelectionState = updateSelectionState,
         composeImm = ComposeInputMethodManager(view),
         stylusHandwritingTrigger = stylusHandwritingTrigger,
         viewConfiguration = viewConfiguration
@@ -75,6 +77,7 @@
     imeOptions: ImeOptions,
     receiveContentConfiguration: ReceiveContentConfiguration?,
     onImeAction: ((ImeAction) -> Unit)?,
+    updateSelectionState: (() -> Unit)?,
     composeImm: ComposeInputMethodManager,
     stylusHandwritingTrigger: MutableSharedFlow?,
     viewConfiguration: ViewConfiguration?
@@ -165,6 +168,7 @@
                             return state.performHandwritingGesture(
                                 gesture,
                                 layoutState,
+                                updateSelectionState,
                                 viewConfiguration
                             )
                         }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
index 3653f27..82c5dd16 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/HandwritingGesture.android.kt
@@ -57,12 +57,15 @@
     internal fun TransformedTextFieldState.performHandwritingGesture(
         handwritingGesture: HandwritingGesture,
         layoutState: TextLayoutState,
+        updateSelectionState: (() -> Unit)?,
         viewConfiguration: ViewConfiguration?
     ): Int {
         return when (handwritingGesture) {
-            is SelectGesture -> performSelectGesture(handwritingGesture, layoutState)
+            is SelectGesture ->
+                performSelectGesture(handwritingGesture, layoutState, updateSelectionState)
             is DeleteGesture -> performDeleteGesture(handwritingGesture, layoutState)
-            is SelectRangeGesture -> performSelectRangeGesture(handwritingGesture, layoutState)
+            is SelectRangeGesture ->
+                performSelectRangeGesture(handwritingGesture, layoutState, updateSelectionState)
             is DeleteRangeGesture -> performDeleteRangeGesture(handwritingGesture, layoutState)
             is JoinOrSplitGesture ->
                 performJoinOrSplitGesture(handwritingGesture, layoutState, viewConfiguration)
@@ -92,7 +95,8 @@
 
     private fun TransformedTextFieldState.performSelectGesture(
         gesture: SelectGesture,
-        layoutState: TextLayoutState
+        layoutState: TextLayoutState,
+        updateSelectionState: (() -> Unit)?,
     ): Int {
         val rangeInTransformedText =
             layoutState
@@ -103,8 +107,8 @@
                 )
                 .apply { if (collapsed) return fallback(gesture) }
 
-        // TODO(332749926) show toolbar after selection.
         selectCharsIn(rangeInTransformedText)
+        updateSelectionState?.invoke()
         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS
     }
 
@@ -159,7 +163,8 @@
 
     private fun TransformedTextFieldState.performSelectRangeGesture(
         gesture: SelectRangeGesture,
-        layoutState: TextLayoutState
+        layoutState: TextLayoutState,
+        updateSelectionState: (() -> Unit)?,
     ): Int {
         val rangeInTransformedText =
             layoutState
@@ -171,8 +176,8 @@
                 )
                 .apply { if (collapsed) return fallback(gesture) }
 
-        // TODO(332749926) show toolbar after selection.
         selectCharsIn(rangeInTransformedText)
+        updateSelectionState?.invoke()
         return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Overscroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Overscroll.kt
index d2a3dc3..aba67bb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Overscroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Overscroll.kt
@@ -17,7 +17,11 @@
 package androidx.compose.foundation
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalAccessorScope
+import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.compositionLocalWithComputedDefaultOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -25,9 +29,8 @@
 
 /**
  * An OverscrollEffect represents a visual effect that displays when the edges of a scrolling
- * container have been reached with a scroll or fling. For the default platform effect that should
- * be used in most cases, see
- * [androidx.compose.foundation.gestures.ScrollableDefaults.overscrollEffect].
+ * container have been reached with a scroll or fling. To create an instance of the default /
+ * currently provided [OverscrollFactory], use [rememberOverscrollEffect].
  *
  * OverscrollEffect conceptually 'decorates' scroll / fling events: consuming some of the delta or
  * velocity before and/or after the event is consumed by the scrolling container. [applyToScroll]
@@ -128,28 +131,56 @@
  * @sample androidx.compose.foundation.samples.OverscrollSample
  * @param overscrollEffect the [OverscrollEffect] to render
  */
-fun Modifier.overscroll(overscrollEffect: OverscrollEffect): Modifier =
-    this.then(overscrollEffect.effectModifier)
+fun Modifier.overscroll(overscrollEffect: OverscrollEffect?): Modifier =
+    this.then(overscrollEffect?.effectModifier ?: Modifier)
 
-@Composable internal expect fun rememberOverscrollEffect(): OverscrollEffect
+/**
+ * Returns a remembered [OverscrollEffect] created from the current value of
+ * [LocalOverscrollFactory]. If [LocalOverscrollFactory] changes, a new [OverscrollEffect] will be
+ * returned. Returns `null` if `null` is provided to [LocalOverscrollFactory].
+ */
+@Composable
+fun rememberOverscrollEffect(): OverscrollEffect? {
+    val overscrollFactory = LocalOverscrollFactory.current ?: return null
+    return remember(overscrollFactory) { overscrollFactory.createOverscrollEffect() }
+}
 
-internal object NoOpOverscrollEffect : OverscrollEffect {
-    override fun applyToScroll(
-        delta: Offset,
-        source: NestedScrollSource,
-        performScroll: (Offset) -> Offset
-    ): Offset = performScroll(delta)
+/**
+ * A factory for creating [OverscrollEffect]s. You can provide a factory instance to
+ * [LocalOverscrollFactory] to globally change the factory, and hence effect, used by components
+ * within the hierarchy.
+ *
+ * See [rememberOverscrollEffect] to remember an [OverscrollEffect] from the current factory
+ * provided to [LocalOverscrollFactory].
+ */
+interface OverscrollFactory {
+    /** Returns a new [OverscrollEffect] instance. */
+    fun createOverscrollEffect(): OverscrollEffect
 
-    override suspend fun applyToFling(
-        velocity: Velocity,
-        performFling: suspend (Velocity) -> Velocity
-    ) {
-        performFling(velocity)
+    /**
+     * Require hashCode() to be implemented. Using a data class is sufficient. Singletons and
+     * instances with no properties may implement this function by returning an arbitrary constant.
+     */
+    override fun hashCode(): Int
+
+    /**
+     * Require equals() to be implemented. Using a data class is sufficient. Singletons may
+     * implement this function with referential equality (`this === other`). Instances with no
+     * properties may implement this function by checking the type of the other object.
+     */
+    override fun equals(other: Any?): Boolean
+}
+
+/**
+ * CompositionLocal that provides an [OverscrollFactory] through the hierarchy. This will be used by
+ * default by scrolling components, so you can provide an [OverscrollFactory] here to override the
+ * overscroll used by components within a hierarchy.
+ *
+ * See [rememberOverscrollEffect] to remember an [OverscrollEffect] from the current provided value.
+ */
+val LocalOverscrollFactory: ProvidableCompositionLocal =
+    compositionLocalWithComputedDefaultOf {
+        defaultOverscrollFactory()
     }
 
-    override val isInProgress: Boolean
-        get() = false
-
-    override val effectModifier: Modifier
-        get() = Modifier
-}
+internal expect fun CompositionLocalAccessorScope.defaultOverscrollFactory(): OverscrollFactory?
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
index 8f3dab9..73b3958 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
@@ -224,7 +224,7 @@
                 enabled = enabled,
                 flingBehavior = flingBehavior,
                 reverseScrolling = reverseScrolling,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
             )
         },
         inspectorInfo =
@@ -302,7 +302,7 @@
                 enabled = enabled,
                 flingBehavior = flingBehavior,
                 reverseScrolling = reverseScrolling,
-                overscrollEffect = ScrollableDefaults.overscrollEffect(),
+                overscrollEffect = rememberOverscrollEffect(),
             )
         },
         inspectorInfo =
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 1ffe0e9..f8388e5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.gestures.Orientation.Vertical
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.relocation.BringIntoViewResponderNode
-import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
@@ -132,8 +131,8 @@
  * draggable, consider using [draggable].
  *
  * This overload provides the access to [OverscrollEffect] that defines the behaviour of the over
- * scrolling logic. Consider using [ScrollableDefaults.overscrollEffect] for the platform
- * look-and-feel.
+ * scrolling logic. Use [androidx.compose.foundation.rememberOverscrollEffect] to create an instance
+ * of the current provided overscroll implementation.
  *
  * @sample androidx.compose.foundation.samples.ScrollableSample
  * @param state [ScrollableState] state of the scrollable. Defines how scroll events will be
@@ -542,15 +541,6 @@
     }
 
     /**
-     * Create and remember default [OverscrollEffect] that will be used for showing over scroll
-     * effects.
-     */
-    @Composable
-    fun overscrollEffect(): OverscrollEffect {
-        return rememberOverscrollEffect()
-    }
-
-    /**
      * Used to determine the value of `reverseDirection` parameter of [Modifier.scrollable] in
      * scrollable layouts.
      *
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index 2871cbc..eb38ebf 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -331,7 +332,7 @@
     verticalAlignment: Alignment.Vertical = Alignment.Top,
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     content: LazyListScope.() -> Unit
 ) {
     LazyList(
@@ -390,7 +391,7 @@
     horizontalAlignment: Alignment.Horizontal = Alignment.Start,
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     content: LazyListScope.() -> Unit
 ) {
     LazyList(
@@ -431,7 +432,7 @@
         horizontalAlignment = horizontalAlignment,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
@@ -485,7 +486,7 @@
         verticalAlignment = verticalAlignment,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
index bff69cc..3554c97 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
@@ -75,7 +76,7 @@
     horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     content: LazyGridScope.() -> Unit
 ) {
     LazyGrid(
@@ -119,7 +120,7 @@
         horizontalArrangement = horizontalArrangement,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
@@ -163,7 +164,7 @@
     verticalArrangement: Arrangement.Vertical = Arrangement.Top,
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     content: LazyGridScope.() -> Unit
 ) {
     LazyGrid(
@@ -207,7 +208,7 @@
         verticalArrangement = verticalArrangement,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
index 7d939d5..b305f00 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
@@ -75,7 +76,7 @@
     horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(0.dp),
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     content: LazyStaggeredGridScope.() -> Unit
 ) {
     LazyStaggeredGrid(
@@ -118,7 +119,7 @@
         horizontalArrangement = horizontalArrangement,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
@@ -199,7 +200,7 @@
     horizontalItemSpacing: Dp = 0.dp,
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     content: LazyStaggeredGridScope.() -> Unit
 ) {
     LazyStaggeredGrid(
@@ -242,7 +243,7 @@
         horizontalItemSpacing = horizontalItemSpacing,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         content = content
     )
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
index cd458cd..4763965 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
@@ -26,7 +26,6 @@
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.TargetedFlingBehavior
 import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
 import androidx.compose.foundation.gestures.snapping.SnapPosition
@@ -34,6 +33,7 @@
 import androidx.compose.foundation.gestures.snapping.snapFlingBehavior
 import androidx.compose.foundation.internal.requirePrecondition
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.rememberOverscrollEffect
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
@@ -124,7 +124,7 @@
     pageNestedScrollConnection: NestedScrollConnection =
         PagerDefaults.pageNestedScrollConnection(state, Orientation.Horizontal),
     snapPosition: SnapPosition = SnapPosition.Start,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
     Pager(
@@ -181,7 +181,7 @@
         key = key,
         pageNestedScrollConnection = pageNestedScrollConnection,
         snapPosition = snapPosition,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         pageContent = pageContent
     )
 }
@@ -253,7 +253,7 @@
     pageNestedScrollConnection: NestedScrollConnection =
         PagerDefaults.pageNestedScrollConnection(state, Orientation.Vertical),
     snapPosition: SnapPosition = SnapPosition.Start,
-    overscrollEffect: OverscrollEffect? = ScrollableDefaults.overscrollEffect(),
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
     Pager(
@@ -310,7 +310,7 @@
         key = key,
         pageNestedScrollConnection = pageNestedScrollConnection,
         snapPosition = snapPosition,
-        overscrollEffect = ScrollableDefaults.overscrollEffect(),
+        overscrollEffect = rememberOverscrollEffect(),
         pageContent = pageContent
     )
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.kt
new file mode 100644
index 0000000..7672491
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.graphics.Color
+import kotlin.jvm.JvmInline
+
+/**
+ * Represents the colors used for text selection by text and text field components.
+ *
+ * See [LocalAutofillHighlight] to provide new values for this throughout the hierarchy.
+ *
+ * @property autofillHighlightColor the color used to draw the background behind autofilled
+ *   elements.
+ */
+@JvmInline
+expect value class AutofillHighlight(val autofillHighlightColor: Color) {
+    companion object {
+        /** Default instance of [AutofillHighlight]. */
+        val Default: AutofillHighlight
+    }
+}
+
+/** CompositionLocal used to change the [AutofillHighlight] used by components in the hierarchy. */
+val LocalAutofillHighlight = compositionLocalOf { AutofillHighlight.Default }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index e806367..8b0621b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -469,6 +469,8 @@
             // semantics. And since we're in a TextField, set the `contentDataType` to be "Text".
             this.contentDataType = ContentDataType.Text
             onAutofillText { text ->
+                state.justAutofilled = true
+                state.autofillHighlightOn = true
                 handleTextUpdateFromSemantics(state, text.text, readOnly, enabled)
                 true
             }
@@ -662,10 +664,19 @@
             true
         }
 
+    val autofillHighlight = LocalAutofillHighlight.current
+    val drawDecorationModifier =
+        Modifier.drawBehind {
+            if (state.autofillHighlightOn || state.justAutofilled) {
+                drawRect(color = autofillHighlight.autofillHighlightColor)
+            }
+        }
+
     // Modifiers that should be applied to the outer text field container. Usually those include
     // gesture and semantics modifiers.
     val decorationBoxModifier =
         modifier
+            .then(drawDecorationModifier)
             .legacyTextInputAdapter(legacyTextInputServiceAdapter, state, manager)
             .then(stylusHandwritingModifier)
             .then(focusModifier)
@@ -1006,6 +1017,10 @@
     private val keyboardActionRunner: KeyboardActionRunner =
         KeyboardActionRunner(keyboardController)
 
+    /** Autofill related values we need to save between */
+    var autofillHighlightOn by mutableStateOf(false)
+    var justAutofilled by mutableStateOf(false)
+
     /**
      * DO NOT USE, use [onValueChange] instead. This is original callback provided to the TextField.
      * In order the CoreTextField to work, the recompose.invalidate() has to be called when we call
@@ -1017,6 +1032,13 @@
         if (it.text != untransformedText?.text) {
             // Text has been changed, enter the HandleState.None and hide the cursor handle.
             handleState = HandleState.None
+
+            // Autofill logic
+            if (justAutofilled) {
+                justAutofilled = false
+            } else {
+                autofillHighlightOn = false
+            }
         }
         selectionPreviewHighlightRange = TextRange.Zero
         deletionPreviewHighlightRange = TextRange.Zero
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
index ece749e..c87663c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
@@ -29,12 +29,17 @@
 import androidx.compose.foundation.text.Handle
 import androidx.compose.foundation.text.KeyboardActionScope
 import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.LocalAutofillHighlight
 import androidx.compose.foundation.text.handwriting.StylusHandwritingNode
 import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
 import androidx.compose.foundation.text.input.InputTransformation
 import androidx.compose.foundation.text.input.KeyboardActionHandler
 import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
 import androidx.compose.foundation.text.input.internal.selection.TextToolbarState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.autofill.ContentDataType
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusEventModifierNode
@@ -42,6 +47,7 @@
 import androidx.compose.ui.focus.FocusRequesterModifierNode
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.focus.requestFocus
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyInputModifierNode
 import androidx.compose.ui.input.pointer.PointerEvent
@@ -51,6 +57,7 @@
 import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.DrawModifierNode
 import androidx.compose.ui.node.GlobalPositionAwareModifierNode
 import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
@@ -101,6 +108,8 @@
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.take
 import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalFoundationApi::class) private val MediaTypesText = setOf(MediaType.Text)
@@ -179,6 +188,7 @@
     var isPassword: Boolean
 ) :
     DelegatingNode(),
+    DrawModifierNode,
     PlatformTextInputModifierNode,
     SemanticsModifierNode,
     FocusRequesterModifierNode,
@@ -413,6 +423,26 @@
         getReceiveContentConfiguration()
     }
 
+    // Mutable state to hold the autofillHighlightOn value
+    private var autofillHighlightOn by mutableStateOf(false)
+
+    override fun ContentDrawScope.draw() {
+        drawContent()
+
+        // Autofill highlight is drawn on top of the content — this way the coloring appears over
+        // any Material background applied.
+        if (autofillHighlightOn) {
+            drawRect(color = currentValueOf(LocalAutofillHighlight).autofillHighlightColor)
+        }
+    }
+
+    private suspend fun observeUntransformedTextChanges() {
+        snapshotFlow { textFieldState.untransformedText.toString() }
+            .drop(1)
+            .take(1)
+            .collect { autofillHighlightOn = false }
+    }
+
     /** Updates all the related properties and invalidates internal state based on the changes. */
     fun updateNode(
         textFieldState: TransformedTextFieldState,
@@ -513,6 +543,8 @@
         onAutofillText { newText ->
             if (!editable) return@onAutofillText false
             textFieldState.replaceAll(newText)
+            autofillHighlightOn = true
+            coroutineScope.launch { observeUntransformedTextChanges() }
             true
         }
 
@@ -740,6 +772,11 @@
                         imeOptions = keyboardOptions.toImeOptions(singleLine),
                         receiveContentConfiguration = receiveContentConfiguration,
                         onImeAction = ::onImeActionPerformed,
+                        updateSelectionState = {
+                            textFieldSelectionState.updateTextToolbarState(
+                                TextToolbarState.Selection
+                            )
+                        },
                         stylusHandwritingTrigger = stylusHandwritingTrigger,
                         viewConfiguration = currentValueOf(LocalViewConfiguration)
                     )
@@ -788,6 +825,7 @@
     imeOptions: ImeOptions,
     receiveContentConfiguration: ReceiveContentConfiguration?,
     onImeAction: ((ImeAction) -> Unit)?,
+    updateSelectionState: (() -> Unit)? = null,
     stylusHandwritingTrigger: MutableSharedFlow? = null,
     viewConfiguration: ViewConfiguration? = null
 ): Nothing
diff --git a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/DesktopOverscroll.commonStubs.kt b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/DesktopOverscroll.commonStubs.kt
index 1f77b7d..fd46e2b 100644
--- a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/DesktopOverscroll.commonStubs.kt
+++ b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/DesktopOverscroll.commonStubs.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation
 
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalAccessorScope
 
-@Composable
-internal actual fun rememberOverscrollEffect(): OverscrollEffect = implementedInJetBrainsFork()
+internal actual fun CompositionLocalAccessorScope.defaultOverscrollFactory(): OverscrollFactory? =
+    implementedInJetBrainsFork()
diff --git a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.commonStubs.kt b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.commonStubs.kt
new file mode 100644
index 0000000..b423b1f
--- /dev/null
+++ b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/text/AutofillHighlight.commonStubs.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import androidx.compose.ui.graphics.Color
+import kotlin.jvm.JvmInline
+
+/**
+ * Represents the colors used for text selection by text and text field components.
+ *
+ * See [LocalAutofillHighlight] to provide new values for this throughout the hierarchy.
+ *
+ * @property autofillHighlightColor the color used to draw the background behind autofilled
+ *   elements.
+ */
+@JvmInline
+actual value class AutofillHighlight actual constructor(actual val autofillHighlightColor: Color) {
+    actual companion object {
+        /** Default instance of [AutofillHighlight]. */
+        actual val Default: AutofillHighlight
+            get() = TODO("Not yet implemented")
+    }
+}
diff --git a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.commonStubs.kt b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.commonStubs.kt
index 7ed3359..a188813 100644
--- a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.commonStubs.kt
+++ b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.commonStubs.kt
@@ -30,6 +30,7 @@
     imeOptions: ImeOptions,
     receiveContentConfiguration: ReceiveContentConfiguration?,
     onImeAction: ((ImeAction) -> Unit)?,
+    updateSelectionState: (() -> Unit)?,
     stylusHandwritingTrigger: MutableSharedFlow?,
     viewConfiguration: ViewConfiguration?
 ): Nothing = implementedInJetBrainsFork()
diff --git a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
index d01e748..96a6a6a 100644
--- a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
+++ b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
@@ -1460,6 +1460,143 @@
         RlEikLgj4eZ2WcKWWJFYlaj8BpHq1rYeAwAA
         """
         )
+
+    val CompositionLocal =
+        bytecodeStub(
+            filename = "CompositionLocal.kt",
+            filepath = "androidx/compose/runtime",
+            checksum = 0xa5bf3022,
+            """
+            package androidx.compose.runtime
+
+            sealed class CompositionLocal constructor(defaultFactory: (() -> T)? = null) {
+                val stubValue: T = defaultFactory!!.invoke()
+
+                inline val current: T
+                    @Composable get() = stubValue
+            }
+
+            abstract class ProvidableCompositionLocal internal constructor(
+                defaultFactory: (() -> T)?
+            ) : CompositionLocal(defaultFactory)
+
+            internal class DynamicProvidableCompositionLocal constructor(
+                defaultFactory: (() -> T)?
+            ) : ProvidableCompositionLocal(defaultFactory)
+
+            internal class StaticProvidableCompositionLocal(
+                defaultFactory: (() -> T)?
+            ) : ProvidableCompositionLocal(defaultFactory)
+
+            fun  compositionLocalOf(
+                defaultFactory: (() -> T)? = null
+            ): ProvidableCompositionLocal = DynamicProvidableCompositionLocal(defaultFactory)
+
+            fun  staticCompositionLocalOf(
+                defaultFactory: (() -> T)? = null
+            ): ProvidableCompositionLocal = StaticProvidableCompositionLocal(defaultFactory)
+        """,
+            """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijg0uOSSMxLKcrPTKnQS87PLcgvTtUr
+                Ks0rycxNFRJyBgtklmTm5/nkJyfmeJdw6XPJYKgvzdQryEksScsvyhXid4TI
+                gtUXAzWIcnED1emlViTmFuSkCrGFpBaXeJcoMWgxAACzjsPdkAAAAA==
+                """,
+            """
+                androidx/compose/runtime/CompositionLocal.class:
+                H4sIAAAAAAAA/5VUXW/bVBh+juPEiZu2brKOfmylZdnIBzRZ2UZZQseWUQhK
+                O0ijSKgXyHXc7jSJPfnY0biZKm74DdzyC5gEFLhA1bjjR017j5N2XVsUJkvn
+                nPfxe573eT/sf1/+9TeAW/iKIWc6bc/l7adFy+09cYVd9ALH5z27WA1t7nPX
+                qbuW2dXAGLKV5t36vtk3i13T2Ss+2tm3Lb+8dh5iMM5iGlSGWIU73F9juJat
+                d1y/y53ifr9X3A0cS0YSxfXhqVTOtUjeKK9Kodksr4W+kWyulUQMCR1R6AwL
+                p25yx7c9x+wWa47vcUdwS2hIMiStx7bV2XT9zaDbZZjOns9Ekk5gUsc4jDdJ
+                L5CjIUU5cqfvdmyGS9nceb4kLmF6DGlcZkgIP9hpmd2AnFMXuc5gNgEFcwyq
+                /5gLhkL9fzeMejDRtnfNoOuvm5bvet8zLI4qOkNjZGNq9Ysq+3AQqkq+vhfI
+                eBum17G9sDffjCR9W07Z6SWdavMedXHP9rdeVzKazdFQMOgEVwPPsx2f4fqo
+                wpk7XZsqnsG0ZM0yTGZ4ZjdzmoPVZFRNvv+AIZ2xXuv6rhcKY1h+u0RobEOt
+                U8fXNmzfbJu+SZjS60foO2VyScgFJKFD+FMurRKd2jcZnh0dzOjKjKIfHeiK
+                QYvcyVTimtzjd2aODvJq/OjAYCtKSXkwG1dTRlwxInN6Sk0pq3SdldQXP8cU
+                I9pIGbE5Ca3+86NCUGxOjWtGvJE2EiFMkE5BxgjWjfiLHxhLShUrJKzJpL7U
+                cR6nR0qzjit4+7+78LXn9nlbduGCQU6fxZY7xJbY4nuO6QceNV2tum3aJuvc
+                sTeD3o7tNSWXVCT9W6bHpT0Ex7d80+psmE+G9nxjoKLm9LngBN13HNc3wyGl
+                QdpyA8+y17l0nR26ts45qks0GNGwTSn55dL+gCwFJUIVOTm0Vgl5hAidgHT+
+                EGP5wrd/YOpPvMPwG+afhxce0joB2WSVnijRqficrMuDa7iCq3Ic6LSAd0/C
+                xOn/FMMi2dXw9x6hW8DkFfXZT4iyer7ADnFtEGCd1ghYPIykh6wqRUxgSn4A
+                Q5lFOW+0R/O/Yv6XE2GxARgKSg7PA0EDGdeJ5AaRREKSMnlIei1Syf+O3Js0
+                pPkkL1m6PAohoXaG8Iuh//tAmOPSMMdVeiurFMsXDvHh87D0km9xgJ7UKTas
+                kzwtU17y1oA7gi/D/T5qtDfI5ybpWNlGpIaParhVw23coSM+rlG0T7bBBO6i
+                vI1xgasCFYFPBRYE1gQ0gXsCNwSmBWYEMgKzAp+9AtxYnCVtBwAA
+                """,
+            """
+                androidx/compose/runtime/CompositionLocalKt.class:
+                H4sIAAAAAAAA/51UW08TQRT+ZntfCpSi0gsoQlUuyhYQVKgkBkNsKJdIgzE8
+                TbcLmV52ye62gRfD3/Bf+KbRxPTZH2U8sy1RiwXTlzNn5nzznW/PObM/fn79
+                DuApnjPMc7NsW6J8pulW/dRyDM1umK6oG9qmtxeusMyCpfPathsCY4hVeJNr
+                NW6eaHuliqHTqY8hrneh944Z3s0UqpZbE6ZWada144apy7CjbXW87PpsoWf6
+                fdtqijIv1YxuIesMZ7niWqFbyPrGTfly88Xi+kZ/WXPeVYbpgmWfaBXDLdlc
+                EDk3Tcvl7US7lrvbqEmBmetQBJEZCLbWU8jrc5PXhd5bTwgqQzAnTOFukKqb
+                S30YRRSDKgYwxLDcRwVCiDEMlY1j3qi5W1x3LfucYfKmxAypq8OR6dAwVG5U
+                nr/a6f7mJooAgioU3GFIOLIdejdGju2LntwH3p3rKpRUkZL1nexFf/nhUSTa
+                WiYYRi4rsGO4vMxdTjVT6k0fvVEmTUQaMLCqdBQKngnpZckrLzKUWxcxtXWh
+                KglFVcKKt7YuUpkYmXDcH1feKFk25Q8TTFmKhZWYL6W2jxMs6ydc4D9gMtcS
+                w0qfL5YV6Sdx+Z1/DsdoN3ihSlPh37TKBsNwQZjGbqNeMuyiJJYcEnPIbSH3
+                ncPIgTgxuduwyU+/bcvJm03hCAq/+v306F12R/e5zeuGa9h/wQap03p1h592
+                EqgHVsPWjS0hN8kOx+EVfixSQ/2yWWSTctpo1Wi3Ch/1D4h+w8D7uc8YbmHk
+                k+wlsmSDXixJtwnRxiGOUVqXPEwIyx1U2PttAyEqKCJALIJbuE1+O4kCORaD
+                af+Hjwiw7bkvGGtnWSFLCsJeuiEPNUaECSIco8SJfwlNSaHpfwid6E/o+LVC
+                7/YUmibCcSJMU3jVAy3gGa0vie0e1XjyCL487ucxlcc0Mnk8wMM8HmHmCMzB
+                LOaOEHQQcDDv4LGDuIMnDhK/AIiJIGwEBwAA
+                """,
+            """
+                androidx/compose/runtime/DynamicProvidableCompositionLocal.class:
+                H4sIAAAAAAAA/51S308TQRD+9lpaOKG0RRDwByhoBBKvoCaG1iaKITSp2EjT
+                F562dwtuudszd3sNvPVv8T/wycQH0/joH2WcbUtEE9KEl5lvZr/5dmZ2f/3+
+                /gPACzxh2OXKi0LpnTtuGHwOY+FEidIyEM67C8UD6TaisCs93vbF3oAgtQxV
+                PXS5nwVjaFSau/UO73LH5+rU+dDuCFeXq/VrZa/XqzSb5WqZ4fkNarNIM2Qq
+                UkldZVh7Wj8LtS+V0+kGzkmiXEOMnf0RKpU3Wgwb41iVrUFHhrteD6NTpyN0
+                O+KSOFypUPMh/zDxfdNTeRoZZG1MwGZI608yZqhcv4ix+6VV5DxxwhNf73NX
+                h9EFw+q4wRgKl5T3QnOPa045K+im6MWZMVPGgIGdUf5cmqhEyNtmOOj3ira1
+                aNn93r9ust9b7Pc20+TzbGeymC5aB6xkvZ0v5vKpZdvEr4jCSumfXzJWfsLo
+                7dAVTYaXN/kK1HLxcoyrs839T3x2pmnXe6EnGGbrUonDJGiLqGlEjYbhtHgk
+                TTxKTh3JU8V1EhFe/zhspaa6MpZ03OARD4QW0Zu/D8xgH4VJ5Ip9aeqXRjWt
+                YcUVIrZh0euP1ms+A1JYoahKeYt8ZnPrG259JWRhlaw9yBaoZhYPCS0MWZjG
+                zEAlgxydMDwaVExijXzWSE8RSI3SKawP/AM8Jv+aTvMkWDhGqoZiDXM13MY8
+                QSzUcAeLx2AxlrB8jEyMmRh3Y9yLkYtxP0b2D5OoO78aBAAA
+                """,
+            """
+                androidx/compose/runtime/ProvidableCompositionLocal.class:
+                H4sIAAAAAAAA/41SXU9TQRA9e1vacsFSikLBLxBEgcRbURNjK0YxjTUFURpe
+                eNr2Lrjt7V6zd2+Db/0t/gOfTHwwjY/+KONsWyKSEHzZmT1z5szszP76/f0H
+                gMe4x/CIK1+H0j/xmmHnUxgJT8fKyI7w9nTYlT5vBGJ7EJFGhqoWNnmQBmOo
+                lOvPai3e5V7A1bH3rtESTVPaql2od16lXK+XtkoMa/+dkUaSIVWWSpothuX7
+                tXZoAqm8VrfjHcWqaYmRVxl5xdLaAalfxipvDPqw3JVaqI+9ljANzSVxuFKh
+                4UP+bhwEdhbU8PtLC5+NS2WEVjzwXosjHgdmm6hGx00T6h2u20JT6Umk4LoY
+                wwRD0nyUEcOTiwd58WKouaw/LFPhtsJnhsXLmmWYPqXsCMN9bjhhTqeboD/C
+                7DFuDzCwNuEn0t6K5PkPGd72e3nXKThuv/evyawW+r31ZKbfy7HNTD6Zd96w
+                ovNqjoB8NpdYcC30tN8rsGLy55eUkxuziptUpM6w8f+/iFrNn7Z/9k0z54kP
+                2oaGux36gmGqJpXYjTsNoet2jlbDcg64lvY+Asf35bHiJtbkr3wYNlBVXRlJ
+                Cu9xzTuCdvvy7y9hcPfDWDdFRdr8+VHOwTDjDDG5BIfWPRorbT+NBBbp9oKs
+                Qza9vsG+YfIruQ6W6HQH8BWiTuAOebNDGiHZgUwaU8iR1PIgI4MVi1ntcXIS
+                IziBuwN7G6tkn1N0mrrIHyJRxUwVV6u4hllyMVdFAfOHYBEWcP0QqQjZCDci
+                3IwwFeFWhPQfROtXvEUEAAA=
+                """,
+            """
+                androidx/compose/runtime/StaticProvidableCompositionLocal.class:
+                H4sIAAAAAAAA/51SXW8SQRQ9s1A+1pZSkNrWj1aLxraJS6smKkiiTZqSYG2E
+                8MLTsDvFgWXX7M6S+sZv8R/4ZOKDIT76o4x3gMZqQpr05d5z75x75t478+v3
+                9x8AnuERw0vuOYEvnXPL9gef/FBYQeQpORBWQ3El7dPAH0qHd1xxODmXSvpe
+                3be5mwRjOK00X9V7fMgtl3td632nJ2xVrtbnqs7XqzSb5WqZ4ek1apOIMyQq
+                0pOqyrD9uN73lSs9qzccWGeRZ2tiaB3NUKm802LYuYpV2Zt0pLnFuh90rZ5Q
+                nYBL4nDP8/V6NP8kcl3dU3kRCSRNLMBkiKuPMmQoz1/EVeulTWQcccYjVx1x
+                W/nBZ4atq+ZiWLmgvBOKO1xxyhmDYYzem2mT1gYMrE/5c6mjEiFnn+F4PMqZ
+                xpphjkf/utR4tDYe7cbJZ9lBKhfPGcesZLwt5DLZ2Iap4xdEYaX4zy8JI7ug
+                9Q7oiibD8+v8BGo5dzHG5dny/xOf9BWt+tB3BMNyXXriJBp0RNDUolpDc1o8
+                kDqeJdMN2fW4igLCxQ/TVmreUIaSjk95wAdCieDN3/dlMBt+FNjiSOr69VlN
+                a1pxiYh9GPT4s/Xqv4AYNimqUt4gn9jd+4YbXwkZ2CJrTrIFqsnjPqHVKQuL
+                WJqoJJDBMik9mFSksE0+qaXTBGKzdAzFib+Hh+Rf02mWBFfaiNWQqyFfw00U
+                CGK1hltYa4OFWMdGG4kQSyFuh7gTIhPibojkH2XEsYYYBAAA
+                """
+        )
 }
 
 /**
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ObservableThemeTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ObservableThemeTest.kt
index 8b95a4d..f808913 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ObservableThemeTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ObservableThemeTest.kt
@@ -195,7 +195,7 @@
  * Immutable as we want to ensure that we always skip recomposition unless the CompositionLocal
  * value inside the function changes.
  */
-@Immutable private class CompositionTracker(var compositions: Int = 0)
+@Immutable internal class CompositionTracker(var compositions: Int = 0)
 
 private val LocalTestTheme =
     staticCompositionLocalOf { error("CompositionLocal LocalTestThemem not present") }
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ScaffoldTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ScaffoldTest.kt
index e9245e3..a3d4e13 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ScaffoldTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/ScaffoldTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material
 
 import android.os.Build
+import androidx.activity.ComponentActivity
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
@@ -25,6 +26,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.windowInsetsPadding
@@ -34,6 +36,11 @@
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.assertNoPendingChanges
+import androidx.compose.testutils.doFramesUntilNoChangesPending
+import androidx.compose.testutils.forGivenTestCase
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.geometry.Offset
@@ -53,7 +60,7 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeLeft
@@ -61,6 +68,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
@@ -71,6 +79,7 @@
 import com.google.common.truth.Truth.assertWithMessage
 import kotlin.math.roundToInt
 import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -80,7 +89,7 @@
 @RunWith(AndroidJUnit4::class)
 class ScaffoldTest {
 
-    @get:Rule val rule = createComposeRule()
+    @get:Rule val rule = createAndroidComposeRule()
 
     private val fabSpacing = 16.dp
     private val scaffoldTag = "Scaffold"
@@ -719,9 +728,68 @@
         assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
     }
 
+    // Regression test for b/373904168
+    @Test
+    fun scaffold_topBarHeightChanging_noRecompositionInBody() {
+        val testCase = TopBarHeightChangingScaffoldTestCase()
+        rule.forGivenTestCase(testCase).performTestWithEventsControl {
+            doFrame()
+            assertNoPendingChanges()
+
+            assertEquals(1, testCase.tracker.compositions)
+
+            testCase.toggleState()
+
+            doFramesUntilNoChangesPending(maxAmountOfFrames = 1)
+
+            assertEquals(1, testCase.tracker.compositions)
+        }
+    }
+
     private fun assertDpIsWithinThreshold(actual: Dp, expected: Dp, threshold: Dp) {
         assertThat(actual.value).isWithin(threshold.value).of(expected.value)
     }
 
     private val roundingError = 0.5.dp
 }
+
+private class TopBarHeightChangingScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+
+    private lateinit var state: MutableState
+
+    val tracker = CompositionTracker()
+
+    @Composable
+    override fun MeasuredContent() {
+        state = remember { mutableStateOf(0.dp) }
+        val paddingValues = remember {
+            object : PaddingValues {
+                override fun calculateBottomPadding(): Dp = state.value
+
+                override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = 0.dp
+
+                override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = 0.dp
+
+                override fun calculateTopPadding(): Dp = 0.dp
+            }
+        }
+
+        Scaffold(
+            topBar = {
+                TopAppBar(title = { Text("Title") }, modifier = Modifier.padding(paddingValues))
+            },
+        ) { contentPadding ->
+            tracker.compositions++
+            Box(Modifier.padding(contentPadding).fillMaxSize())
+        }
+    }
+
+    @Composable
+    override fun ContentWrappers(content: @Composable () -> Unit) {
+        MaterialTheme { content() }
+    }
+
+    override fun toggleState() {
+        state.value = if (state.value == 0.dp) 10.dp else 0.dp
+    }
+}
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SlideUsingKeysTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SlideUsingKeysTest.kt
new file mode 100644
index 0000000..d4a5f00
--- /dev/null
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SlideUsingKeysTest.kt
@@ -0,0 +1,269 @@
+/*
+ * 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.material
+
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.NativeKeyEvent
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.test.runComposeUiTest
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import kotlin.math.roundToInt
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class SlideUsingKeysTest {
+
+    @Test
+    fun slider_ltr_0steps_change_using_keys() = runComposeUiTest {
+        val state = mutableStateOf(0.5f)
+        var sliderFocused = false
+
+        setContent {
+            Slider(
+                value = state.value,
+                onValueChange = { state.value = it },
+                valueRange = 0f..1f,
+                modifier = Modifier.onFocusChanged { sliderFocused = it.isFocused }
+            )
+        }
+
+        // Press tab to focus on Slider
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyUp))
+        runOnIdle { assertTrue(sliderFocused) }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f + (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.53f - (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f + (1 + it) / 10f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.80f - (1 + it) / 10f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f + (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.53f - (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(1f, state.value) }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(0f, state.value) }
+    }
+
+    @Test
+    fun slider_rtl_0steps_change_using_keys() = runComposeUiTest {
+        val state = mutableStateOf(0.5f)
+        var sliderFocused = false
+        setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                Slider(
+                    value = state.value,
+                    onValueChange = { state.value = it },
+                    valueRange = 0f..1f,
+                    modifier = Modifier.onFocusChanged { sliderFocused = it.isFocused }
+                )
+            }
+        }
+
+        // Press tab to focus on Slider
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyUp))
+        runOnIdle { assertTrue(sliderFocused) }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f - (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.47f + (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+    }
+
+    @Test
+    fun slider_ltr_29steps_using_keys() = runComposeUiTest {
+        val state = mutableStateOf(15f)
+        var sliderFocused = false
+        setContent {
+            Slider(
+                value = state.value,
+                steps = 29,
+                onValueChange = { state.value = it },
+                valueRange = 0f..30f,
+                modifier = Modifier.onFocusChanged { sliderFocused = it.isFocused }
+            )
+        }
+
+        // Press tab to focus on Slider
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyUp))
+        runOnIdle { assertTrue(sliderFocused) }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyUp))
+            runOnIdle { assertEquals((15f + (1f + it)), (state.value)) }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyUp))
+            runOnIdle { assertEquals((18f - (1 + it)), state.value) }
+        }
+
+        runOnIdle { state.value = 0f }
+
+        val page = ((29 + 1) / 10).coerceIn(1, 10) // same logic as in Slider slideOnKeyEvents
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyUp))
+            runOnIdle { assertEquals((1f + it) * page, state.value) }
+        }
+
+        runOnIdle { state.value = 30f }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyUp))
+            runOnIdle { assertEquals(30f - (1 + it) * page, state.value) }
+        }
+
+        runOnIdle { state.value = 0f }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyUp))
+            runOnIdle { assertEquals(1f + it, state.value) }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyUp))
+            runOnIdle { assertEquals(3f - (1f + it), state.value) }
+        }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(30f, state.value) }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(0f, state.value) }
+    }
+}
+
+private fun KeyEventType.toNativeAction(): Int {
+    return when (this) {
+        KeyEventType.KeyUp -> NativeKeyEvent.ACTION_UP
+        KeyEventType.KeyDown -> NativeKeyEvent.ACTION_DOWN
+        else -> error("KeyEventType - $this")
+    }
+}
+
+private fun KeyEvent(key: Key, type: KeyEventType): KeyEvent {
+    return KeyEvent(NativeKeyEvent(type.toNativeAction(), key.nativeKeyCode))
+}
+
+private fun Float.round2decPlaces() = (this * 100).roundToInt() / 100f
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
index b7b30dc..ff6395f 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
@@ -29,7 +29,10 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.UiComposable
@@ -374,6 +377,35 @@
     contentWindowInsets: WindowInsets,
     bottomBar: @Composable @UiComposable () -> Unit
 ) {
+    // Create the backing value for the content padding
+    // These values will be updated during measurement, but before subcomposing the body content
+    // Remembering and updating a single PaddingValues avoids needing to recompose when the values
+    // change
+    val contentPadding = remember {
+        object : PaddingValues {
+            var topContentPadding by mutableStateOf(0.dp)
+            var startContentPadding by mutableStateOf(0.dp)
+            var endContentPadding by mutableStateOf(0.dp)
+            var bottomContentPadding by mutableStateOf(0.dp)
+
+            override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp =
+                when (layoutDirection) {
+                    LayoutDirection.Ltr -> startContentPadding
+                    LayoutDirection.Rtl -> endContentPadding
+                }
+
+            override fun calculateTopPadding(): Dp = topContentPadding
+
+            override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp =
+                when (layoutDirection) {
+                    LayoutDirection.Ltr -> endContentPadding
+                    LayoutDirection.Rtl -> startContentPadding
+                }
+
+            override fun calculateBottomPadding(): Dp = bottomContentPadding
+        }
+    }
+
     SubcomposeLayout { constraints ->
         val layoutWidth = constraints.maxWidth
         val layoutHeight = constraints.maxHeight
@@ -486,34 +518,27 @@
                 0
             }
 
+        // Update the backing state for the content padding before subcomposing the body
+        val insets = contentWindowInsets.asPaddingValues(this)
+        contentPadding.topContentPadding =
+            if (topBarPlaceables.isEmpty()) {
+                insets.calculateTopPadding()
+            } else {
+                0.dp
+            }
+        contentPadding.bottomContentPadding =
+            if (bottomBarPlaceables.isEmpty() || bottomBarHeight == null) {
+                insets.calculateBottomPadding()
+            } else {
+                bottomBarHeight.toDp()
+            }
+        contentPadding.startContentPadding = insets.calculateStartPadding(layoutDirection)
+        contentPadding.endContentPadding = insets.calculateEndPadding(layoutDirection)
+
         val bodyContentHeight = layoutHeight - topBarHeight
 
         val bodyContentPlaceables =
-            subcompose(ScaffoldLayoutContent.MainContent) {
-                    val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
-                    val innerPadding =
-                        PaddingValues(
-                            top =
-                                if (topBarPlaceables.isEmpty()) {
-                                    insets.calculateTopPadding()
-                                } else {
-                                    0.dp
-                                },
-                            bottom =
-                                if (bottomBarPlaceables.isEmpty() || bottomBarHeight == null) {
-                                    insets.calculateBottomPadding()
-                                } else {
-                                    bottomBarHeight.toDp()
-                                },
-                            start =
-                                insets.calculateStartPadding(
-                                    (this@SubcomposeLayout).layoutDirection
-                                ),
-                            end =
-                                insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection)
-                        )
-                    content(innerPadding)
-                }
+            subcompose(ScaffoldLayoutContent.MainContent) { content(contentPadding) }
                 .fastMap { it.measure(looseConstraints.copy(maxHeight = bodyContentHeight)) }
 
         layout(layoutWidth, layoutHeight) {
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 fd1af73..aa58990 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
@@ -76,6 +76,11 @@
 import androidx.compose.ui.graphics.PointMode
 import androidx.compose.ui.graphics.StrokeCap
 import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.type
 import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
@@ -162,7 +167,9 @@
     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
     require(steps >= 0) { "steps should be >= 0" }
     val onValueChangeState = rememberUpdatedState(onValueChange)
+    val onValueChangeFinishedState = rememberUpdatedState(onValueChangeFinished)
     val tickFractions = remember(steps) { stepsToTickFractions(steps) }
+
     BoxWithConstraints(
         modifier
             .minimumInteractiveComponentSize()
@@ -176,6 +183,15 @@
                 steps
             )
             .focusable(enabled, interactionSource)
+            .slideOnKeyEvents(
+                enabled,
+                steps,
+                valueRange,
+                value,
+                LocalLayoutDirection.current == LayoutDirection.Rtl,
+                onValueChangeState,
+                onValueChangeFinishedState
+            )
     ) {
         val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
         val widthPx = constraints.maxWidth.toFloat()
@@ -260,6 +276,88 @@
     }
 }
 
+private fun Modifier.slideOnKeyEvents(
+    enabled: Boolean,
+    steps: Int,
+    valueRange: ClosedFloatingPointRange,
+    value: Float,
+    isRtl: Boolean,
+    onValueChangeState: State<(Float) -> Unit>,
+    onValueChangeFinishedState: State<(() -> Unit)?>
+): Modifier {
+    require(steps >= 0) { "steps should be >= 0" }
+    return this.onKeyEvent {
+        if (!enabled) return@onKeyEvent false
+        when (it.type) {
+            KeyEventType.KeyDown -> {
+                val rangeLength = abs(valueRange.endInclusive - valueRange.start)
+                // When steps == 0, it means that a user is not limited by a step length (delta)
+                // when using touch or mouse. But it is not possible to adjust the value
+                // continuously when using keyboard buttons - the delta has to be discrete.
+                // In this case, 1% of the valueRange seems to make sense.
+                val actualSteps = if (steps > 0) steps + 1 else 100
+                val delta = rangeLength / actualSteps
+                when (it.key) {
+                    Key.DirectionUp -> {
+                        onValueChangeState.value((value + delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.DirectionDown -> {
+                        onValueChangeState.value((value - delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.DirectionRight -> {
+                        val sign = if (isRtl) -1 else 1
+                        onValueChangeState.value((value + sign * delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.DirectionLeft -> {
+                        val sign = if (isRtl) -1 else 1
+                        onValueChangeState.value((value - sign * delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.MoveHome -> {
+                        onValueChangeState.value(valueRange.start)
+                        true
+                    }
+                    Key.MoveEnd -> {
+                        onValueChangeState.value(valueRange.endInclusive)
+                        true
+                    }
+                    Key.PageUp -> {
+                        val page = (actualSteps / 10).coerceIn(1, 10)
+                        onValueChangeState.value((value - page * delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.PageDown -> {
+                        val page = (actualSteps / 10).coerceIn(1, 10)
+                        onValueChangeState.value((value + page * delta).coerceIn(valueRange))
+                        true
+                    }
+                    else -> false
+                }
+            }
+            KeyEventType.KeyUp -> {
+                when (it.key) {
+                    Key.DirectionUp,
+                    Key.DirectionDown,
+                    Key.DirectionRight,
+                    Key.DirectionLeft,
+                    Key.MoveHome,
+                    Key.MoveEnd,
+                    Key.PageUp,
+                    Key.PageDown -> {
+                        onValueChangeFinishedState.value?.invoke()
+                        true
+                    }
+                    else -> false
+                }
+            }
+            else -> false
+        }
+    }
+}
+
 /**
  * Material Design
  * slider.
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 3df7cd3..a43c6e0 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -271,6 +271,7 @@
 
   public final class ButtonShapes {
     ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.ButtonShapes copy(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
     method public androidx.compose.ui.graphics.Shape getCheckedShape();
     method public androidx.compose.ui.graphics.Shape getPressedShape();
     method public androidx.compose.ui.graphics.Shape getShape();
@@ -2624,25 +2625,17 @@
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors elevatedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getCheckedShape();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getElevatedPressedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
     method public float getIconSize();
     method public float getIconSpacing();
     method public float getMinHeight();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getOutlinedPressedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
-    method public androidx.compose.ui.graphics.Shape getPressedShape();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRoundShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getTonalPressedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonShapes shapes();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonShapes shapes(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors tonalToggleButtonColors();
@@ -2652,19 +2645,10 @@
     property public final float IconSpacing;
     property public final float MinHeight;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape checkedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape elevatedPressedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape outlinedPressedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
-    property public final androidx.compose.ui.graphics.Shape pressedShape;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape pressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape roundShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape tonalPressedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalShape;
     field public static final androidx.compose.material3.ToggleButtonDefaults INSTANCE;
   }
 
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 3df7cd3..a43c6e0 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -271,6 +271,7 @@
 
   public final class ButtonShapes {
     ctor public ButtonShapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method public androidx.compose.material3.ButtonShapes copy(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
     method public androidx.compose.ui.graphics.Shape getCheckedShape();
     method public androidx.compose.ui.graphics.Shape getPressedShape();
     method public androidx.compose.ui.graphics.Shape getShape();
@@ -2624,25 +2625,17 @@
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors elevatedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getCheckedShape();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getElevatedPressedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getElevatedShape();
     method public float getIconSize();
     method public float getIconSpacing();
     method public float getMinHeight();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getOutlinedPressedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getOutlinedShape();
-    method public androidx.compose.ui.graphics.Shape getPressedShape();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPressedShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRoundShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getSquareShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalCheckedShape();
-    method public androidx.compose.ui.graphics.Shape getTonalPressedShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getTonalShape();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors outlinedToggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
-    method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonShapes shapes(androidx.compose.ui.graphics.Shape shape, androidx.compose.ui.graphics.Shape pressedShape, androidx.compose.ui.graphics.Shape checkedShape);
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonShapes shapes();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.ButtonShapes shapes(optional androidx.compose.ui.graphics.Shape? shape, optional androidx.compose.ui.graphics.Shape? pressedShape, optional androidx.compose.ui.graphics.Shape? checkedShape);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors toggleButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long checkedContainerColor, optional long checkedContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ToggleButtonColors tonalToggleButtonColors();
@@ -2652,19 +2645,10 @@
     property public final float IconSpacing;
     property public final float MinHeight;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape checkedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape elevatedPressedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape elevatedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape outlinedPressedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape outlinedShape;
-    property public final androidx.compose.ui.graphics.Shape pressedShape;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape pressedShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape roundShape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape squareShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalCheckedShape;
-    property public final androidx.compose.ui.graphics.Shape tonalPressedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape tonalShape;
     field public static final androidx.compose.material3.ToggleButtonDefaults INSTANCE;
   }
 
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt
index bd9ed28..d9aa02d 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ButtonGroupSamples.kt
@@ -87,10 +87,10 @@
             checkedShape = ToggleButtonDefaults.checkedShape
         )
     val middleButtonShapes =
-        ToggleButtonDefaults.shapes(
-            ShapeDefaults.Small,
-            ToggleButtonDefaults.pressedShape,
-            ToggleButtonDefaults.checkedShape
+        ButtonShapes(
+            shape = ShapeDefaults.Small,
+            pressedShape = ToggleButtonDefaults.pressedShape,
+            checkedShape = ToggleButtonDefaults.checkedShape
         )
     val endButtonShapes =
         ButtonShapes(
@@ -138,10 +138,10 @@
             checkedShape = ToggleButtonDefaults.checkedShape
         )
     val middleButtonShapes =
-        ToggleButtonDefaults.shapes(
-            ShapeDefaults.Small,
-            ToggleButtonDefaults.pressedShape,
-            ToggleButtonDefaults.checkedShape
+        ButtonShapes(
+            shape = ShapeDefaults.Small,
+            pressedShape = ToggleButtonDefaults.pressedShape,
+            checkedShape = ToggleButtonDefaults.checkedShape
         )
     val endButtonShapes =
         ButtonShapes(
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
index 53dc2b9..c7b6002 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material3
 
 import android.os.Build
+import androidx.activity.ComponentActivity
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
@@ -24,10 +25,21 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.assertNoPendingChanges
+import androidx.compose.testutils.doFramesUntilNoChangesPending
+import androidx.compose.testutils.forGivenTestCase
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.geometry.Offset
@@ -47,11 +59,12 @@
 import androidx.compose.ui.test.assertHeightIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.captureToImage
-import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
@@ -61,6 +74,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlin.math.roundToInt
+import org.junit.Assert.assertEquals
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,7 +83,7 @@
 @RunWith(AndroidJUnit4::class)
 class ScaffoldTest {
 
-    @get:Rule val rule = createComposeRule()
+    @get:Rule val rule = createAndroidComposeRule()
 
     private val scaffoldTag = "Scaffold"
     private val roundingError = 0.5.dp
@@ -593,7 +607,73 @@
         assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
     }
 
+    // Regression test for b/373904168
+    @Test
+    fun scaffold_topBarHeightChanging_noRecompositionInBody() {
+        val testCase = TopBarHeightChangingScaffoldTestCase()
+        rule.forGivenTestCase(testCase).performTestWithEventsControl {
+            doFrame()
+            assertNoPendingChanges()
+
+            assertEquals(1, testCase.tracker.compositions)
+
+            testCase.toggleState()
+
+            doFramesUntilNoChangesPending(maxAmountOfFrames = 1)
+
+            assertEquals(1, testCase.tracker.compositions)
+        }
+    }
+
     private fun assertDpIsWithinThreshold(actual: Dp, expected: Dp, threshold: Dp) {
         assertThat(actual.value).isWithin(threshold.value).of(expected.value)
     }
 }
+
+private class TopBarHeightChangingScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+
+    private lateinit var state: MutableState
+
+    val tracker = CompositionTracker()
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    override fun MeasuredContent() {
+        state = remember { mutableStateOf(0.dp) }
+        val paddingValues = remember {
+            object : PaddingValues {
+                override fun calculateBottomPadding(): Dp = state.value
+
+                override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = 0.dp
+
+                override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = 0.dp
+
+                override fun calculateTopPadding(): Dp = 0.dp
+            }
+        }
+
+        Scaffold(
+            topBar = {
+                TopAppBar(title = { Text("Title") }, modifier = Modifier.padding(paddingValues))
+            },
+        ) { contentPadding ->
+            tracker.compositions++
+            Box(Modifier.padding(contentPadding).fillMaxSize())
+        }
+    }
+
+    @Composable
+    override fun ContentWrappers(content: @Composable () -> Unit) {
+        MaterialTheme { content() }
+    }
+
+    override fun toggleState() {
+        state.value = if (state.value == 0.dp) 10.dp else 0.dp
+    }
+}
+
+/**
+ * Immutable as we want to ensure that we always skip recomposition unless the CompositionLocal
+ * value inside the function changes.
+ */
+@Immutable private class CompositionTracker(var compositions: Int = 0)
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SlideUsingKeysTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SlideUsingKeysTest.kt
new file mode 100644
index 0000000..e97de56
--- /dev/null
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SlideUsingKeysTest.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.NativeKeyEvent
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.test.runComposeUiTest
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import kotlin.math.roundToInt
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class SlideUsingKeysTest {
+
+    @Test
+    fun slider_ltr_0steps_change_using_keys() = runComposeUiTest {
+        val state = mutableStateOf(0.5f)
+        var sliderFocused = false
+
+        setContent {
+            Slider(
+                value = state.value,
+                onValueChange = { state.value = it },
+                valueRange = 0f..1f,
+                modifier = Modifier.onFocusChanged { sliderFocused = it.isFocused }
+            )
+        }
+
+        // Press tab to focus on Slider
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyUp))
+        runOnIdle { assertTrue(sliderFocused) }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f + (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.53f - (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f + (1 + it) / 10f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.80f - (1 + it) / 10f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f + (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.53f - (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(1f, state.value) }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(0f, state.value) }
+    }
+
+    @Test
+    fun slider_rtl_0steps_change_using_keys() = runComposeUiTest {
+        val state = mutableStateOf(0.5f)
+        var sliderFocused = false
+        setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                Slider(
+                    value = state.value,
+                    onValueChange = { state.value = it },
+                    valueRange = 0f..1f,
+                    modifier = Modifier.onFocusChanged { sliderFocused = it.isFocused }
+                )
+            }
+        }
+
+        // Press tab to focus on Slider
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyUp))
+        runOnIdle { assertTrue(sliderFocused) }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.50f - (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyUp))
+            runOnIdle {
+                assertEquals(
+                    (0.47f + (1 + it) / 100f).round2decPlaces(),
+                    (state.value).round2decPlaces()
+                )
+            }
+        }
+    }
+
+    @Test
+    fun slider_ltr_29steps_using_keys() = runComposeUiTest {
+        val state = mutableStateOf(15f)
+        var sliderFocused = false
+        setContent {
+            Slider(
+                value = state.value,
+                steps = 29,
+                onValueChange = { state.value = it },
+                valueRange = 0f..30f,
+                modifier = Modifier.onFocusChanged { sliderFocused = it.isFocused }
+            )
+        }
+
+        // Press tab to focus on Slider
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.Tab, KeyEventType.KeyUp))
+        runOnIdle { assertTrue(sliderFocused) }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionRight, KeyEventType.KeyUp))
+            runOnIdle { assertEquals((15f + (1f + it)), (state.value)) }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionLeft, KeyEventType.KeyUp))
+            runOnIdle { assertEquals((18f - (1 + it)), state.value) }
+        }
+
+        runOnIdle { state.value = 0f }
+
+        val page = ((29 + 1) / 10).coerceIn(1, 10) // same logic as in Slider slideOnKeyEvents
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageDown, KeyEventType.KeyUp))
+            runOnIdle { assertEquals((1f + it) * page, state.value) }
+        }
+
+        runOnIdle { state.value = 30f }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.PageUp, KeyEventType.KeyUp))
+            runOnIdle { assertEquals(30f - (1 + it) * page, state.value) }
+        }
+
+        runOnIdle { state.value = 0f }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionUp, KeyEventType.KeyUp))
+            runOnIdle { assertEquals(1f + it, state.value) }
+        }
+
+        repeat(3) {
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyDown))
+            onRoot().performKeyPress(KeyEvent(Key.DirectionDown, KeyEventType.KeyUp))
+            runOnIdle { assertEquals(3f - (1f + it), state.value) }
+        }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveEnd, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(30f, state.value) }
+
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyDown))
+        onRoot().performKeyPress(KeyEvent(Key.MoveHome, KeyEventType.KeyUp))
+        runOnIdle { assertEquals(0f, state.value) }
+    }
+}
+
+private fun KeyEventType.toNativeAction(): Int {
+    return when (this) {
+        KeyEventType.KeyUp -> NativeKeyEvent.ACTION_UP
+        KeyEventType.KeyDown -> NativeKeyEvent.ACTION_DOWN
+        else -> error("KeyEventType - $this")
+    }
+}
+
+private fun KeyEvent(key: Key, type: KeyEventType): KeyEvent {
+    return KeyEvent(NativeKeyEvent(type.toNativeAction(), key.nativeKeyCode))
+}
+
+private fun Float.round2decPlaces() = (this * 100).roundToInt() / 100f
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
index 31245c0..24210ad 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
@@ -29,13 +29,17 @@
 import androidx.compose.material3.internal.systemBarsForVisualComponents
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.traversalIndex
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.offset
@@ -136,6 +140,35 @@
     contentWindowInsets: WindowInsets,
     bottomBar: @Composable () -> Unit
 ) {
+    // Create the backing value for the content padding
+    // These values will be updated during measurement, but before subcomposing the body content
+    // Remembering and updating a single PaddingValues avoids needing to recompose when the values
+    // change
+    val contentPadding = remember {
+        object : PaddingValues {
+            var topContentPadding by mutableStateOf(0.dp)
+            var startContentPadding by mutableStateOf(0.dp)
+            var endContentPadding by mutableStateOf(0.dp)
+            var bottomContentPadding by mutableStateOf(0.dp)
+
+            override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp =
+                when (layoutDirection) {
+                    LayoutDirection.Ltr -> startContentPadding
+                    LayoutDirection.Rtl -> endContentPadding
+                }
+
+            override fun calculateTopPadding(): Dp = topContentPadding
+
+            override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp =
+                when (layoutDirection) {
+                    LayoutDirection.Ltr -> endContentPadding
+                    LayoutDirection.Rtl -> startContentPadding
+                }
+
+            override fun calculateBottomPadding(): Dp = bottomContentPadding
+        }
+    }
+
     SubcomposeLayout(modifier = Modifier.semantics { isTraversalGroup = true }) { constraints ->
         val layoutWidth = constraints.maxWidth
         val layoutHeight = constraints.maxHeight
@@ -264,30 +297,25 @@
                 0
             }
 
+        // Update the backing state for the content padding before subcomposing the body
+        val insets = contentWindowInsets.asPaddingValues(this)
+        contentPadding.topContentPadding =
+            if (topBarPlaceable.width == 0 && topBarPlaceable.height == 0) {
+                insets.calculateTopPadding()
+            } else {
+                topBarPlaceable.height.toDp()
+            }
+        contentPadding.bottomContentPadding =
+            if (isBottomBarEmpty) {
+                insets.calculateBottomPadding()
+            } else {
+                bottomBarPlaceable.height.toDp()
+            }
+        contentPadding.startContentPadding = insets.calculateStartPadding(layoutDirection)
+        contentPadding.endContentPadding = insets.calculateEndPadding(layoutDirection)
+
         val bodyContentPlaceable =
             subcompose(ScaffoldLayoutContent.MainContent) {
-                    val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
-                    val innerPadding =
-                        PaddingValues(
-                            top =
-                                if (topBarPlaceable.width == 0 && topBarPlaceable.height == 0) {
-                                    insets.calculateTopPadding()
-                                } else {
-                                    topBarPlaceable.height.toDp()
-                                },
-                            bottom =
-                                if (isBottomBarEmpty) {
-                                    insets.calculateBottomPadding()
-                                } else {
-                                    bottomBarPlaceable.height.toDp()
-                                },
-                            start =
-                                insets.calculateStartPadding(
-                                    (this@SubcomposeLayout).layoutDirection
-                                ),
-                            end =
-                                insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection)
-                        )
                     Box(
                         modifier =
                             Modifier.semantics {
@@ -295,7 +323,7 @@
                                 traversalIndex = 3f
                             }
                     ) {
-                        content(innerPadding)
+                        content(contentPadding)
                     }
                 }
                 .first()
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
index e37af95..b839648 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
@@ -257,6 +257,9 @@
             "extralargeIncreased=$extraLargeIncreased, " +
             "extraExtraLarge=$extraExtraLarge)"
     }
+
+    /** Cached shapes used in components */
+    internal var defaultToggleButtonShapesCached: ButtonShapes? = null
 }
 
 /** Contains the default values used by [Shapes] */
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 d7cb737..1b29dd1 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
@@ -75,6 +75,11 @@
 import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.type
 import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
@@ -689,6 +694,15 @@
                 .requiredSizeIn(minWidth = ThumbWidth, minHeight = TrackHeight)
                 .sliderSemantics(state, enabled)
                 .focusable(enabled, interactionSource)
+                .slideOnKeyEvents(
+                    enabled,
+                    state.steps,
+                    state.valueRange,
+                    state.value,
+                    state.isRtl,
+                    state.onValueChange,
+                    state.onValueChangeFinished
+                )
                 .then(press)
                 .then(drag)
     ) { measurables, constraints ->
@@ -717,6 +731,89 @@
     }
 }
 
+private fun Modifier.slideOnKeyEvents(
+    enabled: Boolean,
+    steps: Int,
+    valueRange: ClosedFloatingPointRange,
+    value: Float,
+    isRtl: Boolean,
+    onValueChangeState: ((Float) -> Unit)?,
+    onValueChangeFinishedState: (() -> Unit)?
+): Modifier {
+    require(steps >= 0) { "steps should be >= 0" }
+    return this.onKeyEvent {
+        if (!enabled) return@onKeyEvent false
+        if (onValueChangeState == null) return@onKeyEvent false
+        when (it.type) {
+            KeyEventType.KeyDown -> {
+                val rangeLength = abs(valueRange.endInclusive - valueRange.start)
+                // When steps == 0, it means that a user is not limited by a step length (delta)
+                // when using touch or mouse. But it is not possible to adjust the value
+                // continuously when using keyboard buttons - the delta has to be discrete.
+                // In this case, 1% of the valueRange seems to make sense.
+                val actualSteps = if (steps > 0) steps + 1 else 100
+                val delta = rangeLength / actualSteps
+                when (it.key) {
+                    Key.DirectionUp -> {
+                        onValueChangeState((value + delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.DirectionDown -> {
+                        onValueChangeState((value - delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.DirectionRight -> {
+                        val sign = if (isRtl) -1 else 1
+                        onValueChangeState((value + sign * delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.DirectionLeft -> {
+                        val sign = if (isRtl) -1 else 1
+                        onValueChangeState((value - sign * delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.MoveHome -> {
+                        onValueChangeState(valueRange.start)
+                        true
+                    }
+                    Key.MoveEnd -> {
+                        onValueChangeState(valueRange.endInclusive)
+                        true
+                    }
+                    Key.PageUp -> {
+                        val page = (actualSteps / 10).coerceIn(1, 10)
+                        onValueChangeState((value - page * delta).coerceIn(valueRange))
+                        true
+                    }
+                    Key.PageDown -> {
+                        val page = (actualSteps / 10).coerceIn(1, 10)
+                        onValueChangeState((value + page * delta).coerceIn(valueRange))
+                        true
+                    }
+                    else -> false
+                }
+            }
+            KeyEventType.KeyUp -> {
+                when (it.key) {
+                    Key.DirectionUp,
+                    Key.DirectionDown,
+                    Key.DirectionRight,
+                    Key.DirectionLeft,
+                    Key.MoveHome,
+                    Key.MoveEnd,
+                    Key.PageUp,
+                    Key.PageDown -> {
+                        onValueChangeFinishedState?.invoke()
+                        true
+                    }
+                    else -> false
+                }
+            }
+            else -> false
+        }
+    }
+}
+
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun RangeSliderImpl(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
index d75437c..bf1ce4f2 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ToggleButton.kt
@@ -103,12 +103,7 @@
     onCheckedChange: (Boolean) -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    shapes: ButtonShapes =
-        ToggleButtonDefaults.shapes(
-            ToggleButtonDefaults.shape,
-            ToggleButtonDefaults.pressedShape,
-            ToggleButtonDefaults.checkedShape
-        ),
+    shapes: ButtonShapes = ToggleButtonDefaults.shapes(),
     colors: ToggleButtonColors = ToggleButtonDefaults.toggleButtonColors(),
     elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
     border: BorderStroke? = null,
@@ -222,12 +217,7 @@
     onCheckedChange: (Boolean) -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    shapes: ButtonShapes =
-        ToggleButtonDefaults.shapes(
-            ToggleButtonDefaults.elevatedShape,
-            ToggleButtonDefaults.elevatedPressedShape,
-            ToggleButtonDefaults.elevatedCheckedShape
-        ),
+    shapes: ButtonShapes = ToggleButtonDefaults.shapes(),
     colors: ToggleButtonColors = ToggleButtonDefaults.elevatedToggleButtonColors(),
     elevation: ButtonElevation? = ButtonDefaults.elevatedButtonElevation(),
     border: BorderStroke? = null,
@@ -300,12 +290,7 @@
     onCheckedChange: (Boolean) -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    shapes: ButtonShapes =
-        ToggleButtonDefaults.shapes(
-            ToggleButtonDefaults.tonalShape,
-            ToggleButtonDefaults.tonalPressedShape,
-            ToggleButtonDefaults.tonalCheckedShape
-        ),
+    shapes: ButtonShapes = ToggleButtonDefaults.shapes(),
     colors: ToggleButtonColors = ToggleButtonDefaults.tonalToggleButtonColors(),
     elevation: ButtonElevation? = ButtonDefaults.filledTonalButtonElevation(),
     border: BorderStroke? = null,
@@ -376,12 +361,7 @@
     onCheckedChange: (Boolean) -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
-    shapes: ButtonShapes =
-        ToggleButtonDefaults.shapes(
-            ToggleButtonDefaults.outlinedShape,
-            ToggleButtonDefaults.outlinedPressedShape,
-            ToggleButtonDefaults.outlinedCheckedShape
-        ),
+    shapes: ButtonShapes = ToggleButtonDefaults.shapes(),
     colors: ToggleButtonColors = ToggleButtonDefaults.outlinedToggleButtonColors(),
     elevation: ButtonElevation? = null,
     border: BorderStroke? = ButtonDefaults.outlinedButtonBorder(enabled),
@@ -438,18 +418,41 @@
         )
 
     /**
-     * Creates a [ButtonShapes] that correspond to the shapes in the default, pressed, and checked
-     * states. Toggle button will morph between these shapes as long as the shapes are all
-     * [CornerBasedShape]s.
+     * Creates a [ButtonShapes] that represents the default shape, pressedShape, and checkedShape
+     * used in a [ToggleButton].
+     */
+    @Composable fun shapes() = MaterialTheme.shapes.defaultShapes
+
+    /**
+     * Creates a [ButtonShapes] that represents the default shape, pressedShape, and checkedShape
+     * used in a [ToggleButton] and its variants.
      *
      * @param shape the unchecked shape for [ButtonShapes]
      * @param pressedShape the unchecked shape for [ButtonShapes]
      * @param checkedShape the unchecked shape for [ButtonShapes]
      */
     @Composable
-    fun shapes(shape: Shape, pressedShape: Shape, checkedShape: Shape): ButtonShapes =
-        remember(shape, pressedShape, checkedShape) {
-            ButtonShapes(shape, pressedShape, checkedShape)
+    fun shapes(
+        shape: Shape? = null,
+        pressedShape: Shape? = null,
+        checkedShape: Shape? = null
+    ): ButtonShapes =
+        MaterialTheme.shapes.defaultShapes.copy(
+            shape = shape,
+            pressedShape = pressedShape,
+            checkedShape = checkedShape
+        )
+
+    internal val Shapes.defaultShapes: ButtonShapes
+        @Composable
+        get() {
+            return defaultToggleButtonShapesCached
+                ?: ButtonShapes(
+                        shape = shape,
+                        pressedShape = pressedShape,
+                        checkedShape = checkedShape
+                    )
+                    .also { defaultToggleButtonShapesCached = it }
         }
 
     /** A round shape that can be used for all [ToggleButton]s and its variants */
@@ -464,46 +467,14 @@
     val shape: Shape
         @Composable get() = ButtonSmallTokens.ContainerShapeSquare.value
 
-    /** The default unchecked shape for [ElevatedToggleButton] */
-    val elevatedShape: Shape
-        @Composable get() = ButtonSmallTokens.ContainerShapeSquare.value
-
-    /** The default unchecked shape for [TonalToggleButton] */
-    val tonalShape: Shape
-        @Composable get() = ButtonSmallTokens.ContainerShapeSquare.value
-
-    /** The default unchecked shape for [OutlinedToggleButton] */
-    val outlinedShape: Shape
-        @Composable get() = ButtonSmallTokens.ContainerShapeSquare.value
-
     /** The default pressed shape for [ToggleButton] */
-    val pressedShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default pressed shape for [ElevatedToggleButton] */
-    val elevatedPressedShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default pressed shape for [TonalToggleButton] */
-    val tonalPressedShape: Shape = RoundedCornerShape(6.dp)
-
-    /** The default pressed shape for [OutlinedToggleButton] */
-    val outlinedPressedShape: Shape = RoundedCornerShape(6.dp)
+    val pressedShape: Shape
+        @Composable get() = RoundedCornerShape(6.dp)
 
     /** The default checked shape for [ToggleButton] */
     val checkedShape: Shape
         @Composable get() = ButtonSmallTokens.SelectedContainerShapeRound.value
 
-    /** The default checked shape for [ElevatedToggleButton] */
-    val elevatedCheckedShape: Shape
-        @Composable get() = ButtonSmallTokens.SelectedContainerShapeRound.value
-
-    /** The default checked shape for [TonalToggleButton] */
-    val tonalCheckedShape: Shape
-        @Composable get() = ButtonSmallTokens.SelectedContainerShapeRound.value
-
-    /** The default checked shape for [OutlinedToggleButton] */
-    val outlinedCheckedShape: Shape
-        @Composable get() = ButtonSmallTokens.SelectedContainerShapeRound.value
-
     /**
      * Creates a [ToggleButtonColors] that represents the default container and content colors used
      * in a [ToggleButton].
@@ -847,6 +818,20 @@
  * @property checkedShape is the checked shape.
  */
 class ButtonShapes(val shape: Shape, val pressedShape: Shape, val checkedShape: Shape) {
+    /** Returns a copy of this ButtonShapes, optionally overriding some of the values. */
+    fun copy(
+        shape: Shape? = this.shape,
+        pressedShape: Shape? = this.pressedShape,
+        checkedShape: Shape? = this.checkedShape
+    ) =
+        ButtonShapes(
+            shape = shape.takeOrElse { this.shape },
+            pressedShape = pressedShape.takeOrElse { this.pressedShape },
+            checkedShape = checkedShape.takeOrElse { this.checkedShape }
+        )
+
+    internal fun Shape?.takeOrElse(block: () -> Shape): Shape = this ?: block()
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other == null || other !is ButtonShapes) return false
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/CompositionLocalNamingDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/CompositionLocalNamingDetectorTest.kt
index 233445e..c4fdde3 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/CompositionLocalNamingDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/CompositionLocalNamingDetectorTest.kt
@@ -18,7 +18,7 @@
 
 package androidx.compose.runtime.lint
 
-import androidx.compose.lint.test.bytecodeStub
+import androidx.compose.lint.test.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -35,132 +35,6 @@
     override fun getIssues(): MutableList =
         mutableListOf(CompositionLocalNamingDetector.CompositionLocalNaming)
 
-    // Simplified CompositionLocal.kt stubs
-    private val compositionLocalStub =
-        bytecodeStub(
-            filename = "CompositionLocal.kt",
-            filepath = "androidx/compose/runtime",
-            checksum = 0xeda1836e,
-            """
-            package androidx.compose.runtime
-
-            sealed class CompositionLocal constructor(defaultFactory: (() -> T)? = null)
-
-            abstract class ProvidableCompositionLocal internal constructor(
-                defaultFactory: (() -> T)?
-            ) : CompositionLocal(defaultFactory)
-
-            internal class DynamicProvidableCompositionLocal constructor(
-                defaultFactory: (() -> T)?
-            ) : ProvidableCompositionLocal(defaultFactory)
-
-            internal class StaticProvidableCompositionLocal(
-                defaultFactory: (() -> T)?
-            ) : ProvidableCompositionLocal(defaultFactory)
-
-            fun  compositionLocalOf(
-                defaultFactory: (() -> T)? = null
-            ): ProvidableCompositionLocal = DynamicProvidableCompositionLocal(defaultFactory)
-
-            fun  staticCompositionLocalOf(
-                defaultFactory: (() -> T)? = null
-            ): ProvidableCompositionLocal = StaticProvidableCompositionLocal(defaultFactory)
-        """,
-            """
-        META-INF/main.kotlin_module:
-        H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijg0uOSSMxLKcrPTKnQS87PLcgvTtUr
-        Ks0rycxNFRJyBgtklmTm5/nkJyfmeJdwqXHJ4FKvl5afL8QWklpc4l2ixKDF
-        AAAiXiK7cAAAAA==
-        """,
-            """
-        androidx/compose/runtime/CompositionLocal.class:
-        H4sIAAAAAAAA/5VTS08TURT+7nT6YOQxFMWCqCCofShTiRoUQlQMSZMiCk03
-        LMxlZsBL2xkyc9vgxjT+C7f+A1YkLkzD0h9lPHdalKCksjmv+51zvjPnzI+f
-        374DeAyLIcc9J/CFc2jZfuPAD10raHpSNFxrNfKFFL5X9m1eT4IxZJcrz8v7
-        vMWtOvf2rI2dfdeWSyt/hxjM87EkdIbEsvCEXGGYzZZrvqwLz9pvNazdpmer
-        TqG11rOKS7kq0euHWi5UKksrETaWzVUHkcCAgTgMBl1+ECFDofzfIxLrYcfd
-        5c26XOO29IOPDNP9aDJs9h2ldBYgPOkGHq9br7utVgkrg6bqt86DmhtE07zr
-        W/SyNdW3GTWgIa2spLKuMozN2X+w7xsRmGH+csUZRk8T1l3JHS45xbRGK0Z3
-        xpQYUAIMrEbxQ6G8IlnOI4aNTnvc0DKa0WkbmklC6Z6b0VJPM512Xk912iZb
-        0Iraq4mUnjZTmhmbNNJ6WlvstDOsqJ98TWhm/OQzYwlVdoE6VZhqmD4ldnZj
-        Ty6+ibeB3xIO36m7/7iOsfOx+ZqkQ1v1HZdhpCw8902zseMGFZWveitMlQdC
-        +b3gwJbY87hsBmQPbUlu19b5Qe/N2PKbge2uCeVMbHYpVUUo6PWl5/mSR0eg
-        z9Du4uqDIkYWLZNklrxnNLBGOp4/xpUjMjTkSCYoDOjIkxzvAjCIoahAHMMY
-        ofdChE6Z9IvCJF+VK1J5nfTIlP7pC+KsnC+wY4x1Cz+IurPU2Q5R9mgve5FA
-        as+JfOEY146i9SsG093obwaJHgNljeN6lNVlE8PDSN/HPOkXhMkQ34ltxEqY
-        LOFGCVO4SSZulXAb09tgIWZwZxvJEEMhZkPMhRgOcTdUkXu/AG89zFz4BAAA
-        """,
-            """
-        androidx/compose/runtime/CompositionLocalKt.class:
-        H4sIAAAAAAAA/51UW08TQRT+ZntfCpSiUIoXhKpclC0gqLSSGAyxoVwiDcbw
-        NN0uZHrZJbvbBl4Mf8N/4ZtGE9Nnf5TxzLZELZSavpw5M+eb73x7zpn9+evb
-        DwDP8IJhgZsl2xKlM023aqeWY2h23XRFzdA2vb1whWXmLZ1Xt90QGEOszBtc
-        q3LzRNsrlg2dTn0Mcb0DvXfM8H42X7HcqjC1cqOmHddNXYYdbavtpTNz+a7p
-        922rIUq8WDU6hWQYzrKF9XynkMxGr3zZhUIhs9Ff1qx3lWEmb9knWtlwizYX
-        RM5N03J5K9Gu5e7Wq1Jg6iYUQWQGgq13FfLm3OQ1oXfXE4LKEMwKU7gbpKp3
-        qQ+jiGJQxQCGGFb6qEAIMYahknHM61V3i+uuZZ8zTPVKzJC8OhypNg1Duafy
-        3NVO9zc3UQQQVKFgjCHhyHbonRg5ti+7ch94d26q0ISKpKzvVDf6yw+PItHS
-        cpdh5LICO4bLS9zlVDOl1vDRG2XSRKQBA6tIR6HgmZBemrzSEkOpeRFTmxeq
-        klBUJax4a/MimYqRCcf9ceWtkmbT/jDBlOVYWIn5kmrrOMHSfsIF/gMmcy0z
-        rPb5YlmBfhKX3/n3cIx2ghcrNBX+TatkMAznhWns1mtFwy5IYskhMYfcFnLf
-        PowciBOTu3Wb/Ml3LTk5syEcQeHXf54evcvO6D63ec1wDfsf2CB1Wq/s8NN2
-        AvXAqtu6sSXkZqLNcXiFH0vUUL9sFtkJOW20arRbg4/6B0S/Y+DD/BcMNzHy
-        WfYSabJBL3aLbhOihUMco7Que5gQVtqosPfbBkJUUESAWISu3Sa/lUSBHIvB
-        Sf/HTwiw7fmvGG9lWSVLCsJeuiEPFSfCUSKMk9DEdUKTUujkNUIT/Qm9c6PQ
-        e12FjhHhOBGOUXjNAy3iOa2viO0+1XjqCL4cHuQwncMMUjk8xKMcHmP2CMzB
-        HOaPEHQQcLDg4ImDuIOnDhK/AezqFjcEBwAA
-        """,
-            """
-        androidx/compose/runtime/DynamicProvidableCompositionLocal.class:
-        H4sIAAAAAAAA/51SXU8TQRQ9sy0tXaGUIljwAxQ0AolbUBNDaxPFEJpUJNLw
-        wtN0d6jT7s6a3dkG3vpb/Ac+mfhgGh/9UcY7bYloQkh4mXvumXPP3Lkzv35/
-        /wHgBZ4w7HDlRaH0zhw3DD6HsXCiRGkZCOfdueKBdA+jsCc93vLF7lAgtQxV
-        I3S5nwVjOKw2dxod3uOOz1Xb+dDqCFdXao0rba/2qzablVqF4fkNarNIM2Sq
-        UkldY1h92uiG2pfK6fQC5zRRrhHGzt4YlSvrxwzr16mqm8OOjHatEUZtpyN0
-        K+KSNFypUPOR/iDxfdNTZQoZZG1MwGZI608yZqhePYhr50ujyHvilCe+3uOu
-        DqNzhpXrLsYweyF5LzT3uObEWUEvRS/OzJIzCxhYl/gzabIyIW+LYX/QL9pW
-        ybIH/X/D5KBfGvQ30hQLbHuymC5a+6xsvZ0v5gupJdvkr0jCyumfXzJWYcL4
-        bdMRTYaXN/kK1HLx4hqX7zb3v/BZV9Osd0NPMMw0pBIHSdASUdOYGg+jOeaR
-        NPmYzB3JtuI6iQivfRy1Ulc9GUvaPuQRD4QW0Zu/D8xgH4VJ5Io9aeoXxzXH
-        o4pLQmzBotcfj9d8BqSwTFmNeItiZmPzG259JWRhhVZ7yE6ZH4OHhBZGKmKm
-        hy4Z5DFDTo+GFZNYpZg11jkCqTGdwtowPsBjiq9pt0CGsydI1VGsY66O25gn
-        iIU67qB0AhZjEUsnyMSYjnE3xr0Y+Rj3Y2T/AJbWL04aBAAA
-        """,
-            """
-        androidx/compose/runtime/ProvidableCompositionLocal.class:
-        H4sIAAAAAAAA/41SXU9TQRA9e1vacsVSikLBLxBEgcRbURNjK0YxjTUFURpe
-        eNr2Lrjt7V6zd2+Db/0t/gOfTHwwjY/+KONsWyKSEHjZmT1z5szszP7+8+Mn
-        gCe4z/CYK1+H0j/2mmHncxgJT8fKyI7wdnXYlT5vBGJrEJFGhqoWNnmQBmOo
-        lOvPay3e5V7A1ZH3vtESTVParJ2rd1alXK+XNksMq5fOSCPJkCpLJc0mw9KD
-        Wjs0gVReq9vxDmPVtMTIq4y8Yml1n9QvYpXXB31Y7nIt1EdeS5iG5pI4XKnQ
-        8CF/Jw4COwtq+MOFhU/HpTJCKx54b8QhjwOzRVSj46YJ9TbXbaGp9ARScF2M
-        4QpD0nySEcPT8wd5/mKouaw/LFPhtsIXhoWLmmWYOqFsC8N9bjhhTqeboD/C
-        7DFuDzCwNuHH0t6K5PmPGN71e3nXKThuv/e/yawU+r21ZKbfy7GNTD6Zd96y
-        ovN6loB8NpeYdy30rN8rsGLy19eUkxuzihtUpM6wfvlfRK3mT9o//abps8SH
-        bUPD3Qp9wTBZk0rsxJ2G0HU7R6thOftcS3sfgeN78khxE2vylz8OG6iqrowk
-        hXe55h1Bu33175cwuHthrJuiIm3+3Chnf5hxiphchEPrHo2Vtp9GAgt0e0nW
-        IZteW2ffMfGNXAeLdLoDOEPUFO6SNzOk4SqyA5k0JpEjqaVBRgbLFrPa4+Qk
-        RnAC9wb2DlbIvqDoFHWRP0CiiukqrlVxHTPkYraKAuYOwCLM48YBUhGyEW5G
-        uBVhMsLtCOm/jQDw8EUEAAA=
-        """,
-            """
-        androidx/compose/runtime/StaticProvidableCompositionLocal.class:
-        H4sIAAAAAAAA/51SXW8SQRQ9s1A+1pZSaiutH60WjW0Tl1ZNVJBEmzQlwUqE
-        8MLTsDvFgWXX7M6S+sZv8R/4ZOKDIT76o4x3gMZqQpr0Ze65Z849c+fO/Pr9
-        /QeAZ3jE8JJ7TuBL59yy/cEnPxRWEHlKDoTVUFxJux74Q+nwjiuOJvtSSd+r
-        +TZ3k2AM9XLzVa3Hh9xyude13nd6wlalSm2u63y/crNZqpQYnl6jNok4Q6Is
-        PakqDDuPa31fudKzesOBdRZ5thaG1vEMFUu7LYbdq1Tl/UlHWluo+UHX6gnV
-        CbgkDfc8X49H608j19U9lRaRQNLEAkyGuPooQ4bS/EFcNV6aRMYRZzxy1TG3
-        lR98Zti+6l4MKxeSd0JxhytOnDEYxui9mV7SegED6xN/LnVWJOQcMJyMRznT
-        yBvmePRvSI1H+fFoL04xyw5TuXjOOGFF4+1aLpONbZo6f0ESVoz//JIwsgva
-        75COaDI8v85PoJZzF9e4fLfV/4VP+opGfeQ7gmG5Jj1xGg06ImhqU+2hNS0e
-        SJ3PyHRDdj2uooBw4cO0lao3lKGk7ToP+EAoEbz5+74MZsOPAlscS12/Matp
-        TSsuCXEAgx5/Nl79FxDDFmUV4g2Kib39b7jxlZCBbVrNCZulmgzuE1qfqrCI
-        pYlLgvhlcnowqUhhh2JSW6cJxGZ0DIVJvIeHFF/TrjZcaSNWRa6K1SpuYo0g
-        1qu4hXwbLMQGNttIhFgKcTvEnRCZEHdDJP8AhWEFiBgEAAA=
-        """
-        )
-
     @Test
     fun noLocalPrefix() {
         lint()
@@ -187,7 +61,7 @@
                 }
             """
                 ),
-                compositionLocalStub
+                Stubs.CompositionLocal
             )
             .run()
             .expect(
@@ -229,7 +103,7 @@
                 }
             """
                 ),
-                compositionLocalStub
+                Stubs.CompositionLocal
             )
             .run()
             .expectClean()
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index 1bb5493..fb2f039 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -46,7 +46,6 @@
                 implementation(libs.kotlinStdlibCommon)
                 implementation(libs.kotlinCoroutinesCore)
                 implementation(project(":collection:collection"))
-                compileOnly(project(":performance:performance-annotation"))
             }
         }
 
@@ -112,9 +111,6 @@
 
         nonAndroidMain {
             dependsOn(commonMain)
-            dependencies {
-                implementation(project(":performance:performance-annotation"))
-            }
         }
 
         jvmStubsMain {
diff --git a/compose/runtime/runtime/proguard-rules.pro b/compose/runtime/runtime/proguard-rules.pro
index 38504ad..6e78193 100644
--- a/compose/runtime/runtime/proguard-rules.pro
+++ b/compose/runtime/runtime/proguard-rules.pro
@@ -31,7 +31,3 @@
     static void compose*RuntimeError(...);
     static java.lang.Void compose*RuntimeError(...);
 }
-
-# NeverInline is compile-time only and present in the platform itself, we don't bundle
-# it with the library, so we can safely ignore this warning
--dontwarn dalvik.annotation.optimization.NeverInline
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt
index 766d3f3..b7eb115 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt
@@ -196,19 +196,18 @@
  * @see ComposeNode
  */
 abstract class AbstractApplier(val root: T) : Applier {
-    private val stack = mutableListOf()
+    private val stack = Stack()
+
     override var current: T = root
         protected set
 
     override fun down(node: T) {
-        stack.add(current)
+        stack.push(current)
         current = node
     }
 
     override fun up() {
-        // Let us fail with IndexOutOfBounds exception
-        // checkPrecondition(stack.isNotEmpty()) { "empty stack" }
-        current = stack.removeAt(stack.size - 1)
+        current = stack.pop()
     }
 
     final override fun clear() {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 5387434..30031fa6 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -3705,7 +3705,7 @@
                         parentContext,
                         reference
                     )
-                    if (needsNodeDelete) {
+                    if (needsNodeDelete && group != groupBeingRemoved) {
                         changeListWriter.endNodeMovementAndDeleteNode(nodeIndex, group)
                         0 // These nodes were deleted
                     } else reader.nodeCount(group)
@@ -3733,7 +3733,7 @@
             } else if (reader.containsMark(group)) {
                 // Traverse the group freeing the child movable content. This group is known to
                 // have at least one child that contains movable content because the group is
-                // marked as containing a mark.
+                // marked as containing a mark
                 val size = reader.groupSize(group)
                 val end = group + size
                 var current = group + 1
@@ -3766,8 +3766,18 @@
                 if (reader.isNode(group)) 1 else runningNodeCount
             } else if (reader.isNode(group)) 1 else reader.nodeCount(group)
         }
-        reportGroup(groupBeingRemoved, needsNodeDelete = false, nodeIndex = 0)
+        // If the group that is being deleted is a node we need to remove any children that
+        // are moved.
+        val rootIsNode = reader.isNode(groupBeingRemoved)
+        if (rootIsNode) {
+            changeListWriter.endNodeMovement()
+            changeListWriter.moveDown(reader.node(groupBeingRemoved))
+        }
+        reportGroup(groupBeingRemoved, needsNodeDelete = rootIsNode, nodeIndex = 0)
         changeListWriter.endNodeMovement()
+        if (rootIsNode) {
+            changeListWriter.moveUp()
+        }
     }
 
     /**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Stack.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Stack.kt
index 29a83368..03989dd 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Stack.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Stack.kt
@@ -16,14 +16,10 @@
 
 package androidx.compose.runtime
 
-import androidx.collection.MutableIntList
-import androidx.collection.MutableObjectList
-import androidx.collection.mutableIntListOf
-import androidx.collection.mutableObjectListOf
 import kotlin.jvm.JvmInline
 
 @JvmInline
-internal value class Stack(private val backing: MutableObjectList = mutableObjectListOf()) {
+internal value class Stack(private val backing: ArrayList = ArrayList()) {
     val size: Int
         get() = backing.size
 
@@ -45,28 +41,40 @@
     fun toArray(): Array = Array(backing.size) { backing[it] } as Array
 }
 
-@JvmInline
-internal value class IntStack(private val slots: MutableIntList = mutableIntListOf()) {
+internal class IntStack {
+    private var slots = IntArray(10)
+    private var tos = 0
+
     val size: Int
-        get() = slots.size
+        get() = tos
 
-    fun push(value: Int) = slots.add(value)
+    fun push(value: Int) {
+        if (tos >= slots.size) {
+            slots = slots.copyOf(slots.size * 2)
+        }
+        slots[tos++] = value
+    }
 
-    fun pop(): Int = slots.removeAt(size - 1)
+    fun pop(): Int = slots[--tos]
 
-    fun peekOr(default: Int): Int = if (size > 0) peek() else default
+    fun peekOr(default: Int): Int = if (tos > 0) peek() else default
 
-    fun peek() = slots[size - 1]
+    fun peek() = slots[tos - 1]
 
-    fun peek2() = slots[size - 2]
+    fun peek2() = slots[tos - 2]
 
     fun peek(index: Int) = slots[index]
 
-    fun isEmpty() = slots.isEmpty()
+    fun isEmpty() = tos == 0
 
-    fun isNotEmpty() = slots.isNotEmpty()
+    fun isNotEmpty() = tos != 0
 
-    fun clear() = slots.clear()
+    fun clear() {
+        tos = 0
+    }
 
-    fun indexOf(value: Int) = slots.indexOf(value)
+    fun indexOf(value: Int): Int {
+        for (i in 0 until tos) if (slots[i] == value) return i
+        return -1
+    }
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operations.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operations.kt
index e060622..40092fd 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operations.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/changelist/Operations.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.changelist.Operation.ObjectParameter
 import androidx.compose.runtime.debugRuntimeCheck
 import androidx.compose.runtime.requirePrecondition
-import dalvik.annotation.optimization.NeverInline
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.InvocationKind.EXACTLY_ONCE
 import kotlin.contracts.contract
@@ -124,7 +123,6 @@
         return (currentSize + resizeAmount).coerceAtLeast(requiredSize)
     }
 
-    @NeverInline
     private fun resizeOpCodes() {
         val resizeAmount = opCodesSize.coerceAtMost(OperationsMaxResizeAmount)
         val newOpCodes = arrayOfNulls(opCodesSize + resizeAmount)
@@ -138,7 +136,6 @@
         }
     }
 
-    @NeverInline
     private fun resizeIntArgs(currentSize: Int, requiredSize: Int) {
         val newIntArgs = IntArray(determineNewSize(currentSize, requiredSize))
         intArgs.copyInto(newIntArgs, 0, 0, currentSize)
@@ -152,7 +149,6 @@
         }
     }
 
-    @NeverInline
     private fun resizeObjectArgs(currentSize: Int, requiredSize: Int) {
         val newObjectArgs = arrayOfNulls(determineNewSize(currentSize, requiredSize))
         objectArgs.copyInto(newObjectArgs, 0, 0, currentSize)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/MutableVector.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/MutableVector.kt
index fa09671..1315b32 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/MutableVector.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/collection/MutableVector.kt
@@ -18,7 +18,6 @@
 
 package androidx.compose.runtime.collection
 
-import dalvik.annotation.optimization.NeverInline
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
@@ -288,7 +287,6 @@
         }
     }
 
-    @NeverInline
     @PublishedApi
     internal fun resizeStorage(capacity: Int) {
         val oldContent = content
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt
index 54fa6f6..ccb6c09 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.runtime
 
+import androidx.compose.runtime.mock.InlineLinear
 import androidx.compose.runtime.mock.Linear
 import androidx.compose.runtime.mock.MockViewValidator
 import androidx.compose.runtime.mock.View
@@ -1572,6 +1573,37 @@
         expectChanges()
         revalidate()
     }
+
+    @Test // 365802563
+    fun movableContent_movingChildOfDeletedNode() = compositionTest {
+        var index by mutableIntStateOf(0)
+        val content = movableContentOf { Text("Some text") }
+        compose {
+            for (i in index..3) {
+                InlineLinear { content() }
+            }
+            for (i in 0..index) {
+                InlineLinear { content() }
+            }
+        }
+        validate {
+            for (i in index..3) {
+                Linear { Text("Some text") }
+            }
+            for (i in 0..index) {
+                Linear { Text("Some text") }
+            }
+        }
+
+        index++
+        advance()
+
+        index++
+        advance()
+
+        index++
+        advance()
+    }
 }
 
 @Composable
diff --git a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/SuspiciousCompositionLocalModifierReadDetectorTest.kt b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/SuspiciousCompositionLocalModifierReadDetectorTest.kt
index f4c3192..3b1f2d1 100644
--- a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/SuspiciousCompositionLocalModifierReadDetectorTest.kt
+++ b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/SuspiciousCompositionLocalModifierReadDetectorTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.lint
 
+import androidx.compose.lint.test.Stubs
 import androidx.compose.ui.lint.SuspiciousCompositionLocalModifierReadDetector.Companion.SuspiciousCompositionLocalModifierRead
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
@@ -64,29 +65,6 @@
         """
         )
 
-    private val CompositionLocalStub =
-        kotlin(
-            """
-            package androidx.compose.runtime
-
-            import java.lang.RuntimeException
-
-            class CompositionLocal(defaultFactory: () -> T)
-
-            class ProvidedValue internal constructor(
-                val compositionLocal: CompositionLocal,
-                val value: T,
-                val canOverride: Boolean
-            )
-
-            fun  compositionLocalOf(defaultFactory: () -> T): CompositionLocal =
-                throw RuntimeException("Not implemented in lint stubs.")
-
-            fun  staticCompositionLocalOf(defaultFactory: () -> T): CompositionLocal =
-                throw RuntimeException("Not implemented in lint stubs.")
-        """
-        )
-
     @Test
     fun testCompositionLocalReadInModifierAttachAndDetach() {
         lint()
@@ -116,7 +94,7 @@
                 }
             """
                 ),
-                CompositionLocalStub,
+                Stubs.CompositionLocal,
                 CompositionLocalConsumerModifierStub,
                 ModifierNodeStub
             )
@@ -167,7 +145,7 @@
                 }
             """
                 ),
-                CompositionLocalStub,
+                Stubs.CompositionLocal,
                 CompositionLocalConsumerModifierStub,
                 ModifierNodeStub
             )
@@ -201,7 +179,7 @@
                 }
             """
                 ),
-                CompositionLocalStub,
+                Stubs.CompositionLocal,
                 CompositionLocalConsumerModifierStub,
                 ModifierNodeStub
             )
@@ -243,7 +221,7 @@
                 }
             """
                 ),
-                CompositionLocalStub,
+                Stubs.CompositionLocal,
                 CompositionLocalConsumerModifierStub,
                 ModifierNodeStub
             )
@@ -275,7 +253,7 @@
                 }
             """
                 ),
-                CompositionLocalStub,
+                Stubs.CompositionLocal,
                 CompositionLocalConsumerModifierStub,
                 ModifierNodeStub
             )
@@ -319,7 +297,7 @@
                 }
             """
                 ),
-                CompositionLocalStub,
+                Stubs.CompositionLocal,
                 CompositionLocalConsumerModifierStub,
                 ModifierNodeStub
             )
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Html.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Html.android.kt
index 3a81e24..03df143 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Html.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Html.android.kt
@@ -53,8 +53,8 @@
 /**
  * Converts a string with HTML tags into [AnnotatedString].
  *
- * If you define your string in the resources, make sure to use HTML-escaped opening brackets "<"
- * instead of "<".
+ * If you define your string in the resources, make sure to use HTML-escaped opening brackets
+ * &lt; instead of <.
  *
  * For a list of supported tags go check
  * [Styling with HTML markup](https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML)
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/SemanticAutofillNavigationDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/AutofillNavigationDemo.kt
similarity index 92%
rename from compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/SemanticAutofillNavigationDemo.kt
rename to compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/AutofillNavigationDemo.kt
index 0d9aea2..bf06862 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/SemanticAutofillNavigationDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/AutofillNavigationDemo.kt
@@ -16,9 +16,11 @@
 
 package androidx.compose.ui.demos.autofill
 
+import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
@@ -37,6 +39,7 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.autofill.ContentType
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalAutofillManager
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.tooling.preview.Preview
@@ -140,12 +143,18 @@
                 Spacer(modifier = Modifier.height(8.dp))
                 BasicTextField(
                     state = remember { TextFieldState() },
-                    modifier = Modifier.semantics { ContentType.Username }
+                    modifier =
+                        Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                            ContentType.Username
+                        }
                 )
 
                 BasicTextField(
                     state = remember { TextFieldState() },
-                    modifier = Modifier.semantics { ContentType.Username }
+                    modifier =
+                        Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                            ContentType.Username
+                        }
                 )
             }
         }
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/TextFieldAutofillDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/TextFieldAutofillDemo.kt
index 7a12760..7c649d7 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/TextFieldAutofillDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/TextFieldAutofillDemo.kt
@@ -18,7 +18,9 @@
 
 import android.annotation.SuppressLint
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.text.BasicSecureTextField
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.input.TextFieldState
@@ -48,6 +50,7 @@
 import androidx.compose.ui.semantics.contentType
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
 
 @SuppressLint("NullAnnotationGroup")
 @OptIn(ExperimentalComposeUiApi::class)
@@ -61,13 +64,19 @@
 
         BasicTextField(
             state = remember { TextFieldState() },
-            modifier = Modifier.semantics { contentType = ContentType.NewUsername },
+            modifier =
+                Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                    contentType = ContentType.NewUsername
+                },
             textStyle = MaterialTheme.typography.body1.copy(color = Color.White),
             cursorBrush = SolidColor(Color.White)
         )
         BasicTextField(
             state = remember { TextFieldState() },
-            modifier = Modifier.semantics { contentType = ContentType.NewPassword },
+            modifier =
+                Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                    contentType = ContentType.NewPassword
+                },
             textStyle = MaterialTheme.typography.body1.copy(color = Color.White),
             cursorBrush = SolidColor(Color.White)
         )
@@ -89,14 +98,20 @@
 
         BasicTextField(
             state = remember { TextFieldState() },
-            modifier = Modifier.semantics { contentType = ContentType.Username },
+            modifier =
+                Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                    contentType = ContentType.Username
+                },
             textStyle = MaterialTheme.typography.body1.copy(color = Color.LightGray),
             cursorBrush = SolidColor(Color.White)
         )
 
         BasicTextField(
             state = remember { TextFieldState() },
-            modifier = Modifier.semantics { contentType = ContentType.Password },
+            modifier =
+                Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                    contentType = ContentType.Password
+                },
             textStyle = MaterialTheme.typography.body1.copy(color = Color.LightGray),
             cursorBrush = SolidColor(Color.White)
         )
@@ -119,7 +134,10 @@
         Text(text = "Enter your username and password below.", color = Color.White)
         BasicTextField(
             state = remember { TextFieldState() },
-            modifier = Modifier.semantics { contentType = ContentType.Username },
+            modifier =
+                Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                    contentType = ContentType.Username
+                },
             textStyle = MaterialTheme.typography.body1.copy(color = Color.LightGray),
             cursorBrush = SolidColor(Color.White)
         )
@@ -133,7 +151,10 @@
                 } else {
                     TextObfuscationMode.RevealLastTyped
                 },
-            modifier = Modifier.semantics { contentType = ContentType.Password },
+            modifier =
+                Modifier.fillMaxWidth().border(1.dp, Color.LightGray).semantics {
+                    contentType = ContentType.Password
+                },
             textStyle = MaterialTheme.typography.body1.copy(color = Color.White),
             cursorBrush = SolidColor(Color.White)
         )
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutofillManagerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutofillManagerTest.kt
index 094ec91..e50e0c3 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutofillManagerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutofillManagerTest.kt
@@ -139,6 +139,31 @@
 
     @Test
     @SmallTest
+    @SdkSuppress(minSdkVersion = 33)
+    fun autofillManager_showAutofillDialog_previousFocusFalse() {
+        val usernameTag = "username_tag"
+        var hasFocus by mutableStateOf(false)
+
+        rule.setContentWithAutofillEnabled {
+            Box(
+                Modifier.semantics {
+                        contentType = ContentType.Username
+                        contentDataType = ContentDataType.Text
+                        focused = hasFocus
+                    }
+                    .size(height, width)
+                    .testTag(usernameTag)
+            )
+        }
+
+        rule.runOnIdle { hasFocus = true }
+
+        rule.runOnIdle { verify(autofillManagerMock).showAutofillDialog(any()) }
+        rule.runOnIdle { verify(autofillManagerMock).notifyViewEntered(any(), any()) }
+    }
+
+    @Test
+    @SmallTest
     @SdkSuppress(minSdkVersion = 26)
     fun autofillManager_notifyViewEntered_previousFocusNull() {
         val usernameTag = "username_tag"
@@ -166,6 +191,34 @@
 
     @Test
     @SmallTest
+    @SdkSuppress(minSdkVersion = 33)
+    fun autofillManager_showAutofillDialog_previousFocusNull() {
+        val usernameTag = "username_tag"
+        var hasFocus by mutableStateOf(false)
+
+        rule.setContentWithAutofillEnabled {
+            Box(
+                modifier =
+                    if (hasFocus)
+                        Modifier.semantics {
+                                contentType = ContentType.Username
+                                contentDataType = ContentDataType.Text
+                                focused = hasFocus
+                            }
+                            .size(height, width)
+                            .testTag(usernameTag)
+                    else plainVisibleModifier(usernameTag)
+            )
+        }
+
+        rule.runOnIdle { hasFocus = true }
+
+        rule.runOnIdle { verify(autofillManagerMock).showAutofillDialog(any()) }
+        rule.runOnIdle { verify(autofillManagerMock).notifyViewEntered(any(), any()) }
+    }
+
+    @Test
+    @SmallTest
     @SdkSuppress(minSdkVersion = 26)
     fun autofillManager_notifyViewExited_previousFocusTrue() {
         val usernameTag = "username_tag"
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/TextFieldsSemanticAutofillTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/TextFieldsSemanticAutofillTest.kt
index 93eba16..b6aa107 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/TextFieldsSemanticAutofillTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/TextFieldsSemanticAutofillTest.kt
@@ -22,18 +22,24 @@
 import android.view.autofill.AutofillValue
 import androidx.annotation.RequiresApi
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.AutofillHighlight
 import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.LocalAutofillHighlight
 import androidx.compose.material.OutlinedTextField
 import androidx.compose.material.Text
 import androidx.compose.material.TextField
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
 import androidx.compose.ui.ComposeUiFlags.isSemanticAutofillEnabled
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.node.RootForTest
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.LocalView
@@ -42,6 +48,7 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.test.TestActivity
 import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -60,6 +67,7 @@
     @get:Rule val rule = createAndroidComposeRule()
     private lateinit var androidComposeView: AndroidComposeView
     private lateinit var composeView: View
+    private var autofillHighlight: AutofillHighlight? = null
 
     // ============================================================================================
     // Tests to verify legacy TextField populating and filling.
@@ -176,6 +184,51 @@
         assertEquals(usernameInput, expectedUsername)
     }
 
+    @Test
+    @SmallTest
+    @SdkSuppress(minSdkVersion = 26)
+    fun performAutofill_customHighlight_legacyTF() {
+        val expectedUsername = "test_username"
+        val usernameTag = "username_tag"
+        var usernameInput by mutableStateOf("")
+        val customHighlightColor = Color.Red
+
+        rule.setContentWithAutofillEnabled {
+            CompositionLocalProvider(
+                LocalAutofillHighlight provides AutofillHighlight(customHighlightColor)
+            ) {
+                Column {
+                    TextField(
+                        value = usernameInput,
+                        onValueChange = { usernameInput = it },
+                        label = { Text("Enter username here") },
+                        modifier =
+                            Modifier.testTag(usernameTag).semantics {
+                                contentType = ContentType.Username
+                            }
+                    )
+                }
+            }
+        }
+
+        val autofillValues =
+            SparseArray().apply {
+                append(usernameTag.semanticsId(), AutofillValue.forText(expectedUsername))
+            }
+
+        // Custom autofill highlight color should not appear prior to autofill being performed
+        rule
+            .onNodeWithTag(usernameTag)
+            .captureToImage()
+            .assertDoesNotContainColor(customHighlightColor)
+
+        rule.runOnIdle { androidComposeView.autofill(autofillValues) }
+        rule.waitForIdle()
+
+        // Custom autofill highlight color should now appear
+        rule.onNodeWithTag(usernameTag).captureToImage().assertContainsColor(customHighlightColor)
+    }
+
     // ============================================================================================
     // Helper functions
     // ============================================================================================
@@ -192,6 +245,7 @@
             isSemanticAutofillEnabled = true
 
             composeView = LocalView.current
+            autofillHighlight = LocalAutofillHighlight.current
             LaunchedEffect(Unit) {
                 // Make sure the delay between batches of events is set to zero.
                 (composeView as RootForTest).setAccessibilityEventBatchIntervalMillis(0L)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt
index 50781fc..d209bec 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/window/DialogTest.kt
@@ -684,6 +684,25 @@
         }
     }
 
+    @Test
+    fun dialogDefaultGravityIsCenter() {
+        lateinit var view: View
+        rule.setContent {
+            Dialog(onDismissRequest = {}) {
+                view = LocalView.current
+                Box(Modifier.size(10.dp))
+            }
+        }
+        rule.runOnIdle {
+            var provider = view
+            while (provider !is DialogWindowProvider) {
+                provider = view.parent as View
+            }
+            val window = provider.window
+            assertThat(window.attributes.gravity).isEqualTo(Gravity.CENTER)
+        }
+    }
+
     private fun setupDialogTest(
         closeDialogOnDismiss: Boolean = true,
         dialogProperties: DialogProperties = DialogProperties(),
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
index e6e857d..7fdaf4a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
@@ -174,6 +174,12 @@
     }
 
     private fun notifyViewEntered(semanticsId: Int) {
+        // When a field is entered for the first time, a pop-up dialog should appear. Compose does
+        // not need to keep track of whether or not this is the first time a field is entered;
+        // the Autofill framework takes care of that. Compose simply calls `showAutofillDialog`
+        // each time a field is entered and the dialog appears if the appropriate conditions are
+        // met.
+        autofillManager.showAutofillDialog(semanticsId)
         currentSemanticsNodes[semanticsId]?.adjustedBounds?.let {
             autofillManager.notifyViewEntered(semanticsId, it)
         }
@@ -459,6 +465,8 @@
 
     fun notifyViewVisibilityChanged(semanticsId: Int, isVisible: Boolean)
 
+    fun showAutofillDialog(semanticsId: Int)
+
     fun commit()
 
     fun cancel()
@@ -493,6 +501,12 @@
         }
     }
 
+    override fun showAutofillDialog(semanticsId: Int) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            AutofillApi33Helper.showAutofillDialog(view, autofillManager, semanticsId)
+        }
+    }
+
     override fun commit() {
         autofillManager.commit()
     }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AutofillUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AutofillUtils.android.kt
index 6309c56..652ff0a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AutofillUtils.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AutofillUtils.android.kt
@@ -30,6 +30,19 @@
  * compiled. It is expected that this class will soft-fail verification, but the classes which use
  * this method will pass.
  */
+@RequiresApi(33)
+internal object AutofillApi33Helper {
+    @RequiresApi(33)
+    fun showAutofillDialog(view: View, autofillManager: AutofillManager, semanticsId: Int) {
+        autofillManager.showAutofillDialog(view, semanticsId)
+    }
+}
+
+/**
+ * This class is here to ensure that the classes that use this API will get verified and can be AOT
+ * compiled. It is expected that this class will soft-fail verification, but the classes which use
+ * this method will pass.
+ */
 @RequiresApi(28)
 internal object AutofillApi28Helper {
     @RequiresApi(28)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
index 3070f6e..54e65469 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
@@ -20,6 +20,7 @@
 import android.graphics.Outline
 import android.os.Build
 import android.view.ContextThemeWrapper
+import android.view.Gravity
 import android.view.KeyEvent
 import android.view.MotionEvent
 import android.view.View
@@ -449,6 +450,7 @@
         window.requestFeature(Window.FEATURE_NO_TITLE)
         window.setBackgroundDrawableResource(android.R.color.transparent)
         WindowCompat.setDecorFitsSystemWindows(window, properties.decorFitsSystemWindows)
+        window.setGravity(Gravity.CENTER)
 
         dialogLayout =
             DialogLayout(context, window).apply {
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
index d3330d3..74ee699 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
@@ -16,8 +16,8 @@
 
 package androidx.constraintlayout.compose.demos
 
-import android.app.Activity
 import androidx.activity.compose.BackHandler
+import androidx.activity.compose.LocalActivity
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
@@ -46,7 +46,6 @@
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
@@ -159,7 +158,7 @@
         }
     }
 
-    val activity = LocalContext.current as? Activity
+    val activity = LocalActivity.current
     // If there's a demo being displayed, return to demo list, otherwise, exit app
     BackHandler {
         if (displayedDemoIndex >= 0) {
diff --git a/gradle.properties b/gradle.properties
index 2acac36..80d48ae 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -71,13 +71,6 @@
 kotlin.stdlib.default.dependency=false
 # mac targets cannot be built on linux, suppress the warning.
 kotlin.native.ignoreDisabledTargets=true
-# Kotlin 2.0.20 will not warn when a compileOnly() dependency is declared in the common source set,
-# only when declared directly in a native source set. Until we can target Kotlin 2.0.21, disable
-# the warning here.
-# (see https://youtrack.jetbrains.com/issue/KT-64109 and
-# https://github.com/Kotlin/kotlinx-atomicfu/issues/376).
-# TODO: Remove when androidx moves to Kotlin 2.0.21, see b/373644574
-kotlin.native.ignoreIncorrectDependencies=true
 # Don't use maven for downloading native prebuilts: KT-64181 b/311215561
 # We should eventually set this to true and get rid of konan prebuilts
 # but it does not seem possible yet.
diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys
index ac7ebac..952ff56 100644
--- a/gradle/verification-keyring.keys
+++ b/gradle/verification-keyring.keys
@@ -8552,6 +8552,22 @@
 =wBmj
 -----END PGP PUBLIC KEY BLOCK-----
 
+pub    520410E2AF06FA82
+uid    Sam Judd 
+
+sub    04155300547530C4
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v@RELEASE_NAME@
+
+mDMEZOLxKBYJKwYBBAHaRw8BAQdAxs2w5zrEoMuboI/b+hJv8zlrWNbcg6coKPu9
+bJ5Ktfq0H1NhbSBKdWRkIDxzYW0uYS5qdWRkQGdtYWlsLmNvbT64OARk4vEoEgor
+BgEEAZdVAQUBAQdA2rpw9zPXi/Kvjlu9NYPouxk7dED9SE1T5G5KyPw8gwMDAQgH
+iH4EGBYKACYWIQTi9RpldnKEIgxt/WBSBBDirwb6ggUCZOLxKAIbDAUJBaOagAAK
+CRBSBBDirwb6gomdAQDcbCDu1+NttutpaDY0yyUNkDgGiFMIQhu1t5J4cCOj3gD9
+Eq/0QAoo6ZBLTyhMHEx96p4akwxOyNhUiOv0QsxtUQw=
+=Rppe
+-----END PGP PUBLIC KEY BLOCK-----
+
 pub    55C7E5E701832382
 -----BEGIN PGP PUBLIC KEY BLOCK-----
 Version: BCPG v@RELEASE_NAME@
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 51604e4..67d9132 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -526,6 +526,7 @@
          
          
          
+         
          
          
          
diff --git a/libraryversions.toml b/libraryversions.toml
index 99b1bb5c..6c18973 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -7,24 +7,24 @@
 ARCH_CORE = "2.3.0-alpha01"
 ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
 AUTOFILL = "1.3.0-beta01"
-BENCHMARK = "1.4.0-alpha03"
+BENCHMARK = "1.4.0-alpha04"
 BIOMETRIC = "1.4.0-alpha02"
 BLUETOOTH = "1.0.0-alpha02"
 BROWSER = "1.9.0-alpha01"
 BUILDSRC_TESTS = "1.0.0-alpha01"
-CAMERA = "1.5.0-alpha02"
+CAMERA = "1.5.0-alpha03"
 CAMERA_PIPE = "1.0.0-alpha01"
 CAMERA_TESTING = "1.0.0-alpha01"
 CAMERA_MEDIA3 = "1.0.0-alpha01"
-CAMERA_VIEWFINDER = "1.4.0-alpha09"
+CAMERA_VIEWFINDER = "1.4.0-alpha10"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-beta02"
-COLLECTION = "1.5.0-alpha04"
-COMPOSE = "1.8.0-alpha04"
+COLLECTION = "1.5.0-alpha05"
+COMPOSE = "1.8.0-alpha05"
 COMPOSE_MATERIAL3 = "1.4.0-alpha02"
 COMPOSE_MATERIAL3_ADAPTIVE = "1.1.0-alpha05"
 COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
-COMPOSE_RUNTIME = "1.8.0-alpha04"
+COMPOSE_RUNTIME = "1.8.0-alpha05"
 CONSTRAINTLAYOUT = "2.2.0-beta01"
 CONSTRAINTLAYOUT_COMPOSE = "1.1.0-beta01"
 CONSTRAINTLAYOUT_CORE = "1.1.0-beta01"
@@ -91,7 +91,7 @@
 LEANBACK_TAB = "1.1.0-beta01"
 LEGACY = "1.1.0-alpha01"
 LIBYUV = "0.1.0-dev01"
-LIFECYCLE = "2.9.0-alpha05"
+LIFECYCLE = "2.9.0-alpha06"
 LIFECYCLE_EXTENSIONS = "2.2.0"
 LINT = "1.0.0-alpha02"
 LOADER = "1.2.0-alpha01"
@@ -117,9 +117,9 @@
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
 REMOTECALLBACK = "1.0.0-alpha02"
 RESOURCEINSPECTION = "1.1.0-alpha01"
-ROOM = "2.7.0-alpha10"
+ROOM = "2.7.0-alpha11"
 SAFEPARCEL = "1.0.0-alpha01"
-SAVEDSTATE = "1.3.0-alpha03"
+SAVEDSTATE = "1.3.0-alpha04"
 SECURITY = "1.1.0-alpha07"
 SECURITY_APP_AUTHENTICATOR = "1.0.0-rc01"
 SECURITY_APP_AUTHENTICATOR_TESTING = "1.0.0-rc01"
@@ -133,7 +133,7 @@
 SLICE_BUILDERS_KTX = "1.0.0-alpha09"
 SLICE_REMOTECALLBACK = "1.0.0-alpha01"
 SLIDINGPANELAYOUT = "1.3.0-alpha01"
-SQLITE = "2.5.0-alpha10"
+SQLITE = "2.5.0-alpha11"
 SQLITE_INSPECTOR = "2.1.0-alpha01"
 STABLE_AIDL = "1.0.0-alpha01"
 STARTUP = "1.2.0-rc01"
@@ -169,8 +169,8 @@
 WEAR_WATCHFACE = "1.3.0-alpha04"
 WEBKIT = "1.13.0-alpha01"
 # Adding a comment to prevent merge conflicts for Window artifact
-WINDOW = "1.4.0-alpha05"
-WINDOW_EXTENSIONS = "1.4.0-beta01"
+WINDOW = "1.4.0-alpha06"
+WINDOW_EXTENSIONS = "1.4.0-rc01"
 WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
 WINDOW_SIDECAR = "1.0.0-rc01"
 WORK = "2.10.0-rc01"
diff --git a/lifecycle/lifecycle-common/src/commonMain/kotlin/androidx/lifecycle/Lifecycle.kt b/lifecycle/lifecycle-common/src/commonMain/kotlin/androidx/lifecycle/Lifecycle.kt
index 34ec279..a78d6b5c 100644
--- a/lifecycle/lifecycle-common/src/commonMain/kotlin/androidx/lifecycle/Lifecycle.kt
+++ b/lifecycle/lifecycle-common/src/commonMain/kotlin/androidx/lifecycle/Lifecycle.kt
@@ -355,9 +355,15 @@
 public val Lifecycle.eventFlow: Flow
     get() =
         callbackFlow {
-                val observer =
-                    LifecycleEventObserver { _, event -> trySend(event) }.also { addObserver(it) }
+                val observer = LifecycleEventObserver { _, event ->
+                    trySend(event)
 
+                    // Completes the producer if lifecycle is `DESTROYED`.
+                    if (event == Lifecycle.Event.ON_DESTROY) {
+                        close()
+                    }
+                }
+                addObserver(observer)
                 awaitClose { removeObserver(observer) }
             }
             .flowOn(Dispatchers.Main.immediate)
diff --git a/lifecycle/lifecycle-runtime-testing/src/commonTest/kotlin/androidx/lifecycle/testing/LifecycleEventFlowTest.kt b/lifecycle/lifecycle-runtime-testing/src/commonTest/kotlin/androidx/lifecycle/testing/LifecycleEventFlowTest.kt
index c69fffe..385472d 100644
--- a/lifecycle/lifecycle-runtime-testing/src/commonTest/kotlin/androidx/lifecycle/testing/LifecycleEventFlowTest.kt
+++ b/lifecycle/lifecycle-runtime-testing/src/commonTest/kotlin/androidx/lifecycle/testing/LifecycleEventFlowTest.kt
@@ -62,10 +62,13 @@
         testScope.runTest {
             val collectedEvents = mutableListOf()
             val lifecycleEventFlow = owner.lifecycle.eventFlow
-            backgroundScope.launch { lifecycleEventFlow.collect { collectedEvents.add(it) } }
+            val job =
+                backgroundScope.launch { lifecycleEventFlow.collect { collectedEvents.add(it) } }
+
             owner.currentState = Lifecycle.State.CREATED
             owner.currentState = Lifecycle.State.DESTROYED
-            owner.currentState = Lifecycle.State.RESUMED
+
+            assertThat(job.isCompleted).isTrue()
             assertThat(collectedEvents)
                 .containsExactly(
                     Lifecycle.Event.ON_CREATE,
diff --git a/lifecycle/lifecycle-runtime/src/androidUnitTest/kotlin/androidx/lifecycle/LifecycleRegistryTest.java b/lifecycle/lifecycle-runtime/src/androidUnitTest/kotlin/androidx/lifecycle/LifecycleRegistryTest.java
index ceec6d4..5a4aa40 100644
--- a/lifecycle/lifecycle-runtime/src/androidUnitTest/kotlin/androidx/lifecycle/LifecycleRegistryTest.java
+++ b/lifecycle/lifecycle-runtime/src/androidUnitTest/kotlin/androidx/lifecycle/LifecycleRegistryTest.java
@@ -69,8 +69,21 @@
             mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
         } catch (IllegalStateException e) {
             assertThat(e.getMessage(),
-                    is("State must be at least CREATED to move to DESTROYED, but was "
-                            + "INITIALIZED in component " + mLifecycleOwner));
+                    is("State must be at least 'CREATED' to be moved to 'DESTROYED' in "
+                            + "component " + mLifecycleOwner));
+        }
+    }
+
+    @Test
+    public void moveDestroyedToAny() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+        try {
+            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(),
+                    is("State is 'DESTROYED' and cannot be moved to `CREATED` in "
+                            + "component " + mLifecycleOwner));
         }
     }
 
diff --git a/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/LifecycleRegistry.kt b/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/LifecycleRegistry.kt
index 6b4de36..b6f34fe 100644
--- a/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/LifecycleRegistry.kt
+++ b/lifecycle/lifecycle-runtime/src/commonMain/kotlin/androidx/lifecycle/LifecycleRegistry.kt
@@ -17,6 +17,7 @@
 
 import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle.State
 import kotlin.jvm.JvmStatic
 
 /**
@@ -75,3 +76,23 @@
         public fun createUnsafe(owner: LifecycleOwner): LifecycleRegistry
     }
 }
+
+/**
+ * Checks the [Lifecycle.State] of a component and throws an error if an invalid state transition is
+ * detected.
+ *
+ * @param owner The [LifecycleOwner] holding the [Lifecycle] of the component.
+ * @param current The current [Lifecycle.State] of the component.
+ * @param next The desired next [Lifecycle.State] of the component.
+ * @throws IllegalStateException if the component is in an invalid state for the desired transition.
+ */
+internal fun checkLifecycleStateTransition(owner: LifecycleOwner?, current: State, next: State) {
+    if (current == State.INITIALIZED && next == State.DESTROYED) {
+        error(
+            "State must be at least '${State.CREATED}' to be moved to '$next' in component $owner"
+        )
+    }
+    if (current == State.DESTROYED && current != next) {
+        error("State is '${State.DESTROYED}' and cannot be moved to `$next` in component $owner")
+    }
+}
diff --git a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/CommonLifecycleRegistryTest.kt b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/CommonLifecycleRegistryTest.kt
index 4dd4dc1..0d5b045 100644
--- a/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/CommonLifecycleRegistryTest.kt
+++ b/lifecycle/lifecycle-runtime/src/commonTest/kotlin/androidx/lifecycle/CommonLifecycleRegistryTest.kt
@@ -17,6 +17,7 @@
 package androidx.lifecycle
 
 import androidx.kruth.assertThat
+import androidx.kruth.assertThrows
 import kotlin.test.BeforeTest
 import kotlin.test.Test
 
@@ -44,15 +45,28 @@
 
     @Test
     fun moveInitializedToDestroyed() {
-        try {
-            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
-        } catch (e: IllegalStateException) {
-            assertThat(e.message)
-                .isEqualTo(
-                    "State must be at least CREATED to move to DESTROYED, " +
-                        "but was INITIALIZED in component $mLifecycleOwner"
-                )
-        }
+        assertThrows {
+                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+            }
+            .hasMessageThat()
+            .contains(
+                "State must be at least 'CREATED' to be moved to 'DESTROYED' in component " +
+                    "$mLifecycleOwner"
+            )
+    }
+
+    @Test
+    fun moveDestroyedToAny() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+        assertThrows {
+                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+            }
+            .hasMessageThat()
+            .contains(
+                "State is 'DESTROYED' and cannot be moved to `CREATED` in component " +
+                    "$mLifecycleOwner"
+            )
     }
 
     @Test
diff --git a/lifecycle/lifecycle-runtime/src/jvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.jvm.kt b/lifecycle/lifecycle-runtime/src/jvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.jvm.kt
index 38f21ec..798d9da 100644
--- a/lifecycle/lifecycle-runtime/src/jvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.jvm.kt
+++ b/lifecycle/lifecycle-runtime/src/jvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.jvm.kt
@@ -123,10 +123,8 @@
         if (state == next) {
             return
         }
-        check(!(state == State.INITIALIZED && next == State.DESTROYED)) {
-            "State must be at least CREATED to move to $next, but was $state in component " +
-                "${lifecycleOwner.get()}"
-        }
+        checkLifecycleStateTransition(lifecycleOwner.get(), state, next)
+
         state = next
         if (handlingEvent || addingObserverCounter != 0) {
             newEventOccurred = true
diff --git a/lifecycle/lifecycle-runtime/src/nonJvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.nonJvm.kt b/lifecycle/lifecycle-runtime/src/nonJvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.nonJvm.kt
index bb30846..a5507f7 100644
--- a/lifecycle/lifecycle-runtime/src/nonJvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.nonJvm.kt
+++ b/lifecycle/lifecycle-runtime/src/nonJvmCommonMain/kotlin/androidx/lifecycle/LifecycleRegistry.nonJvm.kt
@@ -107,10 +107,8 @@
         if (state == next) {
             return
         }
-        check(!(state == State.INITIALIZED && next == State.DESTROYED)) {
-            "State must be at least CREATED to move to $next, but was $state in component " +
-                "${lifecycleOwner.get()}"
-        }
+        checkLifecycleStateTransition(lifecycleOwner.get(), state, next)
+
         state = next
         if (handlingEvent || addingObserverCounter != 0) {
             newEventOccurred = true
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 d6426a7..38b8829 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
@@ -16,6 +16,7 @@
 
 package androidx.navigation.compose.samples
 
+import android.net.Uri
 import android.os.Bundle
 import android.os.Parcelable
 import androidx.annotation.Sampled
@@ -60,9 +61,16 @@
 import androidx.navigation.compose.navigation
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.toRoute
+import java.util.UUID
+import kotlin.reflect.typeOf
 import kotlinx.parcelize.Parcelize
+import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
-import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
 import kotlinx.serialization.json.Json
 
 @Serializable
@@ -308,6 +316,48 @@
     }
 }
 
+@Composable
+@Preview
+fun ThirdPartySerializableType() {
+    val navController = rememberNavController()
+    NavHost(navController, startDestination = User(WrappedUUID(UUID.randomUUID()))) {
+        composable(typeMap = mapOf(typeOf() to WrappedUUIDNavType)) { entry ->
+            val uuid = entry.toRoute().id.uuid
+            Box(Modifier.fillMaxSize().background(Color.Blue)) {
+                Text(text = "uuid: $uuid", modifier = Modifier.testTag("text"))
+            }
+        }
+    }
+}
+
+@Serializable class User(val id: WrappedUUID)
+
+@Serializable class WrappedUUID(@Serializable(with = UUIDSerializer::class) val uuid: UUID)
+
+object WrappedUUIDNavType : NavType(isNullableAllowed = false) {
+    override fun put(bundle: Bundle, key: String, value: WrappedUUID) {
+        bundle.putString(key, value.uuid.toString())
+    }
+
+    override fun get(bundle: Bundle, key: String): WrappedUUID? =
+        WrappedUUID(UUID.fromString(bundle.getString(key)))
+
+    override fun parseValue(value: String): WrappedUUID = WrappedUUID(UUID.fromString(value))
+
+    override fun serializeAsValue(value: WrappedUUID): String = Uri.encode(value.uuid.toString())
+}
+
+object UUIDSerializer : KSerializer {
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
+
+    override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
+
+    override fun serialize(encoder: Encoder, value: UUID) {
+        encoder.encodeString(value.toString())
+    }
+}
+
 private val phrases =
     listOf(
         "Easy As Pie",
diff --git a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
index ed18f94..3fcb721 100644
--- a/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
+++ b/pdf/integration-tests/testapp/src/androidTest/kotlin/androidx/pdf/PdfViewerFragmentTestSuite.kt
@@ -160,7 +160,6 @@
         onView(withId(R.id.search_container)).check(matches(isDisplayed()))
 
         onView(withId(R.id.find_query_box)).perform(typeText(SEARCH_QUERY))
-        Thread.sleep(DELAY_TIME_MS)
         onView(withId(R.id.match_status_textview)).check(matches(isDisplayed()))
         onView(withId(R.id.match_status_textview)).check(searchViewAssertion.extractAndMatch())
 
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java
index 05d927c..45d4354 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/find/FindInFileView.java
@@ -114,10 +114,17 @@
                         newMatchCount.mSelectedIndex + 1,
                         // Zero-based - change to one-based for user.
                         newMatchCount.mTotalMatches);
+
+                String matchStatusDescription =
+                        getContext().getString(R.string.match_status_description,
+                        newMatchCount.mSelectedIndex + 1,
+                        //Zero-based - change to one-based for user.
+                        newMatchCount.mTotalMatches);
+
                 if (newMatchCount.mIsAllPagesCounted) {
                     if (newMatchCount.mSelectedIndex >= 0) {
                         Accessibility.get().announce(getContext(), FindInFileView.this,
-                                matchStatusText);
+                                matchStatusDescription);
                     }
                     else {
                         Accessibility.get().announce(getContext(), FindInFileView.this,
diff --git a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
index d8586f9..d9f6557 100644
--- a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
@@ -47,7 +47,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignEnd="@+id/query_box"
-            android:paddingRight="10dp"
+            android:paddingStart="8dp"
+            android:paddingEnd="10dp"
             android:textColor="?attr/colorOnSurfaceVariant"
             android:textAppearance="?attr/textAppearanceTitleSmall"
             android:layout_gravity="center_vertical">
diff --git a/pdf/pdf-viewer/src/main/res/values-af/strings.xml b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
index 10839ec..f62de4d 100644
--- a/pdf/pdf-viewer/src/main/res/values-af/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Hierdie lêer is beskerm"
     "Wagwoord"
-    
-    
+    "Verkeerde wagwoord. Probeer weer."
     "Kanselleer"
     "Maak oop"
     "wagwoordveld"
     "wagwoord verkeerd"
-    
-    
+    "Verkeerde wagwoord. Probeer weer."
     "%1$d / %2$d"
     "bladsy %1$d van %2$d"
     "zoem %1$d persent"
diff --git a/pdf/pdf-viewer/src/main/res/values-am/strings.xml b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
index 97d9b07..9251d8b 100644
--- a/pdf/pdf-viewer/src/main/res/values-am/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "ይህ ፋይል የተጠበቀ ነው"
     "የይለፍ ቃል"
-    
-    
+    "የተሳሳተ የይለፍ ቃል። እንደገና ይሞክሩ።"
     "ይቅር"
     "ክፈት"
     "የይለፍ ቃል መስክ"
     "የይለፍ ቃል ትክክል አይደለም"
-    
-    
+    "የተሳሳተ የይለፍ ቃል። እንደገና ይሞክሩ።"
     "%1$d / %2$d"
     "ገጽ %1$d%2$d"
     "%1$d በመቶ ያጉሉ"
diff --git a/pdf/pdf-viewer/src/main/res/values-az/strings.xml b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
index 5227846..56f789c 100644
--- a/pdf/pdf-viewer/src/main/res/values-az/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Bu fayl qorunur"
     "Parol"
-    
-    
+    "Yanlış parol. Yenidən cəhd edin."
     "Ləğv edin"
     "Açın"
     "parol sahəsi"
     "parol səhvdir"
-    
-    
+    "Yanlış parol. Yenidən cəhd edin."
     "%1$d / %2$d"
     "səhifə %1$d / %2$d"
     "%1$d faiz zum"
diff --git a/pdf/pdf-viewer/src/main/res/values-be/strings.xml b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
index 4129be0..e5246b7 100644
--- a/pdf/pdf-viewer/src/main/res/values-be/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Гэты файл абаронены"
     "Пароль"
-    
-    
+    "Няправільны пароль. Паўтарыце спробу."
     "Скасаваць"
     "Адкрыць"
     "поле ўводу пароля"
     "няправільны пароль"
-    
-    
+    "Няправільны пароль. Паўтарыце спробу."
     "%1$d з %2$d"
     "старонка %1$d з %2$d"
     "маштаб %1$d%%"
diff --git a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
index b8c23c7..1963288 100644
--- a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Този файл е защитен"
     "Парола"
-    
-    
+    "Грешна парола. Опитайте отново."
     "Отказ"
     "Отваряне"
     "поле за парола"
     "неправилна парола"
-    
-    
+    "Грешна парола. Опитайте отново."
     "%1$d/%2$d"
     "страница %1$d от %2$d"
     "Процент на промяна на мащаба: %1$d"
diff --git a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
index 0b1bbae..7e9b6a8 100644
--- a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
@@ -30,8 +30,7 @@
     "%1$d শতাংশ জুম করুন"
     "%1$d নম্বর পৃষ্ঠায় যান"
     "পৃষ্ঠা %1$d"
-    
-    
+    "খালি পৃষ্ঠা"
     "পিডিএফ দেখানো যাবে না (%1$s ভুল ফর্ম্যাটে আছে)"
     "%1$d পৃষ্ঠা দেখানো যাবে না (ফাইলে সমস্যা)"
     "লিঙ্ক: %1$s-এ ওয়েবপেজ"
diff --git a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
index 4a50a63..6e2be84 100644
--- a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
@@ -19,12 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Fajl je zaštićen"
     "Lozinka"
-    "Pogrešna zaporka. Pokušajte ponovo."
+    "Pogrešna lozinka Pokušajte ponovo."
     "Otkaži"
     "Otvori"
     "polje za lozinku"
     "pogrešna lozinka"
-    "Pogrešna zaporka. Pokušajte ponovo."
+    "Pogrešna lozinka Ponovni pokušaj."
     "%1$d/%2$d"
     "%1$d. stranica od %2$d"
     "zumiranje %1$d posto"
diff --git a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
index e11d799..e5237f2 100644
--- a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Aquest fitxer està protegit"
     "Contrasenya"
-    
-    
+    "Contrasenya incorrecta. Torna-ho a provar."
     "Cancel·la"
     "Obre"
     "camp de contrasenya"
     "contrasenya incorrecta"
-    
-    
+    "Contrasenya incorrecta. Torna-ho a provar."
     "%1$d/%2$d"
     "pàgina %1$d de %2$d"
     "zoom %1$d per cent"
diff --git a/pdf/pdf-viewer/src/main/res/values-da/strings.xml b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
index c6d4d93..120231b 100644
--- a/pdf/pdf-viewer/src/main/res/values-da/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Denne fil er beskyttet"
     "Adgangskode"
-    
-    
+    "Forkert adgangskode. Prøv igen."
     "Annuller"
     "Åbn"
     "felt til adgangskode"
     "adgangskoden er forkert"
-    
-    
+    "Forkert adgangskode. Prøv igen."
     "%1$d/%2$d"
     "side %1$d af %2$d"
     "zoom %1$d %%"
diff --git a/pdf/pdf-viewer/src/main/res/values-de/strings.xml b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
index afdce21d2..8af7f80 100644
--- a/pdf/pdf-viewer/src/main/res/values-de/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Diese Datei ist geschützt"
     "Passwort"
-    
-    
+    "Falsches Passwort. Versuch es nochmal."
     "Abbrechen"
     "Öffnen"
     "Passwortfeld"
     "Falsches Passwort"
-    
-    
+    "Falsches Passwort. Versuch es nochmal."
     "%1$d/%2$d"
     "Seite %1$d von %2$d"
     "Zoom: %1$d %%"
     "Gehe zu Seite %1$d"
     "Seite %1$d"
-    
-    
+    "Leere Seite"
     "Anzeige von PDF nicht möglich („%1$s“ hat ungültiges Dateiformat)"
     "Anzeige von Seite %1$d nicht möglich (Dateifehler)"
     "Link: Webseite unter %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
index ed44253..b4f73b8 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "This file is protected"
     "Password"
-    
-    
+    "Wrong password. Try again."
     "Cancel"
     "Open"
     "password field"
     "password incorrect"
-    
-    
+    "Wrong password. Try again."
     "%1$d/%2$d"
     "page %1$d of %2$d"
     "zoom %1$d per cent"
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
index ed44253..b4f73b8 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "This file is protected"
     "Password"
-    
-    
+    "Wrong password. Try again."
     "Cancel"
     "Open"
     "password field"
     "password incorrect"
-    
-    
+    "Wrong password. Try again."
     "%1$d/%2$d"
     "page %1$d of %2$d"
     "zoom %1$d per cent"
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
index ed44253..b4f73b8 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "This file is protected"
     "Password"
-    
-    
+    "Wrong password. Try again."
     "Cancel"
     "Open"
     "password field"
     "password incorrect"
-    
-    
+    "Wrong password. Try again."
     "%1$d/%2$d"
     "page %1$d of %2$d"
     "zoom %1$d per cent"
diff --git a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
index 4cd0c72..ecf2f42 100644
--- a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
@@ -30,8 +30,7 @@
     "zoom %1$d por ciento"
     "Ir a la página %1$d"
     "página %1$d"
-    
-    
+    "Página vacía"
     "No se puede mostrar el PDF (%1$s tiene un formato no válido)"
     "No se puede mostrar la página %1$d (error de archivo)"
     "Vínculo: página web en %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-es/strings.xml b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
index c1e1b81..48a146f 100644
--- a/pdf/pdf-viewer/src/main/res/values-es/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Este archivo está protegido"
     "Contraseña"
-    
-    
+    "Contraseña incorrecta. Reinténtalo."
     "Cancelar"
     "Abrir"
     "campo de contraseña"
     "contraseña incorrecta"
-    
-    
+    "Contraseña incorrecta. Inténtalo de nuevo."
     "%1$d/%2$d"
     "página %1$d de %2$d"
     "zoom %1$d por ciento"
     "Ir a la página %1$d"
     "página %1$d"
-    
-    
+    "Página en blanco"
     "No se puede mostrar el PDF (el formato de %1$s no es válido)"
     "No se puede mostrar la página %1$d (error de archivo)"
     "Enlace: página web en %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
index a1ebeb0..c408c2b 100644
--- a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Fitxategi hau babestuta dago"
     "Pasahitza"
-    
-    
+    "Pasahitza ez da zuzena. Saiatu berriro."
     "Utzi"
     "Ireki"
     "pasahitzaren eremua"
     "pasahitza okerra da"
-    
-    
+    "Pasahitza ez da zuzena. Saiatu berriro."
     "%1$d/%2$d"
     "%2$d orritatik %1$dgarrena"
     "zooma ehuneko %1$d"
diff --git a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
index 5a53c7a..e017f32 100644
--- a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "این فایل محافظت شده است"
     "گذرواژه"
-    
-    
+    "گذرواژه اشتباه است. دوباره امتحان کنید."
     "لغو کردن"
     "باز کردن"
     "فیلد گذرواژه"
     "گذرواژه نادرست است"
-    
-    
+    "گذرواژه اشتباه است. دوباره امتحان کنید."
     "‫%1$d / %2$d"
     "صفحه %1$d از %2$d"
     "بزرگ‌نمایی %1$d درصد"
diff --git a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
index 2b9ae50..9b9eb0a 100644
--- a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Tämä tiedosto on suojattu"
     "Salasana"
-    
-    
+    "Väärä salasana. Yritä uudelleen."
     "Peruuta"
     "Avaa"
     "salasanakenttä"
     "väärä salasana"
-    
-    
+    "Väärä salasana. Yritä uudelleen."
     "%1$d / %2$d"
     "sivu %1$d/%2$d"
     "zoomaus %1$d prosenttia"
diff --git a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
index 6fe787f..564ac9f 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Ce fichier est protégé"
     "Mot de passe"
-    
-    
+    "Mot de passe incorrect. Réessayez."
     "Annuler"
     "Ouvrir"
     "champ du mot de passe"
     "mot de passe incorrect"
-    
-    
+    "Mot de passe incorrect. Réessayez."
     "%1$d/%2$d"
     "page %1$d sur %2$d"
     "zoom %1$d pour cent"
     "Accéder à la page %1$d"
     "page %1$d"
-    
-    
+    "Page vide"
     "Impossible d\'afficher le PDF (format de %1$s non valide)"
     "Impossible d\'afficher la page %1$d (erreur de fichier)"
     "Lien : page Web sur %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
index 537248b..f8067f1 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Ce fichier est protégé"
     "Mot de passe"
-    
-    
+    "Mot de passe incorrect. Réessayez."
     "Annuler"
     "Ouvrir"
     "champ de mot de passe"
     "mot de passe incorrect"
-    
-    
+    "Mot de passe incorrect. Réessayez."
     "%1$d/%2$d"
     "page %1$d sur %2$d"
     "zoom : %1$d pour cent"
     "Accéder à la page %1$d"
     "page %1$d"
-    
-    
+    "Page vide"
     "Impossible d\'afficher le PDF (format non valide pour %1$s)"
     "Impossible d\'afficher la page %1$d (erreur de fichier)"
     "Lien : page Web du domaine %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
index 9d7609d..e9996c3 100644
--- a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Este ficheiro está protexido"
     "Contrasinal"
-    
-    
+    "O contrasinal é incorrecto. Téntao de novo."
     "Cancelar"
     "Abrir"
     "campo do contrasinal"
     "contrasinal incorrecto"
-    
-    
+    "O contrasinal é incorrecto. Téntao de novo."
     "%1$d/%2$d"
     "páxina %1$d de %2$d"
     "zoom ao %1$d por cento"
diff --git a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
index 0d49081..1904222 100644
--- a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "यह फ़ाइल सुरक्षित है"
     "पासवर्ड"
-    
-    
+    "पासवर्ड गलत है. फिर से कोशिश करें."
     "रद्द करें"
     "खोलें"
     "पासवर्ड फ़ील्ड"
     "पासवर्ड गलत है"
-    
-    
+    "पासवर्ड गलत है. फिर से कोशिश करें."
     "%1$d / %2$d"
     "%2$d में से %1$d नंबर का पेज"
     "%1$d प्रतिशत ज़ूम करें"
diff --git a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
index 5c88617..13cefe9 100644
--- a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "A fájl védett"
     "Jelszó"
-    
-    
+    "Helytelen jelszó. Próbálkozzon újra."
     "Mégse"
     "Megnyitás"
     "jelszómező"
     "helytelen jelszó"
-    
-    
+    "Helytelen jelszó. Próbálkozzon újra."
     "%2$d/%1$d."
     "%2$d/%1$d. oldal"
     "%1$d százalékos nagyítás"
diff --git a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
index 91fdd5a..53b5c63 100644
--- a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Այս ֆայլը պաշտպանված է"
     "Գաղտնաբառ"
-    
-    
+    "Գաղտնաբառը սխալ է։ Նորից փորձեք։"
     "Չեղարկել"
     "Բացել"
     "գաղտնաբառի դաշտ"
     "գաղտնաբառը սխալ է"
-    
-    
+    "Գաղտնաբառը սխալ է։ Նորից փորձեք։"
     "%1$d/%2$d"
     "Էջ %1$d/%2$d"
     "մասշտաբ՝ %1$d տոկոս"
     "Անցեք %1$d էջ"
     "էջ %1$d"
-    
-    
+    "Դատարկ էջ"
     "Հնարավոր չէ ցուցադրել PDF-ը (%1$s ֆայլը անվավեր ձևաչափ ունի)"
     "Հնարավոր չէ ցուցադրել էջ %1$d-ը (ֆայլի սխալ)"
     "Հղում՝ կայքէջ %1$s-ում"
diff --git a/pdf/pdf-viewer/src/main/res/values-in/strings.xml b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
index 69f12ed..d1c6170b 100644
--- a/pdf/pdf-viewer/src/main/res/values-in/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "File ini dilindungi"
     "Sandi"
-    
-    
+    "Sandi salah. Coba lagi."
     "Batal"
     "Buka"
     "kolom sandi"
     "sandi salah"
-    
-    
+    "Sandi salah. Coba lagi."
     "%1$d/%2$d"
     "halaman %1$d dari %2$d"
     "zoom %1$d persen"
diff --git a/pdf/pdf-viewer/src/main/res/values-is/strings.xml b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
index 902fdca..845c448 100644
--- a/pdf/pdf-viewer/src/main/res/values-is/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Þessi skrá er varin"
     "Aðgangsorð"
-    
-    
+    "Rangt aðgangsorð. Reyndu aftur."
     "Hætta við"
     "Opna"
     "reitur fyrir aðgangsorð"
     "rangt aðgangsorð"
-    
-    
+    "Rangt aðgangsorð. Reyndu aftur."
     "%1$d / %2$d"
     "síða %1$d af %2$d"
     "stækka/minnka %1$d prósent"
diff --git a/pdf/pdf-viewer/src/main/res/values-it/strings.xml b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
index 74ac139..bea8957 100644
--- a/pdf/pdf-viewer/src/main/res/values-it/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Questo file è protetto"
     "Password"
-    
-    
+    "Password errata. Riprova."
     "Annulla"
     "Apri"
     "campo password"
     "password errata"
-    
-    
+    "Password errata. Riprova."
     "%1$d/%2$d"
     "pagina %1$d di %2$d"
     "zoom %1$d%%"
     "Vai alla pagina %1$d"
     "pagina %1$d"
-    
-    
+    "Pagina vuota"
     "Impossibile visualizzare PDF (%1$s è in un formato non valido)"
     "Impossibile visualizzare la pagina %1$d (errore del file)"
     "Link: pagina web all\'indirizzo %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
index c39ccba..e7adcaa3 100644
--- a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "הקובץ הזה מוגן"
     "סיסמה"
-    
-    
+    "הסיסמה שגויה. אפשר לנסות שוב."
     "ביטול"
     "פתיחה"
     "שדה הסיסמה"
     "הסיסמה שגויה"
-    
-    
+    "הסיסמה שגויה. אפשר לנסות שוב."
     "‎%1$d / %2$d‫‎"
     "דף %1$d מתוך %2$d"
     "שינוי מרחק התצוגה ב-%1$d אחוזים"
diff --git a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
index 84be70b..6640153 100644
--- a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Бұл файл қорғалған"
     "Құпия сөз"
-    
-    
+    "Құпия сөз қате. Қайталап көріңіз."
     "Бас тарту"
     "Ашу"
     "құпия сөз өрісі"
     "құпия сөз қате"
-    
-    
+    "Құпия сөз қате. Қайталап көріңіз."
     "%1$d/%2$d"
     "Бет: %1$d/%2$d"
     "%1$d пайызға масштабтау"
diff --git a/pdf/pdf-viewer/src/main/res/values-km/strings.xml b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
index 5662d5e..11cd884 100644
--- a/pdf/pdf-viewer/src/main/res/values-km/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "ឯកសារនេះត្រូវបានការពារ"
     "ពាក្យសម្ងាត់"
-    
-    
+    "ពាក្យសម្ងាត់ខុស។ សូមព្យាយាមម្ដងទៀត។"
     "បោះបង់"
     "បើក"
     "កន្លែងបញ្ចូលពាក្យ​សម្ងាត់"
     "ពាក្យសម្ងាត់​មិនត្រឹមត្រូវ"
-    
-    
+    "ពាក្យសម្ងាត់ខុស។ សូមព្យាយាមម្ដងទៀត។"
     "%1$d / %2$d"
     "ទំព័រទី %1$d នៃ %2$d"
     "ពង្រីកបង្រួម %1$d ភាគរយ"
diff --git a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
index d9c9253..0726b77 100644
--- a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "보호된 파일임"
     "비밀번호"
-    
-    
+    "잘못된 비밀번호입니다. 다시 시도하세요."
     "취소"
     "열기"
     "비밀번호 입력란"
     "비밀번호가 잘못됨"
-    
-    
+    "잘못된 비밀번호입니다. 다시 시도하세요."
     "%1$d/%2$d"
     "%2$d페이지 중 %1$d페이지"
     "%1$d%% 확대/축소"
diff --git a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
index d9f9661..6eee324 100644
--- a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "ໄຟລ໌ນີ້ຖືກປ້ອງກັນໄວ້"
     "ລະຫັດຜ່ານ"
-    
-    
+    "ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ. ກະລຸນາລອງໃໝ່."
     "ຍົກເລີກ"
     "ເປີດ"
     "ຊ່ອງຂໍ້ມູນລະຫັດຜ່ານ"
     "ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ"
-    
-    
+    "ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ. ກະລຸນາລອງໃໝ່."
     "%1$d / %2$d"
     "ໜ້າທີ %1$d ຈາກທັງໝົດ %2$d ໜ້າ"
     "ຊູມ %1$d ເປີເຊັນ"
diff --git a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
index 85fd702..44ef381 100644
--- a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Šis failas yra apsaugotas"
     "Slaptažodis"
-    
-    
+    "Netinkamas slaptažodis. Bandykite dar kartą."
     "Atšaukti"
     "Atidaryti"
     "slaptažodžio laukas"
     "slaptažodis netinkamas"
-    
-    
+    "Netinkamas slaptažodis. Bandykite dar kartą."
     "%1$d%2$d"
     "%1$d psl. iš %2$d"
     "keisti mastelį %1$d proc."
diff --git a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
index 8c4557e..62b43d2 100644
--- a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Šis fails ir aizsargāts"
     "Parole"
-    
-    
+    "Nepareiza parole. Mēģiniet vēlreiz."
     "Atcelt"
     "Atvērt"
     "paroles lauks"
     "parole nav pareiza"
-    
-    
+    "Nepareiza parole. Mēģiniet vēlreiz."
     "%1$d/%2$d"
     "%1$d. lapa no %2$d"
     "tālummaiņa procentos ir %1$d"
diff --git a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
index 5d794fe..86424ba 100644
--- a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Датотекава е заштитена"
     "Лозинка"
-    
-    
+    "Погрешна лозинка. Обидете се повторно."
     "Откажи"
     "Отвори"
     "поле за лозинка"
     "лозинката е неточна"
-    
-    
+    "Погрешна лозинка. Обидете се повторно."
     "%1$d/%2$d"
     "страница %1$d од %2$d"
     "зумирајте %1$d проценти"
     "Одете на страницата %1$d"
     "страница %1$d"
-    
-    
+    "Празна страница"
     "Не може да се прикаже PDF (%1$s е со неважечки формат)"
     "Не може да се прикаже страницата %1$d (грешка во датотеката)"
     "Линк: веб-страница на %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
index aa5115e..eee4b83 100644
--- a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Энэ файл хамгаалагдсан"
     "Нууц үг"
-    
-    
+    "Нууц үг буруу байна. Дахин оролдоно уу."
     "Цуцлах"
     "Нээх"
     "нууц үгний талбар"
     "нууц үг буруу байна"
-    
-    
+    "Нууц үг буруу байна. Дахин оролдоно уу."
     "%2$d%1$d"
     "Нийт %2$d хуудасны %1$d-р хуудас"
     "%1$d хувь томруулсан"
     "%1$d-р хуудсанд очих"
     "%1$d-р хуудас"
-    
-    
+    "Хоосон хуудас"
     "PDF-г үзүүлэх боломжгүй (%1$s-н формат буруу байна)"
     "%1$d хуудсыг үзүүлэх боломжгүй (файлын алдаа)"
     "Холбоос: %1$s дээрх веб хуудас"
diff --git a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
index bc8d2b3..7d642d7 100644
--- a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Fail ini dilindungi"
     "Kata laluan"
-    
-    
+    "Kata laluan salah. Cuba lagi."
     "Batal"
     "Buka"
     "medan kata laluan"
     "kata laluan salah"
-    
-    
+    "Kata laluan salah. Cuba lagi."
     "%1$d / %2$d"
     "halaman %1$d daripada %2$d"
     "zum %1$d peratus"
diff --git a/pdf/pdf-viewer/src/main/res/values-my/strings.xml b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
index a2566ac..e642765 100644
--- a/pdf/pdf-viewer/src/main/res/values-my/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "ဤဖိုင်ကို ကာကွယ်ထားသည်"
     "စကားဝှက်"
-    
-    
+    "စကားဝှက် မှားနေသည်။ ထပ်စမ်းကြည့်ပါ။"
     "မလုပ်တော့"
     "ဖွင့်ရန်"
     "စကားဝှက်ထည့်ရန် အကွက်"
     "စကားဝှက် မှားနေသည်"
-    
-    
+    "စကားဝှက် မှားနေသည်။ ထပ်စမ်းကြည့်ပါ။"
     "%1$d / %2$d"
     "စာမျက်နှာ %2$d အနက် %1$d"
     "ဇူးမ် %1$d ရာခိုင်နှုန်း"
diff --git a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
index 85659cc..163e356 100644
--- a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Denne filen er beskyttet"
     "Passord"
-    
-    
+    "Feil passord. Prøv på nytt."
     "Avbryt"
     "Åpne"
     "passordfelt"
     "feil passord"
-    
-    
+    "Feil passord. Prøv på nytt."
     "%1$d/%2$d"
     "side %1$d av %2$d"
     "zoom %1$d prosent"
diff --git a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
index 10fbe4e..4530550 100644
--- a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "यो फाइलमा पासवर्ड राखिएको छ"
     "पासवर्ड"
-    
-    
+    "पासवर्ड गलत छ। फेरि प्रयास गर्नुहोस्।"
     "रद्द गर्नुहोस्"
     "खोल्नुहोस्"
     "पासवर्ड हाल्ने फिल्ड"
     "पासवर्ड मिलेन"
-    
-    
+    "पासवर्ड गलत छ। फेरि प्रयास गर्नुहोस्।"
     "%1$d / %2$d"
     "%2$d मध्ये %1$d औँ पेज"
     "%1$d प्रतिशत जुम गर्नुहोस्"
diff --git a/pdf/pdf-viewer/src/main/res/values-or/strings.xml b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
index c9e20691..4b08b7b 100644
--- a/pdf/pdf-viewer/src/main/res/values-or/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "ଏହି ଫାଇଲ ସୁରକ୍ଷିତ ହୋଇଛି"
     "ପାସୱାର୍ଡ"
-    
-    
+    "ଭୁଲ ପାସୱାର୍ଡ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"
     "ବାତିଲ କରନ୍ତୁ"
     "ଖୋଲନ୍ତୁ"
     "ପାସୱାର୍ଡ ଫିଲ୍ଡ"
     "ପାସୱାର୍ଡ ଭୁଲ‌ ଅଛି"
-    
-    
+    "ଭୁଲ ପାସୱାର୍ଡ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"
     "%1$d / %2$d"
     "%2$dରୁ %1$d ପୃଷ୍ଠା"
     "ଜୁମ %1$d ଶତକଡ଼ା"
diff --git a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
index 20e7fe3..3d52bbc 100644
--- a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Ten plik jest chroniony"
     "Hasło"
-    
-    
+    "Błędne hasło. Spróbuj ponownie."
     "Anuluj"
     "Otwórz"
     "pole hasła"
     "nieprawidłowe hasło"
-    
-    
+    "Błędne hasło. Spróbuj ponownie."
     "%1$d/%2$d"
     "strona %1$d%2$d"
     "powiększenie %1$d procent"
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
index 3e43b8f..57583b90 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Este arquivo está protegido"
     "Senha"
-    
-    
+    "Senha errada. Tente de novo."
     "Cancelar"
     "Abrir"
     "campo senha"
     "senha incorreta"
-    
-    
+    "Senha errada. Tente de novo."
     "%1$d / %2$d"
     "página %1$d de %2$d"
     "zoom de %1$d%%"
diff --git a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
index 3e43b8f..57583b90 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Este arquivo está protegido"
     "Senha"
-    
-    
+    "Senha errada. Tente de novo."
     "Cancelar"
     "Abrir"
     "campo senha"
     "senha incorreta"
-    
-    
+    "Senha errada. Tente de novo."
     "%1$d / %2$d"
     "página %1$d de %2$d"
     "zoom de %1$d%%"
diff --git a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
index 35adde4..f33ce79 100644
--- a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Acest fișier este protejat"
     "Parolă"
-    
-    
+    "Parola este greșită. Încearcă din nou."
     "Anulează"
     "Deschide"
     "câmpul pentru parolă"
     "parolă incorectă"
-    
-    
+    "Parola este greșită. Încearcă din nou."
     "%1$d / %2$d"
     "pagina %1$d din %2$d"
     "zoom de %1$d %%"
diff --git a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
index 4edc7b3..b3bed54 100644
--- a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Файл защищен паролем"
     "Пароль"
-    
-    
+    "Неверный пароль. Повторите попытку."
     "Отмена"
     "Открыть"
     "поле \"Пароль\""
     "неверный пароль"
-    
-    
+    "Неверный пароль. Повторите попытку."
     "%1$d из %2$d"
     "страница %1$d из %2$d"
     "масштаб: %1$d %%"
diff --git a/pdf/pdf-viewer/src/main/res/values-si/strings.xml b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
index e842242..7aeb8c8 100644
--- a/pdf/pdf-viewer/src/main/res/values-si/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "මෙම ගොනුව ආරක්ෂා කර ඇත"
     "මුරපදය"
-    
-    
+    "මුරපදය වැරදියි. නැවත උත්සාහ කරන්න."
     "අවලංගු කරන්න"
     "විවෘත කරන්න"
     "මුරපද ක්ෂේත්‍රය"
     "මුරපදය වැරදියි"
-    
-    
+    "මුරපදය වැරදියි. නැවත උත්සාහ කරන්න."
     "%1$d / %2$d"
     "%2$d න් %1$d වෙනි පිටුව"
     "විශාලනය සියයට %1$d"
diff --git a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
index 8988446..2c4e7b4 100644
--- a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Tento súbor je chránený"
     "Heslo"
-    
-    
+    "Chybné heslo. Skúste to znova."
     "Zrušiť"
     "Otvoriť"
     "pole pre heslo"
     "heslo je nesprávne"
-    
-    
+    "Chybné heslo. Skúste to znova."
     "%1$d / %2$d"
     "strana %1$d%2$d"
     "lupa na %1$d percent"
diff --git a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
index 006d91f..a78b08a 100644
--- a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Ky skedar është i mbrojtur."
     "Fjalëkalimi"
-    
-    
+    "Fjalëkalim i gabuar. Provo përsëri."
     "Anulo"
     "Hap"
     "fusha e fjalëkalimit"
     "fjalëkalimi është i pasaktë"
-    
-    
+    "Fjalëkalim i gabuar. Provo përsëri."
     "%1$d / %2$d"
     "faqja %1$d nga %2$d"
     "zmadho me %1$d për qind"
     "Shko te faqja %1$d"
     "faqja %1$d"
-    
-    
+    "Faqe bosh"
     "Skedari PDF nuk mund të shfaqet (\"%1$s\" ka format të pavlefshëm)"
     "Nuk mund të shfaqet faqja %1$d (gabim i skedarit)"
     "Lidhja: faqe uebi te %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
index b5c39af..9f50a92a 100644
--- a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Faili hii inalindwa"
     "Nenosiri"
-    
-    
+    "Nenosiri si sahihi. Jaribu tena."
     "Acha"
     "Fungua"
     "sehemu ya nenosiri"
     "nenosiri si sahihi"
-    
-    
+    "Nenosiri si sahihi. Jaribu tena."
     "%1$d / %2$d"
     "ukurasa wa %1$d kati ya %2$d"
     "kuza kwa asilimia %1$d"
diff --git a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
index a191911..3c329ca 100644
--- a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "இந்த ஃபைல் பாதுகாக்கப்பட்டுள்ளது"
     "கடவுச்சொல்"
-    
-    
+    "தவறான கடவுச்சொல். மீண்டும் முயலவும்."
     "ரத்துசெய்"
     "திற"
     "கடவுச்சொல் புலம்"
     "கடவுச்சொல் தவறானது"
-    
-    
+    "தவறான கடவுச்சொல். மீண்டும் முயலவும்."
     "%1$d / %2$d"
     "பக்கம் %1$d/%2$d"
     "%1$d சதவீத அளவை மாற்று"
     "பக்கம் %1$dக்கு செல்லும்"
     "பக்கம் %1$d"
-    
-    
+    "காலியான பக்கம்"
     "PDFஐக் காட்ட முடியவில்லை (%1$s தவறான வடிவத்தில் உள்ளது)"
     "பக்கம் %1$dஐக் காட்ட முடியவில்லை (ஃபைல் பிழை)"
     "இணைப்பு: %1$s இல் உள்ள இணையப் பக்கம்"
diff --git a/pdf/pdf-viewer/src/main/res/values-th/strings.xml b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
index 2e219a7..17236b8 100644
--- a/pdf/pdf-viewer/src/main/res/values-th/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "ไฟล์นี้ได้รับการป้องกัน"
     "รหัสผ่าน"
-    
-    
+    "รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง"
     "ยกเลิก"
     "เปิด"
     "ช่องรหัสผ่าน"
     "รหัสผ่านไม่ถูกต้อง"
-    
-    
+    "รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง"
     "%1$d/%2$d"
     "หน้า %1$d จาก %2$d"
     "ซูม %1$d เปอร์เซ็นต์"
diff --git a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
index 89ba298..1daaa55 100644
--- a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Pinoprotektahan ang file na ito"
     "Password"
-    
-    
+    "Maling password. Subukan ulit."
     "Kanselahin"
     "Buksan"
     "field ng password"
     "mali ang password"
-    
-    
+    "Mali ang password. Subukan ulit."
     "%1$d / %2$d"
     "page %1$d sa %2$d"
     "i-zoom nang %1$d porsyento"
diff --git a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
index d490735..c378486 100644
--- a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Bu dosya korunuyor"
     "Şifre"
-    
-    
+    "Yanlış şifre. Tekrar deneyin."
     "İptal"
     "Aç"
     "şifre alanı"
     "şifre yanlış"
-    
-    
+    "Yanlış şifre. Tekrar deneyin."
     "%1$d/%2$d"
     "sayfa %1$d/%2$d"
     "yakınlaştırma yüzdesi %1$d"
     "Şu sayfaya git %1$d"
     "sayfa %1$d"
-    
-    
+    "Boş sayfa"
     "PDF görüntülenemiyor (%1$s geçersiz biçimde)"
     "%1$d. sayfa görüntülenemiyor (dosya hatası)"
     "Bağlantı: %1$s alan adındaki web sayfası"
diff --git a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
index 2b5ff33..358244d 100644
--- a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
@@ -19,21 +19,18 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Цей файл захищено"
     "Пароль"
-    
-    
+    "Неправильний пароль. Повторіть спробу."
     "Скасувати"
     "Відкрити"
     "поле пароля"
     "неправильний пароль"
-    
-    
+    "Неправильний пароль. Повторіть спробу."
     "%1$d / %2$d"
     "сторінка %1$d з %2$d"
     "масштаб %1$d%%"
     "Перейти на сторінку %1$d"
     "сторінка %1$d"
-    
-    
+    "Пуста сторінка"
     "Не вдається відобразити PDF (недійсний формат файлу \"%1$s\")"
     "Не вдається відобразити сторінку %1$d (помилка файлу)"
     "Посилання: вебсторінка в домені %1$s"
diff --git a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
index 439d5bd..eaea88b 100644
--- a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Ushbu fayl himoyalangan"
     "Parol"
-    
-    
+    "Parol xato. Qaytadan urining."
     "Bekor qilish"
     "Ochish"
     "parol maydonchasi"
     "parol xato"
-    
-    
+    "Parol xato. Qaytadan urining."
     "%1$d / %2$d"
     "sahifa: %1$d/%2$d"
     "zum: %1$d foiz"
diff --git a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
index b2e8c54..1960c80 100644
--- a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Tệp này đang được bảo vệ"
     "Mật khẩu"
-    
-    
+    "Sai mật khẩu. Hãy thử lại."
     "Huỷ"
     "Mở"
     "trường nhập mật khẩu"
     "mật khẩu không chính xác"
-    
-    
+    "Sai mật khẩu. Hãy thử lại."
     "%1$d/%2$d"
     "trang %1$d trong số %2$d"
     "thu phóng %1$d phần trăm"
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
index 34d7055..cf29a52 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "此文件受密码保护"
     "密码"
-    
-    
+    "密码错误,请重试。"
     "取消"
     "打开"
     "密码字段"
     "密码不正确"
-    
-    
+    "密码错误,请重试。"
     "第 %1$d 页,共 %2$d 页"
     "第 %1$d 页,共 %2$d 页"
     "缩放比例为百分之 %1$d"
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
index 129b719..06c8e92 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "此檔案受到保護"
     "密碼"
-    
-    
+    "密碼錯誤,請再試一次。"
     "取消"
     "開啟"
     "密碼欄位"
     "密碼不正確"
-    
-    
+    "密碼錯誤,請再試一次。"
     "%1$d/%2$d"
     "第 %1$d 頁,共 %2$d 頁"
     "縮放 %1$d%%"
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
index 77f6a34..12b583f 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "這個檔案受密碼保護"
     "密碼"
-    
-    
+    "密碼錯誤,請再試一次。"
     "取消"
     "開啟"
     "密碼欄位"
     "密碼不正確"
-    
-    
+    "密碼錯誤,請再試一次。"
     "%1$d/%2$d"
     "第 %1$d 頁,共 %2$d 頁"
     "縮放 %1$d%%"
diff --git a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
index 7269d13..a3ad158 100644
--- a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
@@ -19,14 +19,12 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     "Leli fayela livikelwe"
     "Iphasiwedi"
-    
-    
+    "Iphasiwedi engalungile. Zama futhi."
     "Khansela"
     "Vula"
     "inkambu yephasiwedi"
     "iphasiwedi ayilungile"
-    
-    
+    "Iphasiwedi engalungile. Zama futhi."
     "%1$d / %2$d"
     "ikhasi %1$d kwangu-%2$d"
     "sondeza iphesenti elingu-%1$d"
diff --git a/pdf/pdf-viewer/src/main/res/values/strings.xml b/pdf/pdf-viewer/src/main/res/values/strings.xml
index 30d92f9a..5387396 100644
--- a/pdf/pdf-viewer/src/main/res/values/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values/strings.xml
@@ -115,6 +115,10 @@
       e.g. "3 of 12" meaning "match 3 is selected, out of 12 matches" [CHAR LIMIT=10] -->
     %1$d / %2$d
 
+    
+    %1$d of %2$d
+
     
     No matching results
 
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.kt
index 4c7b7e8..7b2682e 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.kt
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.kt
@@ -131,6 +131,18 @@
         listenerInterceptsMove_correctListenerCalls(true)
     }
 
+    @Test
+    fun listenerInterceptsMove_rvChildClicks_correctListenerCallsAndSendsCancel() {
+        val actionCancelFromMove1 = MotionEventItem(100, ACTION_CANCEL, 500f, 400f)
+        val secondOnItemTouchListener = MyOnItemTouchListener()
+        recyclerView.addOnItemTouchListener(secondOnItemTouchListener)
+
+        listenerInterceptsMove_correctListenerCalls(true)
+
+        assertThat(secondOnItemTouchListener.motionEventItems)
+            .isEqualTo(listOf(ActionDown to true, actionCancelFromMove1 to true))
+    }
+
     private fun listenerInterceptsMove_correctListenerCalls(childClickable: Boolean) {
         childView.isClickable = childClickable
         onItemTouchListener.motionEventItemToStartIntecepting = ActionMove1
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index c2fe4eab..0693836 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -3706,6 +3706,17 @@
         mInterceptingOnItemTouchListener = null;
         if (findInterceptingOnItemTouchListener(e)) {
             cancelScroll();
+            MotionEvent cancelEvent = MotionEvent.obtain(e);
+            cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
+            final int listenerCount = mOnItemTouchListeners.size();
+            for (int i = 0; i < listenerCount; i++) {
+                final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+                if (listener == null || listener == mInterceptingOnItemTouchListener) {
+                    continue;
+                } else {
+                    listener.onInterceptTouchEvent(this, cancelEvent);
+                }
+            }
             return true;
         }
 
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 14d991e..1a62c78 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -96,8 +96,8 @@
     androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.1")
     androidTestImplementation(libs.rxjava2)
     androidTestImplementation(libs.kotlinCoroutinesTest)
-    testImplementation(libs.mockitoCore4)
     androidTestImplementation("androidx.collection:collection")
+    androidTestImplementation(project(":sqlite:sqlite-bundled"))
 }
 
 room {
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/BooksDaoTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/BooksDaoTest.kt
index 19d0822..5443b97 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/BooksDaoTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/BooksDaoTest.kt
@@ -24,6 +24,7 @@
 import androidx.room.integration.kotlintestapp.vo.BookWithPublisher
 import androidx.room.integration.kotlintestapp.vo.Lang
 import androidx.room.integration.kotlintestapp.vo.Publisher
+import androidx.sqlite.SQLiteException
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.base.Optional
@@ -34,18 +35,26 @@
 import kotlinx.coroutines.runBlocking
 import org.hamcrest.CoreMatchers
 import org.hamcrest.CoreMatchers.equalTo
-import org.hamcrest.CoreMatchers.instanceOf
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.notNullValue
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
 import org.junit.Assert.fail
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
 
 @MediumTest
-class BooksDaoTest : TestDatabaseTest() {
+@RunWith(Parameterized::class)
+class BooksDaoTest(useBundledSQLite: Boolean) : TestDatabaseTest(useBundledSQLite) {
+
+    private companion object {
+        @JvmStatic
+        @Parameters(name = "useBundledSQLite={0}")
+        fun parameters() = arrayOf(false, true)
+    }
 
     @Test
     fun addPublisherIdError() {
@@ -212,17 +221,14 @@
         booksDao.addPublishers(TestUtil.PUBLISHER)
         booksDao.addBooks(TestUtil.BOOK_1)
 
-        var throwable: Throwable? = null
         try {
             booksDao.updateBookTitle(TestUtil.BOOK_1.bookId, null)
-        } catch (t: Throwable) {
-            throwable = t
+            fail("updateBookTitle should have failed")
+        } catch (ex: SQLiteConstraintException) {
+            // ignored on purpose
+        } catch (ex: SQLiteException) {
+            assertThat(ex).hasMessageThat().contains("NOT NULL constraint failed")
         }
-        assertNotNull(throwable)
-        assertThat(
-            throwable,
-            instanceOf(SQLiteConstraintException::class.java)
-        )
     }
 
     @Test
@@ -397,6 +403,8 @@
                 fail("addAuthorPublisherBooks should have failed")
             } catch (ex: SQLiteConstraintException) {
                 // ignored on purpose
+            } catch (ex: SQLiteException) {
+                assertThat(ex).hasMessageThat().contains("UNIQUE constraint failed")
             }
 
             assertThat(booksDao.getBooksSuspend().isEmpty(), `is`(true))
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
index 3abf6e3..0914690 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
@@ -22,13 +22,14 @@
 import androidx.room.integration.kotlintestapp.dao.BooksDao
 import androidx.room.integration.kotlintestapp.dao.UsersDao
 import androidx.room.integration.kotlintestapp.testutil.TestObserver
+import androidx.sqlite.driver.bundled.BundledSQLiteDriver
 import androidx.test.core.app.ApplicationProvider
 import java.util.concurrent.TimeUnit
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 
-abstract class TestDatabaseTest {
+abstract class TestDatabaseTest(private val useBundledSQLite: Boolean = false) {
     @Rule @JvmField val countingTaskExecutorRule = CountingTaskExecutorRule()
     protected lateinit var database: TestDatabase
     protected lateinit var booksDao: BooksDao
@@ -42,6 +43,7 @@
                     ApplicationProvider.getApplicationContext(),
                     TestDatabase::class.java
                 )
+                .apply { if (useBundledSQLite) setDriver(BundledSQLiteDriver()) }
                 .build()
 
         booksDao = database.booksDao()
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt
index 28f9f0c..bf4537a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt
@@ -16,8 +16,10 @@
 
 package androidx.room.ext
 
+import androidx.room.compiler.processing.XConstructorElement
 import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XExecutableParameterElement
+import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.XTypeElement
 import kotlin.contracts.contract
 
@@ -26,7 +28,7 @@
     return this.hasAnnotation(androidx.room.Entity::class)
 }
 
-fun XTypeElement.getValueClassUnderlyingElement(): XExecutableParameterElement {
+fun XTypeElement.getValueClassUnderlyingInfo(): ValueClassInfo {
     check(this.isValueClass()) {
         "Can't get value class property, type element '$this' is not a value class"
     }
@@ -34,12 +36,21 @@
     // * Primary constructor is required for value class
     // * Value class must have exactly one primary constructor parameter
     // * Value class primary constructor must only have final read-only (val) property parameter
-    return checkNotNull(this.findPrimaryConstructor()) {
+    val constructor =
+        checkNotNull(this.findPrimaryConstructor()) {
             "Couldn't find primary constructor for value class."
         }
-        .parameters
-        .single()
+    val param = constructor.parameters.first()
+    val field = getDeclaredFields().first { it.name == param.name }
+    return ValueClassInfo(constructor, param, field)
 }
 
+/** Store information about the underlying value property of a Kotlin value class */
+class ValueClassInfo(
+    val constructor: XConstructorElement,
+    val parameter: XExecutableParameterElement,
+    val field: XFieldElement,
+)
+
 /** Suffix of the Kotlin synthetic class created interface method implementations. */
 const val DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 798bfeb..86c5e95 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -28,7 +28,7 @@
 import androidx.room.ext.CollectionTypeNames.LONG_SPARSE_ARRAY
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaTypeNames
-import androidx.room.ext.getValueClassUnderlyingElement
+import androidx.room.ext.getValueClassUnderlyingInfo
 import androidx.room.ext.isByteBuffer
 import androidx.room.ext.isEntityElement
 import androidx.room.ext.isNotByte
@@ -377,12 +377,15 @@
         val typeElement = type.typeElement
         if (typeElement?.isValueClass() == true) {
             // Extract the type value of the Value class element
-            val underlyingProperty = typeElement.getValueClassUnderlyingElement()
+            val underlyingInfo = typeElement.getValueClassUnderlyingInfo()
+            if (underlyingInfo.constructor.isPrivate() || underlyingInfo.field.getter == null) {
+                return null
+            }
             val underlyingTypeColumnAdapter =
                 findColumnTypeAdapter(
                     // Find an adapter for the non-null underlying type, nullability will be handled
                     // by the value class adapter.
-                    out = underlyingProperty.asMemberOf(type).makeNonNullable(),
+                    out = underlyingInfo.parameter.asMemberOf(type).makeNonNullable(),
                     affinity = affinity,
                     skipDefaultConverter = false
                 ) ?: return null
@@ -391,7 +394,7 @@
                 valueTypeColumnAdapter = underlyingTypeColumnAdapter,
                 affinity = underlyingTypeColumnAdapter.typeAffinity,
                 out = type,
-                valuePropertyName = underlyingProperty.name
+                valuePropertyName = underlyingInfo.parameter.name
             )
         }
         return when {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
index 1e314bf..db835d4 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
@@ -24,7 +24,7 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.runKspTestWithK1
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.runProcessorTestWithK1
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -225,25 +225,44 @@
                 """
             package foo
             class Subject {
-              fun makeULong(): ULong {
-                TODO()
-              }
+              fun uLongFunction(): ULong = TODO()
+              fun durationFunction(): kotlin.time.Duration = TODO()
             }
             """
                     .trimIndent()
             )
-        runKspTestWithK1(
+        runKspTest(
             sources = listOf(src),
             config =
                 XProcessingEnvConfig.DEFAULT.copy(excludeMethodsWithInvalidJvmSourceNames = false)
         ) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("foo.Subject")
-            val returnType =
-                subject.getDeclaredMethods().single { it.name == "makeULong" }.returnType
-            val prop = checkNotNull(returnType.typeElement).getValueClassUnderlyingElement()
-            assertThat(prop.name).isEqualTo("data")
-            assertThat(prop.type)
-                .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
+            subject
+                .getDeclaredMethods()
+                .first { it.name == "uLongFunction" }
+                .let { uLongFunction ->
+                    val returnType = uLongFunction.returnType
+                    val info = checkNotNull(returnType.typeElement).getValueClassUnderlyingInfo()
+                    assertThat(info.parameter.name).isEqualTo("data")
+                    assertThat(info.field.name).isEqualTo("data")
+                    assertThat(info.parameter.type)
+                        .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
+                    assertThat(info.field.type)
+                        .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
+                }
+            subject
+                .getDeclaredMethods()
+                .first { it.name == "durationFunction" }
+                .let { durationFunction ->
+                    val returnType = durationFunction.returnType
+                    val info = checkNotNull(returnType.typeElement).getValueClassUnderlyingInfo()
+                    assertThat(info.parameter.name).isEqualTo("rawValue")
+                    assertThat(info.field.name).isEqualTo("rawValue")
+                    assertThat(info.parameter.type)
+                        .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
+                    assertThat(info.field.type)
+                        .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
+                }
         }
     }
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 29cbd79..d43a813 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -30,6 +30,7 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
@@ -195,7 +196,6 @@
             Source.java(
                 "foo.bar.Fruit",
                 """ package foo.bar;
-                import androidx.room.*;
                 enum Fruit {
                     APPLE,
                     BANANA,
@@ -222,7 +222,6 @@
             Source.kotlin(
                 "Foo.kt",
                 """
-            import androidx.room.*
             @JvmInline
             value class IntValueClass(val data: Int)
             @JvmInline
@@ -282,7 +281,6 @@
             Source.kotlin(
                 "Foo.kt",
                 """
-            import androidx.room.*
             @JvmInline
             value class Foo(val value : Int) {
                 val double
@@ -292,15 +290,78 @@
                     .trimIndent()
             )
 
-        runProcessorTestWithK1(sources = listOf(source)) { invocation ->
-            TypeAdapterStore.create(
-                context = invocation.context,
-                builtInConverterFlags = BuiltInConverterFlags.DEFAULT
-            )
+        runKspTest(sources = listOf(source)) { invocation ->
+            val store =
+                TypeAdapterStore.create(
+                    context = invocation.context,
+                    builtInConverterFlags = BuiltInConverterFlags.DEFAULT
+                )
             val typeElement = invocation.processingEnv.requireTypeElement("Foo")
-            assertThat(typeElement.getDeclaredFields()).hasSize(1)
-            assertThat(typeElement.getDeclaredFields().single().type.asTypeName())
-                .isEqualTo(PRIMITIVE_INT)
+            val result =
+                store.findColumnTypeAdapter(
+                    out = typeElement.type,
+                    affinity = null,
+                    skipDefaultConverter = false
+                )
+            assertThat(result).isInstanceOf()
+        }
+    }
+
+    @Test
+    fun testValueClassWithPrivateVal() {
+        val source =
+            Source.kotlin(
+                "Foo.kt",
+                """
+            @JvmInline
+            value class Foo(private val value : Int)
+            """
+                    .trimIndent()
+            )
+
+        runKspTest(sources = listOf(source)) { invocation ->
+            val store =
+                TypeAdapterStore.create(
+                    context = invocation.context,
+                    builtInConverterFlags = BuiltInConverterFlags.DEFAULT
+                )
+            val typeElement = invocation.processingEnv.requireTypeElement("Foo")
+            val result =
+                store.findColumnTypeAdapter(
+                    out = typeElement.type,
+                    affinity = null,
+                    skipDefaultConverter = false
+                )
+            assertThat(result).isNull()
+        }
+    }
+
+    @Test
+    fun testValueClassWithPrivateConstructor() {
+        val source =
+            Source.kotlin(
+                "Foo.kt",
+                """
+            @JvmInline
+            value class Foo private constructor(val value : Int)
+            """
+                    .trimIndent()
+            )
+
+        runKspTest(sources = listOf(source)) { invocation ->
+            val store =
+                TypeAdapterStore.create(
+                    context = invocation.context,
+                    builtInConverterFlags = BuiltInConverterFlags.DEFAULT
+                )
+            val typeElement = invocation.processingEnv.requireTypeElement("Foo")
+            val result =
+                store.findColumnTypeAdapter(
+                    out = typeElement.type,
+                    affinity = null,
+                    skipDefaultConverter = false
+                )
+            assertThat(result).isNull()
         }
     }
 
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
index 92bad67..ae1b3c1 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
@@ -563,7 +563,7 @@
     /** Asserts that we are not on a suspending transaction. */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // used in generated code
     open fun assertNotSuspendingTransaction() {
-        check(inTransaction() || suspendingTransactionId.get() == null) {
+        check(!inCompatibilityMode() || inTransaction() || suspendingTransactionId.get() == null) {
             "Cannot access database on a different coroutine" +
                 " context inherited from a suspending transaction."
         }
diff --git a/savedstate/savedstate/api/current.txt b/savedstate/savedstate/api/current.txt
index 7378076..fe1c8f0 100644
--- a/savedstate/savedstate/api/current.txt
+++ b/savedstate/savedstate/api/current.txt
@@ -23,6 +23,8 @@
     method public inline java.util.List getIntList(String key);
     method public inline java.util.List getIntListOrElse(String key, kotlin.jvm.functions.Function0> defaultValue);
     method public inline int getIntOrElse(String key, kotlin.jvm.functions.Function0 defaultValue);
+    method public inline long getLong(String key);
+    method public inline long getLongOrElse(String key, kotlin.jvm.functions.Function0 defaultValue);
     method public inline  T getParcelable(String key);
     method public inline  java.util.List getParcelableList(String key);
     method public inline  java.util.List getParcelableListOrElse(String key, kotlin.jvm.functions.Function0> defaultValue);
@@ -86,6 +88,7 @@
     method public inline void putFloat(String key, float value);
     method public inline void putInt(String key, int value);
     method public inline void putIntList(String key, java.util.List values);
+    method public inline void putLong(String key, long value);
     method public inline  void putParcelable(String key, T value);
     method public inline  void putParcelableList(String key, java.util.List values);
     method public inline void putSavedState(String key, android.os.Bundle value);
diff --git a/savedstate/savedstate/api/restricted_current.txt b/savedstate/savedstate/api/restricted_current.txt
index 27b0b57..47dcf78 100644
--- a/savedstate/savedstate/api/restricted_current.txt
+++ b/savedstate/savedstate/api/restricted_current.txt
@@ -25,6 +25,8 @@
     method public inline int getIntOrElse(String key, kotlin.jvm.functions.Function0 defaultValue);
     method @kotlin.PublishedApi internal inline  java.util.List getListResultOrElse(String key, kotlin.jvm.functions.Function0> defaultValue, kotlin.jvm.functions.Function0?> currentValue);
     method @kotlin.PublishedApi internal inline  java.util.List getListResultOrThrow(String key, kotlin.jvm.functions.Function0?> currentValue);
+    method public inline long getLong(String key);
+    method public inline long getLongOrElse(String key, kotlin.jvm.functions.Function0 defaultValue);
     method public inline  T getParcelable(String key);
     method public inline  java.util.List getParcelableList(String key);
     method public inline  java.util.List getParcelableListOrElse(String key, kotlin.jvm.functions.Function0> defaultValue);
@@ -90,6 +92,7 @@
     method public inline void putFloat(String key, float value);
     method public inline void putInt(String key, int value);
     method public inline void putIntList(String key, java.util.List values);
+    method public inline void putLong(String key, long value);
     method public inline  void putParcelable(String key, T value);
     method public inline  void putParcelableList(String key, java.util.List values);
     method public inline void putSavedState(String key, android.os.Bundle value);
@@ -124,6 +127,7 @@
     field public static final double DEFAULT_DOUBLE = 0.0;
     field public static final float DEFAULT_FLOAT = 0.0f;
     field public static final int DEFAULT_INT = 0; // 0x0
+    field public static final long DEFAULT_LONG = 0L; // 0x0L
   }
 
 }
diff --git a/savedstate/savedstate/bcv/native/current.txt b/savedstate/savedstate/bcv/native/current.txt
index 0406089..1a70510 100644
--- a/savedstate/savedstate/bcv/native/current.txt
+++ b/savedstate/savedstate/bcv/native/current.txt
@@ -69,6 +69,8 @@
     final inline fun getIntList(kotlin/String): kotlin.collections/List // androidx.savedstate/SavedStateReader.getIntList|getIntList(kotlin.String){}[0]
     final inline fun getIntListOrElse(kotlin/String, kotlin/Function0>): kotlin.collections/List // androidx.savedstate/SavedStateReader.getIntListOrElse|getIntListOrElse(kotlin.String;kotlin.Function0>){}[0]
     final inline fun getIntOrElse(kotlin/String, kotlin/Function0): kotlin/Int // androidx.savedstate/SavedStateReader.getIntOrElse|getIntOrElse(kotlin.String;kotlin.Function0){}[0]
+    final inline fun getLong(kotlin/String): kotlin/Long // androidx.savedstate/SavedStateReader.getLong|getLong(kotlin.String){}[0]
+    final inline fun getLongOrElse(kotlin/String, kotlin/Function0): kotlin/Long // androidx.savedstate/SavedStateReader.getLongOrElse|getLongOrElse(kotlin.String;kotlin.Function0){}[0]
     final inline fun getSavedState(kotlin/String): androidx.savedstate/SavedState // androidx.savedstate/SavedStateReader.getSavedState|getSavedState(kotlin.String){}[0]
     final inline fun getSavedStateOrElse(kotlin/String, kotlin/Function0): androidx.savedstate/SavedState // androidx.savedstate/SavedStateReader.getSavedStateOrElse|getSavedStateOrElse(kotlin.String;kotlin.Function0){}[0]
     final inline fun getString(kotlin/String): kotlin/String // androidx.savedstate/SavedStateReader.getString|getString(kotlin.String){}[0]
@@ -95,6 +97,7 @@
     final inline fun putFloat(kotlin/String, kotlin/Float) // androidx.savedstate/SavedStateWriter.putFloat|putFloat(kotlin.String;kotlin.Float){}[0]
     final inline fun putInt(kotlin/String, kotlin/Int) // androidx.savedstate/SavedStateWriter.putInt|putInt(kotlin.String;kotlin.Int){}[0]
     final inline fun putIntList(kotlin/String, kotlin.collections/List) // androidx.savedstate/SavedStateWriter.putIntList|putIntList(kotlin.String;kotlin.collections.List){}[0]
+    final inline fun putLong(kotlin/String, kotlin/Long) // androidx.savedstate/SavedStateWriter.putLong|putLong(kotlin.String;kotlin.Long){}[0]
     final inline fun putSavedState(kotlin/String, androidx.savedstate/SavedState) // androidx.savedstate/SavedStateWriter.putSavedState|putSavedState(kotlin.String;androidx.savedstate.SavedState){}[0]
     final inline fun putString(kotlin/String, kotlin/String) // androidx.savedstate/SavedStateWriter.putString|putString(kotlin.String;kotlin.String){}[0]
     final inline fun putStringList(kotlin/String, kotlin.collections/List) // androidx.savedstate/SavedStateWriter.putStringList|putStringList(kotlin.String;kotlin.collections.List){}[0]
@@ -110,6 +113,8 @@
         final fun (): kotlin/Float // androidx.savedstate.internal/SavedStateUtils.DEFAULT_FLOAT.|(){}[0]
     final const val DEFAULT_INT // androidx.savedstate.internal/SavedStateUtils.DEFAULT_INT|{}DEFAULT_INT[0]
         final fun (): kotlin/Int // androidx.savedstate.internal/SavedStateUtils.DEFAULT_INT.|(){}[0]
+    final const val DEFAULT_LONG // androidx.savedstate.internal/SavedStateUtils.DEFAULT_LONG|{}DEFAULT_LONG[0]
+        final fun (): kotlin/Long // androidx.savedstate.internal/SavedStateUtils.DEFAULT_LONG.|(){}[0]
 
     final inline fun <#A1: reified kotlin/Any?> getValueFromSavedState(kotlin/String, kotlin/Function0<#A1?>, kotlin/Function1, kotlin/Function0<#A1>): #A1 // androidx.savedstate.internal/SavedStateUtils.getValueFromSavedState|getValueFromSavedState(kotlin.String;kotlin.Function0<0:0?>;kotlin.Function1;kotlin.Function0<0:0>){0§}[0]
     final inline fun keyNotFoundError(kotlin/String): kotlin/Nothing // androidx.savedstate.internal/SavedStateUtils.keyNotFoundError|keyNotFoundError(kotlin.String){}[0]
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
index 73eea19..c17f0fa 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
@@ -60,6 +60,14 @@
         return getSingleResultOrElse(key, defaultValue) { source.getInt(key) }
     }
 
+    actual inline fun getLong(key: String): Long {
+        return getSingleResultOrThrow(key) { source.getLong(key) }
+    }
+
+    actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long {
+        return getSingleResultOrElse(key, defaultValue) { source.getLong(key) }
+    }
+
     /**
      * Retrieves a [Parcelable] object associated with the specified key. Throws an
      * [IllegalStateException] if the key doesn't exist.
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
index 2d1c86e..68df552 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateWriter.android.kt
@@ -38,6 +38,10 @@
         source.putInt(key, value)
     }
 
+    actual inline fun putLong(key: String, value: Long) {
+        source.putLong(key, value)
+    }
+
     /**
      * Stores an [Parcelable] value associated with the specified key in the [SavedState].
      *
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
index 4f4d741..e3f37ac 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
@@ -115,6 +115,27 @@
     public inline fun getIntOrElse(key: String, defaultValue: () -> Int): Int
 
     /**
+     * Retrieves an [Long] value associated with the specified [key]. Throws an
+     * [IllegalStateException] if the [key] doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @return The [Long] value associated with the [key].
+     * @throws IllegalStateException If the [key] is not found.
+     */
+    public inline fun getLong(key: String): Long
+
+    /**
+     * Retrieves an [Int] value associated with the specified [key], or a default value if the [key]
+     * doesn't exist.
+     *
+     * @param key The [key] to retrieve the value for.
+     * @param defaultValue A function providing the default value if the [key] is not found.
+     * @return The [Int] value associated with the [key], or the default value if the [key] is not
+     *   found.
+     */
+    public inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long
+
+    /**
      * Retrieves a [String] value associated with the specified [key]. Throws an
      * [IllegalStateException] if the [key] doesn't exist.
      *
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt
index c290244..bf7aff6 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateWriter.kt
@@ -63,6 +63,14 @@
     public inline fun putInt(key: String, value: Int)
 
     /**
+     * Stores an int value associated with the specified key in the [SavedState].
+     *
+     * @param key The key to associate the value with.
+     * @param value The [Long] value to store.
+     */
+    public inline fun putLong(key: String, value: Long)
+
+    /**
      * Stores a string value associated with the specified key in the [SavedState].
      *
      * @param key The key to associate the value with.
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/internal/SavedStateUtils.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/internal/SavedStateUtils.kt
index 8cebe5e9..bf602bd 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/internal/SavedStateUtils.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/internal/SavedStateUtils.kt
@@ -20,9 +20,10 @@
 internal object SavedStateUtils {
 
     const val DEFAULT_BOOLEAN = false
-    const val DEFAULT_FLOAT = 0f
+    const val DEFAULT_FLOAT = 0F
     const val DEFAULT_DOUBLE = 0.0
     const val DEFAULT_INT = 0
+    const val DEFAULT_LONG = 0L
 
     @Suppress("NOTHING_TO_INLINE")
     inline fun keyNotFoundError(key: String): Nothing =
diff --git a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
index 1034b38..b40f557 100644
--- a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
+++ b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
@@ -245,6 +245,42 @@
     }
 
     @Test
+    fun getLong_whenSet_returns() {
+        val underTest = savedState { putLong(KEY_1, Long.MAX_VALUE) }
+        val actual = underTest.read { getLong(KEY_1) }
+
+        assertThat(actual).isEqualTo(Long.MAX_VALUE)
+    }
+
+    @Test
+    fun getLong_whenNotSet_throws() {
+        assertThrows { savedState().read { getLong(KEY_1) } }
+    }
+
+    @Test
+    fun getLong_whenSet_differentType_returnsDefault() {
+        val underTest = savedState { putBoolean(KEY_1, false) }
+        val actual = underTest.read { getLong(KEY_1) }
+
+        assertThat(actual).isEqualTo(SavedStateUtils.DEFAULT_LONG)
+    }
+
+    @Test
+    fun getLongOrElse_whenSet_returns() {
+        val underTest = savedState { putLong(KEY_1, Long.MAX_VALUE) }
+        val actual = underTest.read { getLongOrElse(KEY_1) { Long.MIN_VALUE } }
+
+        assertThat(actual).isEqualTo(Long.MAX_VALUE)
+    }
+
+    @Test
+    fun getLongOrElse_whenNotSet_returnsElse() {
+        val actual = savedState().read { getLongOrElse(KEY_1) { Long.MIN_VALUE } }
+
+        assertThat(actual).isEqualTo(Long.MIN_VALUE)
+    }
+
+    @Test
     fun getString_whenSet_returns() {
         val underTest = savedState { putString(KEY_1, STRING_VALUE) }
         val actual = underTest.read { getString(KEY_1) }
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
index 5df4565..01e3cd9 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
@@ -59,6 +59,14 @@
             source.map[key] as? Int ?: SavedStateUtils.DEFAULT_INT
         }
 
+    actual inline fun getLong(key: String): Long =
+        getSingleResultOrThrow(key) { source.map[key] as? Long ?: SavedStateUtils.DEFAULT_LONG }
+
+    actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long =
+        getSingleResultOrElse(key, defaultValue) {
+            source.map[key] as? Long ?: SavedStateUtils.DEFAULT_LONG
+        }
+
     actual inline fun getString(key: String): String =
         getSingleResultOrThrow(key) { source.map[key] as? String }
 
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
index 8bb79dd..4b614d6 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateWriter.nonAndroid.kt
@@ -38,6 +38,10 @@
         source.map[key] = value
     }
 
+    actual inline fun putLong(key: String, value: Long) {
+        source.map[key] = value
+    }
+
     actual inline fun putString(key: String, value: String) {
         source.map[key] = value
     }
diff --git a/wear/OWNERS b/wear/OWNERS
index e457e5c..9d812b7 100644
--- a/wear/OWNERS
+++ b/wear/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 188444
 [email protected]
[email protected]
 [email protected]
 [email protected]
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index 18055fe..a245b51 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -541,6 +541,7 @@
 
   public final class TransformingLazyColumnKt {
     method @Deprecated @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.TransformingLazyColumnState state, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function1 content);
+    method @androidx.compose.runtime.Composable public static void TransformingLazyColumn(androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.TransformingLazyColumnState state, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function1 content);
     method @androidx.compose.runtime.Composable public static void TransformingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.TransformingLazyColumnState state, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function1 content);
   }
 
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index 18055fe..a245b51 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -541,6 +541,7 @@
 
   public final class TransformingLazyColumnKt {
     method @Deprecated @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.TransformingLazyColumnState state, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function1 content);
+    method @androidx.compose.runtime.Composable public static void TransformingLazyColumn(androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.TransformingLazyColumnState state, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function1 content);
     method @androidx.compose.runtime.Composable public static void TransformingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.TransformingLazyColumnState state, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function1 content);
   }
 
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategyTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategyTest.kt
index 4141c0e..c2f73de 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategyTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategyTest.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
@@ -38,7 +39,7 @@
 class TransformingLazyColumnCenterBoundsMeasurementStrategyTest {
     private val screenHeight = 100
     private val screenWidth = 120
-
+    private val density = 1f
     private val containerConstraints =
         Constraints(
             minWidth = screenWidth,
@@ -278,6 +279,7 @@
             lastMeasuredAnchorItemHeight = lastMeasuredAnchorItemHeight,
             scrollToBeConsumed = scrollToBeConsumed,
             coroutineScope = CoroutineScope(EmptyCoroutineContext),
+            density = Density(density),
             layout = { width, height, _ -> EmptyMeasureResult(width, height) },
         )
 
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnTransformingLazyColumnContentPaddingMeasurementStrategyTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategyTest.kt
similarity index 96%
rename from wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnTransformingLazyColumnContentPaddingMeasurementStrategyTest.kt
rename to wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategyTest.kt
index 7287a39..ee6664e 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnTransformingLazyColumnContentPaddingMeasurementStrategyTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategyTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
@@ -38,9 +39,10 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class TransformingLazyColumnTransformingLazyColumnContentPaddingMeasurementStrategyTest {
+class TransformingLazyColumnContentPaddingMeasurementStrategyTest {
     private val screenHeight = 100
     private val screenWidth = 120
+    private val density = 1f
 
     private val containerConstraints =
         Constraints(
@@ -312,9 +314,12 @@
 
     private val measureScope: IntrinsicMeasureScope =
         object : IntrinsicMeasureScope {
-            override val fontScale = 1f
+            override val fontScale: Float
+                get() = this@TransformingLazyColumnContentPaddingMeasurementStrategyTest.density
+
             override val layoutDirection: LayoutDirection = LayoutDirection.Ltr
-            override val density = 1f
+            override val density: Float
+                get() = this@TransformingLazyColumnContentPaddingMeasurementStrategyTest.density
         }
 
     private fun TransformingLazyColumnMeasurementStrategy.measure(
@@ -336,6 +341,7 @@
             lastMeasuredAnchorItemHeight = lastMeasuredAnchorItemHeight,
             scrollToBeConsumed = scrollToBeConsumed,
             coroutineScope = CoroutineScope(EmptyCoroutineContext),
+            density = Density(density),
             layout = { width, height, _ -> EmptyMeasureResult(width, height) },
         )
 
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnStateSaverTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnStateSaverTest.kt
new file mode 100644
index 0000000..df15e86
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/lazy/TransformingLazyColumnStateSaverTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation.lazy
+
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlin.test.fail
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TransformingLazyColumnStateSaverTest {
+    private val allowingScope = SaverScope { true }
+
+    @Test
+    fun saveAndRestoreState() {
+        val original = TransformingLazyColumnState(2, 10)
+        val saved = with(TransformingLazyColumnState.Saver) { allowingScope.save(original) }
+        if (saved == null) {
+            fail("Saved state should not be null")
+        }
+        val restored =
+            TransformingLazyColumnState.Saver.restore(saved)
+                ?: fail("Restored state should not be null")
+
+        assertThat(restored.anchorItemIndex).isEqualTo(original.anchorItemIndex)
+        assertThat(restored.anchorItemScrollOffset).isEqualTo(original.anchorItemScrollOffset)
+    }
+
+    @Test
+    fun saveAndRestoreStateInitialState() {
+        val original = TransformingLazyColumnState(0, 0)
+        val saved = with(TransformingLazyColumnState.Saver) { allowingScope.save(original) }
+        if (saved == null) {
+            fail("Saved state should not be null")
+        }
+        val restored =
+            TransformingLazyColumnState.Saver.restore(saved)
+                ?: fail("Restored state should not be null")
+
+        assertThat(restored.anchorItemIndex).isEqualTo(original.anchorItemIndex)
+        assertThat(restored.anchorItemScrollOffset).isEqualTo(original.anchorItemScrollOffset)
+    }
+}
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumn.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumn.kt
index 31086eab..19ce875 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumn.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumn.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.lazy.layout.LazyLayout
 import androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
@@ -84,6 +85,10 @@
  * is a wear specific version of LazyColumn that adds support for scaling and morphing animations.
  *
  * @sample androidx.wear.compose.foundation.samples.TransformingLazyColumnLettersSample
+ * @param contentPadding a padding around the whole content. This will add padding for the content
+ *   after it has been clipped, which is not possible via [modifier] param. You can use it to add a
+ *   padding before the first item or after the last one. If you want to add a spacing between each
+ *   item use [verticalArrangement].
  * @param modifier The modifier to be applied to the layout.
  * @param state The state object to be used to control the list and the applied layout.
  * @param verticalArrangement The vertical arrangement of the items.
@@ -99,16 +104,65 @@
  *   separate [Modifier.rotaryScrollable] modifier.
  * @param content The content of the list.
  */
+// TODO: b/372629395 - Default to ContentPaddingMeasurementStrategy when no contentPadding provided.
+@Composable
+fun TransformingLazyColumn(
+    contentPadding: PaddingValues,
+    modifier: Modifier = Modifier,
+    state: TransformingLazyColumnState = rememberTransformingLazyColumnState(),
+    verticalArrangement: Arrangement.Vertical =
+        Arrangement.spacedBy(space = 4.dp, alignment = Alignment.Top),
+    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+    userScrollEnabled: Boolean = true,
+    rotaryScrollableBehavior: RotaryScrollableBehavior? = RotaryScrollableDefaults.behavior(state),
+    content: TransformingLazyColumnScope.() -> Unit
+) {
+    TransformingLazyColumnImpl(
+        modifier = modifier,
+        state = state,
+        verticalArrangement = verticalArrangement,
+        horizontalAlignment = horizontalAlignment,
+        measurementStrategyProvider = {
+            TransformingLazyColumnContentPaddingMeasurementStrategy(
+                contentPadding = contentPadding,
+                intrinsicMeasureScope = this
+            )
+        },
+        flingBehavior = flingBehavior,
+        userScrollEnabled = userScrollEnabled,
+        rotaryScrollableBehavior = rotaryScrollableBehavior,
+        content = content,
+    )
+}
+
+/**
+ * The vertically scrolling list that only composes and lays out the currently visible items. This
+ * is a wear specific version of LazyColumn that adds support for scaling and morphing animations.
+ *
+ * @sample androidx.wear.compose.foundation.samples.TransformingLazyColumnLettersSample
+ * @param modifier The modifier to be applied to the layout.
+ * @param state The state object to be used to control the list and the applied layout.
+ * @param verticalArrangement The vertical arrangement of the items.
+ * @param horizontalAlignment The horizontal alignment of the items.
+ * @param flingBehavior The fling behavior to be used for the list. This parameter and the
+ *   [rotaryScrollableBehavior] (which controls rotary scroll) should produce similar scroll effect
+ *   visually.
+ * @param userScrollEnabled Whether the user should be able to scroll the list. This also affects
+ *   scrolling with rotary.
+ * @param rotaryScrollableBehavior Parameter for changing rotary scrollable behavior. This parameter
+ *   and the [flingBehavior] (which controls touch scroll) should produce similar scroll effect. Can
+ *   be null if rotary support is not required or when it should be handled externally with a
+ *   separate [Modifier.rotaryScrollable] modifier.
+ * @param content The content of the list.
+ */
+// TODO: b/372629395 - Remove this overload without contentPadding when clients are migrated.
 @Composable
 fun TransformingLazyColumn(
     modifier: Modifier = Modifier,
     state: TransformingLazyColumnState = rememberTransformingLazyColumnState(),
     verticalArrangement: Arrangement.Vertical =
-        Arrangement.spacedBy(
-            space = 4.dp,
-            // TODO: b/352513793 - Add support for reverseLayout.
-            alignment = Alignment.Top
-        ),
+        Arrangement.spacedBy(space = 4.dp, alignment = Alignment.Top),
     horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategy.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategy.kt
index 52dbc03..42178aa 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategy.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnCenterBoundsMeasurementStrategy.kt
@@ -19,6 +19,7 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastForEach
 import kotlin.math.abs
 import kotlin.math.roundToInt
@@ -54,6 +55,7 @@
         anchorItemScrollOffset: Int,
         lastMeasuredAnchorItemHeight: Int,
         coroutineScope: CoroutineScope,
+        density: Density,
         scrollToBeConsumed: Float,
         layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
     ): TransformingLazyColumnMeasureResult {
@@ -154,6 +156,8 @@
             canScrollForward = canScrollForward,
             canScrollBackward = canScrollBackward,
             coroutineScope = coroutineScope,
+            density = density,
+            itemSpacing = itemSpacing,
             measureResult =
                 layout(containerConstraints.maxWidth, containerConstraints.maxHeight) {
                     visibleItems.fastForEach { it.place(this) }
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategy.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategy.kt
index 17c4b94..be60fced 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategy.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategy.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachIndexed
 import androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress.Companion.bottomItemScrollProgress
@@ -52,6 +53,7 @@
         anchorItemScrollOffset: Int,
         lastMeasuredAnchorItemHeight: Int,
         coroutineScope: CoroutineScope,
+        density: Density,
         scrollToBeConsumed: Float,
         layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
     ): TransformingLazyColumnMeasureResult {
@@ -150,6 +152,8 @@
             canScrollForward = canScrollForward,
             canScrollBackward = canScrollBackward,
             coroutineScope = coroutineScope,
+            density = density,
+            itemSpacing = itemSpacing,
             measureResult =
                 layout(containerConstraints.maxWidth, containerConstraints.maxHeight) {
                     visibleItems.fastForEach { it.place(this) }
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasureResult.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasureResult.kt
index 30e910f..7b7474b 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasureResult.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasureResult.kt
@@ -17,6 +17,7 @@
 package androidx.wear.compose.foundation.lazy
 
 import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import kotlinx.coroutines.CoroutineScope
 
@@ -30,12 +31,19 @@
     val anchorItemScrollOffset: Int,
     /** Last known height for the anchor item or negative number if it hasn't been measured. */
     val lastMeasuredItemHeight: Int,
+    /** Scope for animations. */
     val coroutineScope: CoroutineScope,
     /** Layout information for the visible items. */
     override val visibleItems: List,
     /** see [TransformingLazyColumnLayoutInfo.totalItemsCount] */
     override val totalItemsCount: Int,
+    /** The spacing between items in the direction of scrolling. */
+    val itemSpacing: Int,
+    /** Density of the last measure. */
+    val density: Density,
+    /** True if there is some space available to continue scrolling in the forward direction. */
     var canScrollForward: Boolean,
+    /** True if there is some space available to continue scrolling in the backward direction. */
     var canScrollBackward: Boolean,
 ) : TransformingLazyColumnLayoutInfo, MeasureResult by measureResult {
     /** see [TransformingLazyColumnLayoutInfo.viewportSize] */
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurement.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurement.kt
index 090bf51..9196266 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurement.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurement.kt
@@ -97,7 +97,6 @@
             val anchorItemIndex: Int
             val anchorItemScrollOffset: Int
             val lastMeasuredAnchorItemHeight: Int
-
             Snapshot.withoutReadObservation {
                 anchorItemIndex =
                     if (itemsCount == 0) 0 else state.anchorItemIndex.coerceIn(0 until itemsCount)
@@ -116,6 +115,7 @@
                         anchorItemScrollOffset = anchorItemScrollOffset,
                         lastMeasuredAnchorItemHeight = lastMeasuredAnchorItemHeight,
                         coroutineScope = coroutineScope,
+                        density = this,
                         layout = { width, height, placement ->
                             layout(
                                 containerConstraints.constrainWidth(width),
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurementStrategy.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurementStrategy.kt
index 3ed2b2a..07b443f 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurementStrategy.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurementStrategy.kt
@@ -19,6 +19,7 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress.Companion.bottomItemScrollProgress
 import androidx.wear.compose.foundation.lazy.TransformingLazyColumnItemScrollProgress.Companion.topItemScrollProgress
 import kotlin.coroutines.EmptyCoroutineContext
@@ -51,6 +52,7 @@
         anchorItemScrollOffset: Int,
         lastMeasuredAnchorItemHeight: Int,
         coroutineScope: CoroutineScope,
+        density: Density,
         scrollToBeConsumed: Float,
         layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
     ): TransformingLazyColumnMeasureResult
@@ -83,5 +85,7 @@
         canScrollForward = false,
         canScrollBackward = false,
         coroutineScope = CoroutineScope(EmptyCoroutineContext),
+        density = Density(1f),
+        itemSpacing = 0,
         measureResult = layout(containerConstraints.maxWidth, containerConstraints.maxHeight) {}
     )
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnScrollScope.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnScrollScope.kt
new file mode 100644
index 0000000..1b0952c
--- /dev/null
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnScrollScope.kt
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.foundation.lazy
+
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.animateTo
+import androidx.compose.animation.core.copy
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastSumBy
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.math.abs
+
+// TODO: b/373832623 - Migrate to `LazyLayoutScrollScope` when `animateScrollToItem` is available.
+internal class TransformingLazyColumnScrollScope(
+    private val state: TransformingLazyColumnState,
+    scrollScope: ScrollScope
+) : ScrollScope by scrollScope {
+    val firstVisibleItemIndex: Int
+        get() = state.layoutInfo.visibleItems.firstOrNull()?.index ?: 0
+
+    val firstVisibleItemScrollOffset: Int
+        get() = state.layoutInfo.visibleItems.firstOrNull()?.offset ?: 0
+
+    val lastVisibleItemIndex: Int
+        get() = state.layoutInfo.visibleItems.lastOrNull()?.index ?: 0
+
+    val anchorItemIndex: Int
+        get() = state.layoutInfoState.value.anchorItemIndex
+
+    val anchorItemScrollOffset: Int
+        get() = state.layoutInfoState.value.anchorItemScrollOffset
+
+    val itemCount: Int
+        get() = state.layoutInfo.totalItemsCount
+
+    fun snapToItem(index: Int, offset: Int = 0) {
+        state.snapToItemIndexInternal(index, offset)
+    }
+
+    fun approximateDistanceTo(targetIndex: Int, targetOffset: Int = 0): Int {
+        val layoutInfo = state.layoutInfoState.value
+        if (layoutInfo.visibleItems.isEmpty()) return 0
+        return if (!isItemVisible(targetIndex)) {
+            val averageSize =
+                calculateVisibleItemsAverageHeight(layoutInfo) + layoutInfo.itemSpacing
+            val indexesDiff = targetIndex - layoutInfo.anchorItemIndex
+            (averageSize * indexesDiff) - layoutInfo.anchorItemScrollOffset
+        } else {
+            val visibleItem = layoutInfo.visibleItems.fastFirstOrNull { it.index == targetIndex }
+            visibleItem?.offset ?: 0
+        } + targetOffset
+    }
+
+    private fun calculateVisibleItemsAverageHeight(
+        measureResult: TransformingLazyColumnMeasureResult
+    ): Int {
+        val visibleItems = measureResult.visibleItems
+        return visibleItems.fastSumBy { it.measuredHeight } / visibleItems.size
+    }
+
+    internal fun TransformingLazyColumnScrollScope.isItemVisible(index: Int): Boolean {
+        return index in firstVisibleItemIndex..lastVisibleItemIndex
+    }
+}
+
+private class ItemFoundInScroll(
+    val itemOffset: Int,
+    val previousAnimation: AnimationState
+) : CancellationException()
+
+private val TargetDistance = 500.dp
+private val BoundDistance = 300.dp
+private val MinimumDistance = 10.dp
+
+private const val DEBUG = false
+
+private inline fun debugLog(generateMsg: () -> String) {
+    if (DEBUG) {
+        println("LazyScrolling: ${generateMsg()}")
+    }
+}
+
+internal suspend fun TransformingLazyColumnScrollScope.animateScrollToItem(
+    index: Int,
+    scrollOffset: Int,
+    numOfItemsForTeleport: Int,
+    density: Density,
+    scrollScope: ScrollScope
+) {
+    with(scrollScope) {
+        try {
+            val targetDistancePx = with(density) { TargetDistance.toPx() }
+            val boundDistancePx = with(density) { BoundDistance.toPx() }
+            val minDistancePx = with(density) { MinimumDistance.toPx() }
+            var loop = true
+            var anim = AnimationState(0f)
+            if (isItemVisible(index)) {
+                val targetItemInitialOffset = approximateDistanceTo(index)
+                // It's already visible, just animate directly
+                throw ItemFoundInScroll(targetItemInitialOffset, anim)
+            }
+            val forward = index > anchorItemIndex
+            fun isOvershot(): Boolean {
+                // Did we scroll past the item?
+                @Suppress("RedundantIf") // It's way easier to understand the logic this way
+                return if (forward) {
+                    if (anchorItemIndex > index) {
+                        true
+                    } else if (anchorItemIndex == index && anchorItemScrollOffset > scrollOffset) {
+                        true
+                    } else {
+                        false
+                    }
+                } else { // backward
+                    if (anchorItemIndex < index) {
+                        true
+                    } else if (anchorItemIndex == index && anchorItemScrollOffset < scrollOffset) {
+                        true
+                    } else {
+                        false
+                    }
+                }
+            }
+            var loops = 1
+            while (loop && itemCount > 0) {
+                val expectedDistance = approximateDistanceTo(index) + scrollOffset
+                val target =
+                    if (abs(expectedDistance) < targetDistancePx) {
+                        maxOf(abs(expectedDistance.toFloat()), minDistancePx)
+                    } else {
+                        targetDistancePx
+                    } * if (forward) 1 else -1
+                debugLog {
+                    "Scrolling to index=$index offset=$scrollOffset from " +
+                        "index=$firstVisibleItemIndex offset=$firstVisibleItemScrollOffset with " +
+                        "calculated target=$target"
+                }
+                anim = anim.copy(value = 0f)
+                var prevValue = 0f
+                anim.animateTo(target, sequentialAnimation = (anim.velocity != 0f)) {
+                    // If we haven't found the item yet, check if it's visible.
+                    debugLog { "firstVisibleItemIndex=$firstVisibleItemIndex" }
+                    if (!isItemVisible(index)) {
+                        // Springs can overshoot their target, clamp to the desired range
+                        val coercedValue =
+                            if (target > 0) {
+                                value.coerceAtMost(target)
+                            } else {
+                                value.coerceAtLeast(target)
+                            }
+                        val delta = coercedValue - prevValue
+                        debugLog {
+                            "Scrolling by $delta (target: $target, coercedValue: $coercedValue)"
+                        }
+                        val consumed = scrollBy(delta)
+                        if (isItemVisible(index)) {
+                            debugLog { "Found the item after performing scrollBy()" }
+                        } else if (!isOvershot()) {
+                            if (delta != consumed) {
+                                debugLog { "Hit end without finding the item" }
+                                cancelAnimation()
+                                loop = false
+                                return@animateTo
+                            }
+                            prevValue += delta
+                            if (forward) {
+                                if (value > boundDistancePx) {
+                                    debugLog { "Struck bound going forward" }
+                                    cancelAnimation()
+                                }
+                            } else {
+                                if (value < -boundDistancePx) {
+                                    debugLog { "Struck bound going backward" }
+                                    cancelAnimation()
+                                }
+                            }
+                            if (forward) {
+                                if (
+                                    loops >= 2 &&
+                                        index - lastVisibleItemIndex > numOfItemsForTeleport
+                                ) {
+                                    // Teleport
+                                    debugLog { "Teleport forward" }
+                                    snapToItem(index = index - numOfItemsForTeleport, offset = 0)
+                                }
+                            } else {
+                                if (
+                                    loops >= 2 &&
+                                        firstVisibleItemIndex - index > numOfItemsForTeleport
+                                ) {
+                                    // Teleport
+                                    debugLog { "Teleport backward" }
+                                    snapToItem(index = index + numOfItemsForTeleport, offset = 0)
+                                }
+                            }
+                        }
+                    }
+                    // We don't throw ItemFoundInScroll when we snap, because once we've snapped to
+                    // the final position, there's no need to animate to it.
+                    if (isOvershot()) {
+                        debugLog {
+                            "Overshot, " +
+                                "item $firstVisibleItemIndex at  $firstVisibleItemScrollOffset," +
+                                " target is $scrollOffset"
+                        }
+                        snapToItem(index = index, offset = scrollOffset)
+                        loop = false
+                        cancelAnimation()
+                        return@animateTo
+                    } else if (isItemVisible(index)) {
+                        val targetItemOffset = approximateDistanceTo(index)
+                        debugLog { "Found item" }
+                        throw ItemFoundInScroll(targetItemOffset, anim)
+                    }
+                }
+                loops++
+            }
+        } catch (itemFound: ItemFoundInScroll) {
+            // We found it, animate to it
+            // Bring to the requested position - will be automatically stopped if not possible
+            val anim = itemFound.previousAnimation.copy(value = 0f)
+            val target = (itemFound.itemOffset + scrollOffset).toFloat()
+            var prevValue = 0f
+            debugLog { "Seeking by $target at velocity ${itemFound.previousAnimation.velocity}" }
+            anim.animateTo(target, sequentialAnimation = (anim.velocity != 0f)) {
+                // Springs can overshoot their target, clamp to the desired range
+                val coercedValue =
+                    when {
+                        target > 0 -> {
+                            value.coerceAtMost(target)
+                        }
+                        target < 0 -> {
+                            value.coerceAtLeast(target)
+                        }
+                        else -> {
+                            debugLog {
+                                "WARNING: somehow ended up seeking 0px, this shouldn't happen"
+                            }
+                            0f
+                        }
+                    }
+                val delta = coercedValue - prevValue
+                debugLog { "Seeking by $delta (coercedValue = $coercedValue)" }
+                val consumed = scrollBy(delta)
+                if (
+                    delta != consumed /* hit the end, stop */ ||
+                        coercedValue != value /* would have overshot, stop */
+                ) {
+                    cancelAnimation()
+                }
+                prevValue += delta
+            }
+            // Once we're finished the animation, snap to the exact position to account for
+            // rounding error (otherwise we tend to end up with the previous item scrolled the
+            // tiniest bit onscreen)
+            // TODO: prevent temporarily scrolling *past* the item
+            snapToItem(index = index, offset = scrollOffset)
+        }
+    }
+}
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnState.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnState.kt
index 44ce562..c63bd80 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnState.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/lazy/TransformingLazyColumnState.kt
@@ -24,13 +24,16 @@
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.listSaver
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Remeasurement
 import androidx.compose.ui.layout.RemeasurementModifier
+import androidx.compose.ui.unit.Density
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.math.abs
 import kotlinx.coroutines.CoroutineScope
@@ -43,7 +46,9 @@
 fun rememberLazyColumnState() = rememberTransformingLazyColumnState()
 
 /** Creates a [TransformingLazyColumnState] that is remembered across compositions. */
-@Composable fun rememberTransformingLazyColumnState() = remember { TransformingLazyColumnState() }
+@Composable
+fun rememberTransformingLazyColumnState() =
+    rememberSaveable(saver = TransformingLazyColumnState.Saver) { TransformingLazyColumnState() }
 
 @Deprecated("Use TransformingLazyColumnState instead", ReplaceWith("TransformingLazyColumnState"))
 typealias LazyColumnState = TransformingLazyColumnState
@@ -53,10 +58,15 @@
  *
  * In most cases, this will be created via [rememberTransformingLazyColumnState].
  */
-class TransformingLazyColumnState : ScrollableState {
+class TransformingLazyColumnState() : ScrollableState {
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
+    internal constructor(initialAnchorItemIndex: Int, initialAnchorItemScrollOffset: Int) : this() {
+        anchorItemIndex = initialAnchorItemIndex
+        anchorItemScrollOffset = initialAnchorItemScrollOffset
+    }
+
     override fun dispatchRawDelta(delta: Float): Float = scrollableState.dispatchRawDelta(delta)
 
     override suspend fun scroll(
@@ -64,7 +74,7 @@
         block: suspend ScrollScope.() -> Unit
     ) = scrollableState.scroll(scrollPriority, block)
 
-    private val layoutInfoState =
+    internal val layoutInfoState =
         mutableStateOf(EmptyTransformingLazyColumnMeasureResult, neverEqualPolicy())
 
     /**
@@ -79,6 +89,9 @@
     val layoutInfo: TransformingLazyColumnLayoutInfo
         get() = layoutInfoState.value
 
+    internal val density: Density
+        get() = layoutInfoState.value.density
+
     internal var scrollToBeConsumed = 0f
         private set
 
@@ -124,7 +137,7 @@
         nearestRange = calculateNearestItemsRange(anchorItemIndex)
     }
 
-    private companion object {
+    internal companion object {
         /**
          * We use the idea of sliding window as an optimization, so user can scroll up to this
          * number of items until we have to regenerate the key to index map.
@@ -146,6 +159,23 @@
                 slidingWindowStart + NearestItemsSlidingWindowSize + NearestItemsExtraItemCount
             return start until end
         }
+
+        /** The default [Saver] implementation for [TransformingLazyColumnState]. */
+        internal val Saver =
+            listSaver(
+                save = {
+                    listOf(
+                        it.anchorItemIndex,
+                        it.anchorItemScrollOffset,
+                    )
+                },
+                restore = {
+                    val scalingLazyColumnState = TransformingLazyColumnState()
+                    scalingLazyColumnState.anchorItemIndex = it[0]
+                    scalingLazyColumnState.anchorItemScrollOffset = it[1]
+                    scalingLazyColumnState
+                }
+            )
     }
 
     private val scrollableState = ScrollableState { -onScroll(-it) }
@@ -165,7 +195,7 @@
         scroll { snapToItemIndexInternal(index, scrollOffset) }
     }
 
-    private fun snapToItemIndexInternal(index: Int, scrollOffset: Int) {
+    internal fun snapToItemIndexInternal(index: Int, scrollOffset: Int) {
         anchorItemIndex = index
         anchorItemScrollOffset = scrollOffset
         lastMeasuredAnchorItemHeight = Int.MIN_VALUE
@@ -208,6 +238,8 @@
         canScrollForward = false,
         canScrollBackward = false,
         coroutineScope = CoroutineScope(EmptyCoroutineContext),
+        density = Density(1f),
+        itemSpacing = 0,
         measureResult =
             object : MeasureResult {
                 override val width: Int = 0
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt
index d7fb8e9..ba9730e 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/IconButtonDemo.kt
@@ -110,7 +110,9 @@
             Row {
                 IconButtonWithCornerAnimationSample()
                 Spacer(modifier = Modifier.width(5.dp))
-                IconButtonWithCornerAnimationSample()
+                IconButtonWithCornerAnimationSample(
+                    colors = IconButtonDefaults.filledVariantIconButtonColors()
+                )
             }
         }
         item { ListHeader { Text("Morphed Animation") } }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitCheckboxButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitCheckboxButtonDemo.kt
index 82e0e68..3ef3e86 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitCheckboxButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitCheckboxButtonDemo.kt
@@ -52,14 +52,14 @@
             DemoSplitCheckboxButton(
                 enabled = true,
                 initiallyChecked = true,
-                primary = "Primary Label with at most three lines of content "
+                primary = "Primary label with at most three lines of content "
             )
         }
         item {
             DemoSplitCheckboxButton(
                 enabled = true,
                 initiallyChecked = true,
-                primary = "Primary Label with at most three lines of content",
+                primary = "Primary label with at most three lines of content",
                 secondary = "Secondary label with at most two lines of text"
             )
         }
@@ -71,6 +71,17 @@
                 primaryMaxLines = 4,
             )
         }
+        item { ListHeader { Text("Disabled Multi-line") } }
+        for (initiallyChecked in booleanArrayOf(true, false)) {
+            item {
+                DemoSplitCheckboxButton(
+                    enabled = false,
+                    initiallyChecked = initiallyChecked,
+                    primary = "Primary label",
+                    secondary = "Secondary label"
+                )
+            }
+        }
     }
 }
 
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt
index b5493b5..ff0b4b1 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitRadioButtonDemo.kt
@@ -65,7 +65,7 @@
                 enabled = true,
                 selected = selectedMultiLineRadioIndex == 1,
                 onSelected = { selectedMultiLineRadioIndex = 1 },
-                primary = "Primary Label with at most three lines of content"
+                primary = "Primary label with at most three lines of content"
             )
         }
         item {
@@ -73,7 +73,7 @@
                 enabled = true,
                 selected = selectedMultiLineRadioIndex == 2,
                 onSelected = { selectedMultiLineRadioIndex = 2 },
-                primary = "Primary Label with at most three lines of content",
+                primary = "Primary label with at most three lines of content",
                 secondary = "Secondary label with at most two lines of text"
             )
         }
@@ -86,6 +86,17 @@
                 primaryMaxLines = 4,
             )
         }
+        item { ListHeader { Text("Disabled Multi-line") } }
+        for (selected in booleanArrayOf(true, false)) {
+            item {
+                DemoSplitRadioButton(
+                    enabled = false,
+                    selected = selected,
+                    primary = "Primary label",
+                    secondary = "Secondary label"
+                )
+            }
+        }
     }
 }
 
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitSwitchButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitSwitchButtonDemo.kt
index eaa636d..c8cc19e 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitSwitchButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SplitSwitchButtonDemo.kt
@@ -52,14 +52,14 @@
             DemoSplitSwitchButton(
                 enabled = true,
                 initiallyChecked = true,
-                primary = "Primary Label with at most three lines of content"
+                primary = "Primary label with at most three lines of content"
             )
         }
         item {
             DemoSplitSwitchButton(
                 enabled = true,
                 initiallyChecked = true,
-                primary = "Primary Label with at most three lines of content",
+                primary = "Primary label with at most three lines of content",
                 secondary = "Secondary label with at most two lines of text"
             )
         }
@@ -71,6 +71,17 @@
                 primaryMaxLines = 4,
             )
         }
+        item { ListHeader { Text("Disabled Multi-line") } }
+        for (initiallyChecked in booleanArrayOf(true, false)) {
+            item {
+                DemoSplitSwitchButton(
+                    enabled = false,
+                    initiallyChecked = initiallyChecked,
+                    primary = "Primary label",
+                    secondary = "Secondary label"
+                )
+            }
+        }
     }
 }
 
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt
index 2f8a733..ac422ad 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/IconButtonSample.kt
@@ -29,6 +29,7 @@
 import androidx.wear.compose.material3.FilledTonalIconButton
 import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.IconButton
+import androidx.wear.compose.material3.IconButtonColors
 import androidx.wear.compose.material3.IconButtonDefaults
 import androidx.wear.compose.material3.IconButtonShapes
 import androidx.wear.compose.material3.OutlinedIconButton
@@ -90,10 +91,13 @@
 
 @Composable
 @Sampled
-fun IconButtonWithCornerAnimationSample() {
+fun IconButtonWithCornerAnimationSample(
+    colors: IconButtonColors = IconButtonDefaults.filledIconButtonColors()
+) {
     FilledIconButton(
         onClick = { /* Do something */ },
         shapes = IconButtonDefaults.animatedShapes(),
+        colors = colors
     ) {
         Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Favorite icon")
     }
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt
index 119deb7..0ef946e 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TransformingLazyColumnSample.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -36,9 +37,11 @@
 import androidx.wear.compose.material3.AppScaffold
 import androidx.wear.compose.material3.Button
 import androidx.wear.compose.material3.EdgeButton
+import androidx.wear.compose.material3.EdgeButtonSize
 import androidx.wear.compose.material3.ListHeader
 import androidx.wear.compose.material3.MaterialTheme
 import androidx.wear.compose.material3.ScreenScaffold
+import androidx.wear.compose.material3.ScreenScaffoldDefaults
 import androidx.wear.compose.material3.Text
 import androidx.wear.compose.material3.lazy.scrollTransform
 import androidx.wear.compose.material3.lazy.targetMorphingHeight
@@ -51,14 +54,11 @@
     val coroutineScope = rememberCoroutineScope()
     TransformingLazyColumn(
         state = state,
-        modifier =
-            Modifier.background(MaterialTheme.colorScheme.background).padding(horizontal = 10.dp)
+        contentPadding = PaddingValues(horizontal = 10.dp, vertical = 20.dp),
+        modifier = Modifier.background(MaterialTheme.colorScheme.background)
     ) {
-        items(20) {
-            Text(
-                "Item $it",
-                color = MaterialTheme.colorScheme.onSurface,
-                style = MaterialTheme.typography.bodyLarge,
+        items(20) { idx ->
+            Column(
                 modifier =
                     Modifier.fillMaxWidth()
                         .scrollTransform(
@@ -67,8 +67,14 @@
                             shape = MaterialTheme.shapes.medium
                         )
                         .padding(10.dp)
-                        .clickable { coroutineScope.launch { state.scrollToItem(it) } }
-            )
+                        .clickable { coroutineScope.launch { state.scrollToItem(idx) } }
+            ) {
+                Text(
+                    "Item $idx",
+                    color = MaterialTheme.colorScheme.onSurface,
+                    style = MaterialTheme.typography.bodyLarge,
+                )
+            }
         }
         item {
             Button(onClick = { coroutineScope.launch { state.scrollToItem(0) } }) { Text("To top") }
@@ -94,9 +100,15 @@
         ) {
             TransformingLazyColumn(
                 state = state,
-                modifier =
-                    Modifier.background(MaterialTheme.colorScheme.background)
-                        .padding(horizontal = 10.dp)
+                contentPadding =
+                    ScreenScaffoldDefaults.contentPaddingWithEdgeButton(
+                        EdgeButtonSize.Small,
+                        start = 10.dp,
+                        end = 10.dp,
+                        top = 20.dp,
+                        extraBottom = 20.dp
+                    ),
+                modifier = Modifier.background(MaterialTheme.colorScheme.background)
             ) {
                 item(contentType = "header") {
                     // No modifier is applied - no Material 3 Motion.
@@ -159,9 +171,10 @@
         ) {
             TransformingLazyColumn(
                 state = state,
+                contentPadding = PaddingValues(horizontal = 10.dp),
                 modifier =
                     Modifier.background(MaterialTheme.colorScheme.background)
-                        .padding(horizontal = 10.dp),
+                        .padding(horizontal = 10.dp)
             ) {
                 item(contentType = "header") {
                     // No modifier is applied - no Material 3 Motion transformations.
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
index 8a30ac6..5e3ffa6 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.CutCornerShape
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
@@ -308,6 +309,26 @@
 
     @RequiresApi(Build.VERSION_CODES.O)
     @Test
+    fun animates_corners_to_75_percent_on_click() {
+        val baseShape = RoundedCornerShape(20.dp)
+        val pressedShape = RoundedCornerShape(0.dp)
+
+        rule.verifyRoundedButtonTapAnimationEnd(
+            baseShape,
+            pressedShape,
+            0.75f,
+            color = { IconButtonDefaults.filledIconButtonColors().containerColor }
+        ) { modifier ->
+            FilledIconButton(
+                onClick = {},
+                shapes = IconButtonShapes(baseShape, pressedShape),
+                modifier = modifier
+            ) {}
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Test
     fun default_shape_is_circular() {
         rule.isShape(
             expectedShape = CircleShape,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
index e960b7b..f383b29 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -29,6 +29,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Add
 import androidx.compose.runtime.Composable
@@ -36,6 +37,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
@@ -61,6 +63,7 @@
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextStyle
@@ -395,6 +398,49 @@
     onNodeWithTag(testTag).captureToImage().assertAgainstGolden(screenshotRule, methodName)
 }
 
+@RequiresApi(Build.VERSION_CODES.O)
+fun ComposeContentTestRule.verifyRoundedButtonTapAnimationEnd(
+    baseShape: RoundedCornerShape,
+    pressedShape: RoundedCornerShape,
+    targetProgress: Float,
+    color: @Composable () -> Color,
+    content: @Composable (Modifier) -> Unit
+) {
+    val expectedAnimationEnd =
+        AnimatedRoundedCornerShape(baseShape, pressedShape) { targetProgress }
+    var fillColor = Color.Transparent
+
+    setContent {
+        fillColor = color()
+        content(Modifier.testTag(TEST_TAG))
+    }
+
+    mainClock.autoAdvance = false
+    onNodeWithTag(TEST_TAG).performClick()
+
+    /**
+     * We are manually advancing by a fixed amount of frames since
+     * 1) the RoundButton.animateButtonShape is internal and therefore we cannot modify the
+     *    animation spec being used. Otherwise, we could set a custom animation time isolated and
+     *    known to this test we could wait for.
+     * 2) rule.mainClock.waitUntil expects a condition. However, the shape validations for
+     *    ImageBitMap only includes of assets
+     */
+    repeat(8) { mainClock.advanceTimeByFrame() }
+
+    onNodeWithTag(TEST_TAG)
+        .captureToImage()
+        .assertShape(
+            density = density,
+            horizontalPadding = 0.dp,
+            verticalPadding = 0.dp,
+            shapeColor = fillColor,
+            backgroundColor = Color.Transparent,
+            antiAliasingGap = 2.0f,
+            shape = expectedAnimationEnd,
+        )
+}
+
 private fun ImageBitmap.histogram(): MutableMap {
     val pixels = this.toPixelMap()
     val histogram = mutableMapOf()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
index 46a50cc..202c8b9 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.CutCornerShape
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
@@ -526,6 +527,27 @@
     }
 
     @RequiresApi(Build.VERSION_CODES.O)
+    @Test
+    fun animates_corners_to_75_percent_on_click() {
+        val baseShape = RoundedCornerShape(20.dp)
+        val pressedShape = RoundedCornerShape(0.dp)
+
+        rule.verifyRoundedButtonTapAnimationEnd(
+            baseShape,
+            pressedShape,
+            0.75f,
+            color = { TextButtonDefaults.filledTextButtonColors().containerColor }
+        ) { modifier ->
+            TextButton(
+                onClick = {},
+                shapes = TextButtonShapes(baseShape, pressedShape),
+                modifier = modifier,
+                colors = TextButtonDefaults.filledTextButtonColors()
+            ) {}
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
     private fun ComposeContentTestRule.verifyTextButtonColors(
         status: Status,
         colors: @Composable () -> TextButtonColors,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
index b984580..b2be46e 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
@@ -16,9 +16,8 @@
 
 package androidx.wear.compose.material3
 
+import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.animateFloat
-import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Indication
@@ -33,8 +32,10 @@
 import androidx.compose.foundation.shape.CornerBasedShape
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.withFrameMillis
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -43,6 +44,7 @@
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
+import kotlinx.coroutines.launch
 
 /**
  * This is a copy of RoundButton from materialcore, with additional onLongClick callback and usage
@@ -103,33 +105,33 @@
     onReleaseAnimationSpec: FiniteAnimationSpec,
 ): Shape {
     val pressed = interactionSource.collectIsPressedAsState()
+    val progress = remember { Animatable(0f) }
+    val scope = rememberCoroutineScope()
 
-    val transition = updateTransition(pressed.value, label = "Pressed State")
-    val progress: State =
-        transition.animateFloat(
-            label = "Pressed",
-            transitionSpec = {
-                when {
-                    false isTransitioningTo true -> onPressAnimationSpec
-                    else -> onReleaseAnimationSpec
+    LaunchedEffect(pressed.value) {
+        when (pressed.value) {
+            true -> scope.launch { progress.animateTo(1f, animationSpec = onPressAnimationSpec) }
+            false -> {
+                waitUntil {
+                    !progress.isRunning || progress.value > MIN_REQUIRED_ANIMATION_PROGRESS
                 }
+                scope.launch { progress.animateTo(0f, animationSpec = onReleaseAnimationSpec) }
             }
-        ) { pressedTarget ->
-            if (pressedTarget) 1f else 0f
         }
+    }
 
     return when {
         shape is RoundedCornerShape && pressedShape is RoundedCornerShape ->
             rememberAnimatedRoundedCornerShape(
                 shape = shape,
                 pressedShape = pressedShape,
-                progress = progress
+                progress = progress.asState()
             )
         else ->
             rememberAnimatedCornerBasedShape(
                 shape = shape,
                 pressedShape = pressedShape,
-                progress = progress
+                progress = progress.asState()
             )
     }
 }
@@ -208,3 +210,16 @@
         Pair(uncheckedShape, interactionSource)
     }
 }
+
+private suspend fun waitUntil(condition: () -> Boolean) {
+    val initialTimeMillis = withFrameMillis { it }
+    while (!condition()) {
+        val timeMillis = withFrameMillis { it }
+        if (timeMillis - initialTimeMillis > MAX_WAIT_TIME_MILLIS) return
+    }
+    return
+}
+
+private const val MAX_WAIT_TIME_MILLIS = 1_000L
+
+private const val MIN_REQUIRED_ANIMATION_PROGRESS = 0.75f
diff --git a/wear/protolayout/OWNERS b/wear/protolayout/OWNERS
index e9f1fd8..3f7fe1b 100644
--- a/wear/protolayout/OWNERS
+++ b/wear/protolayout/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 1235285
 [email protected]
-
[email protected]
diff --git a/wear/tiles/OWNERS b/wear/tiles/OWNERS
index c60ea38..06f9c48 100644
--- a/wear/tiles/OWNERS
+++ b/wear/tiles/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 1112272
 [email protected]
[email protected]
[email protected]
[email protected]
diff --git a/work/work-runtime/build.gradle b/work/work-runtime/build.gradle
index 7ae14b4..94aa053 100644
--- a/work/work-runtime/build.gradle
+++ b/work/work-runtime/build.gradle
@@ -60,8 +60,6 @@
         androidTest.assets.srcDirs += files("$projectDir/src/schemas".toString())
     }
     namespace "androidx.work"
-    // b/370769491
-    experimentalProperties["android.lint.useK2Uast"] = false
 }
 
 dependencies {